diff --git a/modules/context/api.go b/modules/context/api.go
index 558a9f51ee..b9d130e2a8 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -16,6 +16,7 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/web/middleware"
@@ -268,6 +269,7 @@ func APIContexter() func(http.Handler) http.Handler {
 				}
 			}
 
+			httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
 			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 			ctx.Data["Context"] = &ctx
diff --git a/modules/context/context.go b/modules/context/context.go
index 68f8a1b408..8824911619 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -28,6 +28,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	mc "code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -767,6 +768,7 @@ func Contexter() func(next http.Handler) http.Handler {
 				}
 			}
 
+			httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
 			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 			ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go
index 5797e981cf..750233d4a7 100644
--- a/modules/httpcache/httpcache.go
+++ b/modules/httpcache/httpcache.go
@@ -17,16 +17,23 @@ import (
 )
 
 // AddCacheControlToHeader adds suitable cache-control headers to response
-func AddCacheControlToHeader(h http.Header, d time.Duration) {
+func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
+	directives := make([]string, 0, 2+len(additionalDirectives))
+
 	if setting.IsProd {
-		h.Set("Cache-Control", "private, max-age="+strconv.Itoa(int(d.Seconds())))
+		if maxAge == 0 {
+			directives = append(directives, "no-store")
+		} else {
+			directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
+		}
 	} else {
-		h.Set("Cache-Control", "no-store")
+		directives = append(directives, "no-store")
+
 		// to remind users they are using non-prod setting.
-		// some users may be confused by "Cache-Control: no-store" in their setup if they did wrong to `RUN_MODE` in `app.ini`.
 		h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
-		h.Add("X-Gitea-Debug", "CacheControl=no-store")
 	}
+
+	h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
 }
 
 // generateETag generates an ETag based on size, filename and file modification time
diff --git a/routers/install/routes.go b/routers/install/routes.go
index 32829ede9e..fdabcb9dc2 100644
--- a/routers/install/routes.go
+++ b/routers/install/routes.go
@@ -9,6 +9,7 @@ import (
 	"net/http"
 	"path"
 
+	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/public"
 	"code.gitea.io/gitea/modules/setting"
@@ -62,6 +63,7 @@ func installRecovery() func(next http.Handler) http.Handler {
 						"SignedUserName": "",
 					}
 
+					httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
 					w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 					if !setting.IsProd {
diff --git a/routers/web/base.go b/routers/web/base.go
index c7ade55a61..30a24a1275 100644
--- a/routers/web/base.go
+++ b/routers/web/base.go
@@ -158,6 +158,7 @@ func Recovery() func(next http.Handler) http.Handler {
 						store["SignedUserName"] = ""
 					}
 
+					httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
 					w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 					if !setting.IsProd {