diff --git a/modules/private/hook.go b/modules/private/hook.go
index 87d6549f9c5..215996b9b99 100644
--- a/modules/private/hook.go
+++ b/modules/private/hook.go
@@ -7,9 +7,9 @@ import (
 	"context"
 	"fmt"
 	"net/url"
-	"time"
 
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -82,29 +82,32 @@ type HookProcReceiveRefResult struct {
 	HeadBranch        string
 }
 
+func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, opts HookOptions) *httplib.Request {
+	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s", hookName, url.PathEscape(ownerName), url.PathEscape(repoName))
+	req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
+	// This "timeout" applies to http.Client's timeout: A Timeout of zero means no timeout.
+	// This "timeout" was previously set to `time.Duration(60+len(opts.OldCommitIDs))` seconds, but it caused unnecessary timeout failures.
+	// It should be good enough to remove the client side timeout, only respect the "ctx" and server side timeout.
+	req.SetReadWriteTimeout(0)
+	return req
+}
+
 // HookPreReceive check whether the provided commits are allowed
 func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
-	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
-	req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
-	req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+	req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, opts)
 	_, extra := requestJSONResp(req, &ResponseText{})
 	return extra
 }
 
 // HookPostReceive updates services and users
 func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
-	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
-	req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
-	req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+	req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, opts)
 	return requestJSONResp(req, &HookPostReceiveResult{})
 }
 
 // HookProcReceive proc-receive hook
 func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
-	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
-
-	req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
-	req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+	req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, opts)
 	return requestJSONResp(req, &HookProcReceiveResult{})
 }