mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Backport #31003 by wxiaoguang Fix #31002 1. Mention Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea 2. Clarify the basic requirements and move the "general configuration" to the top 3. Add a comment for the "container registry" 4. Use 1.21 behavior if the reverse proxy is not correctly configured Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
This commit is contained in:
		| @@ -17,15 +17,35 @@ menu: | |||||||
|  |  | ||||||
| # Reverse Proxies | # Reverse Proxies | ||||||
|  |  | ||||||
|  | ## General configuration | ||||||
|  |  | ||||||
|  | 1. Set `[server] ROOT_URL = https://git.example.com/` in your `app.ini` file. | ||||||
|  | 2. Make the reverse-proxy pass `https://git.example.com/foo` to `http://gitea:3000/foo`. | ||||||
|  | 3. Make sure the reverse-proxy does not decode the URI. The request `https://git.example.com/a%2Fb` should be passed as `http://gitea:3000/a%2Fb`. | ||||||
|  | 4. Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea to make Gitea see the real URL being visited. | ||||||
|  |  | ||||||
|  | ### Use a sub-path | ||||||
|  |  | ||||||
|  | Usually it's **not recommended** to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases. | ||||||
|  |  | ||||||
|  | To make Gitea work with a sub-path (eg: `https://common.example.com/gitea/`), | ||||||
|  | there are some extra requirements besides the general configuration above: | ||||||
|  |  | ||||||
|  | 1. Use `[server] ROOT_URL = https://common.example.com/gitea/` in your `app.ini` file. | ||||||
|  | 2. Make the reverse-proxy pass `https://common.example.com/gitea/foo` to `http://gitea:3000/foo`. | ||||||
|  | 3. The container registry requires a fixed sub-path `/v2` at the root level which must be configured: | ||||||
|  |    - Make the reverse-proxy pass `https://common.example.com/v2` to `http://gitea:3000/v2`. | ||||||
|  |    - Make sure the URI and headers are also correctly passed (see the general configuration above). | ||||||
|  |  | ||||||
| ## Nginx | ## Nginx | ||||||
|  |  | ||||||
| If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`: | If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`. | ||||||
|  |  | ||||||
| ``` | Make sure `client_max_body_size` is large enough, otherwise there would be "413 Request Entity Too Large" error when uploading large files. | ||||||
|  |  | ||||||
|  | ```nginx | ||||||
| server { | server { | ||||||
|     listen 80; |     ... | ||||||
|     server_name git.example.com; |  | ||||||
|  |  | ||||||
|     location / { |     location / { | ||||||
|         client_max_body_size 512M; |         client_max_body_size 512M; | ||||||
|         proxy_pass http://localhost:3000; |         proxy_pass http://localhost:3000; | ||||||
| @@ -39,37 +59,35 @@ server { | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Resolving Error: 413 Request Entity Too Large |  | ||||||
|  |  | ||||||
| This error indicates nginx is configured to restrict the file upload size, |  | ||||||
| it affects attachment uploading, form posting, package uploading and LFS pushing, etc. |  | ||||||
| You can fine tune the `client_max_body_size` option according to [nginx document](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size). |  | ||||||
|  |  | ||||||
| ## Nginx with a sub-path | ## Nginx with a sub-path | ||||||
|  |  | ||||||
| In case you already have a site, and you want Gitea to share the domain name, you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section inside the `http` section of `nginx.conf`: | In case you already have a site, and you want Gitea to share the domain name, | ||||||
|  | you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section | ||||||
|  | into the `http` section of `nginx.conf`: | ||||||
|  |  | ||||||
| ``` | ```nginx | ||||||
| server { | server { | ||||||
|     listen 80; |     ... | ||||||
|     server_name git.example.com; |     location ~ ^/(gitea|v2)($|/) { | ||||||
|  |  | ||||||
|     # Note: Trailing slash |  | ||||||
|     location /gitea/ { |  | ||||||
|         client_max_body_size 512M; |         client_max_body_size 512M; | ||||||
|  |  | ||||||
|         # make nginx use unescaped URI, keep "%2F" as is |         # make nginx use unescaped URI, keep "%2F" as-is, remove the "/gitea" sub-path prefix, pass "/v2" as-is. | ||||||
|         rewrite ^ $request_uri; |         rewrite ^ $request_uri; | ||||||
|         rewrite ^/gitea(/.*) $1 break; |         rewrite ^(/gitea)?(/.*) $2 break; | ||||||
|         proxy_pass http://127.0.0.1:3000$uri; |         proxy_pass http://127.0.0.1:3000$uri; | ||||||
|  |  | ||||||
|         # other common HTTP headers, see the "Nginx" config section above |         # other common HTTP headers, see the "Nginx" config section above | ||||||
|         proxy_set_header ... |         proxy_set_header Connection $http_connection; | ||||||
|  |         proxy_set_header Upgrade $http_upgrade; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Real-IP $remote_addr; | ||||||
|  |         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||||
|  |         proxy_set_header X-Forwarded-Proto $scheme; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/git/` correctly in your configuration. | Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/gitea/` correctly in your configuration. | ||||||
|  |  | ||||||
| ## Nginx and serve static resources directly | ## Nginx and serve static resources directly | ||||||
|  |  | ||||||
| @@ -93,7 +111,7 @@ or use a cdn for the static files. | |||||||
|  |  | ||||||
| Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration. | Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration. | ||||||
|  |  | ||||||
| ```apacheconf | ```nginx | ||||||
| server { | server { | ||||||
|     listen 80; |     listen 80; | ||||||
|     server_name git.example.com; |     server_name git.example.com; | ||||||
| @@ -112,7 +130,7 @@ server { | |||||||
|  |  | ||||||
| Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration. | Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration. | ||||||
|  |  | ||||||
| ```apacheconf | ```nginx | ||||||
| # application server running Gitea | # application server running Gitea | ||||||
| server { | server { | ||||||
|     listen 80; |     listen 80; | ||||||
| @@ -124,7 +142,7 @@ server { | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ```apacheconf | ```nginx | ||||||
| # static content delivery server | # static content delivery server | ||||||
| server { | server { | ||||||
|     listen 80; |     listen 80; | ||||||
| @@ -151,6 +169,8 @@ If you want Apache HTTPD to serve your Gitea instance, you can add the following | |||||||
|     ProxyRequests off |     ProxyRequests off | ||||||
|     AllowEncodedSlashes NoDecode |     AllowEncodedSlashes NoDecode | ||||||
|     ProxyPass / http://localhost:3000/ nocanon |     ProxyPass / http://localhost:3000/ nocanon | ||||||
|  |     ProxyPreserveHost On | ||||||
|  |     RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME} | ||||||
| </VirtualHost> | </VirtualHost> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -172,6 +192,8 @@ In case you already have a site, and you want Gitea to share the domain name, yo | |||||||
|     AllowEncodedSlashes NoDecode |     AllowEncodedSlashes NoDecode | ||||||
|     # Note: no trailing slash after either /git or port |     # Note: no trailing slash after either /git or port | ||||||
|     ProxyPass /git http://localhost:3000 nocanon |     ProxyPass /git http://localhost:3000 nocanon | ||||||
|  |     ProxyPreserveHost On | ||||||
|  |     RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME} | ||||||
| </VirtualHost> | </VirtualHost> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -183,7 +205,7 @@ Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`. | |||||||
|  |  | ||||||
| If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile: | If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile: | ||||||
|  |  | ||||||
| ```apacheconf | ``` | ||||||
| git.example.com { | git.example.com { | ||||||
|     reverse_proxy localhost:3000 |     reverse_proxy localhost:3000 | ||||||
| } | } | ||||||
| @@ -193,7 +215,7 @@ git.example.com { | |||||||
|  |  | ||||||
| In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile: | In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile: | ||||||
|  |  | ||||||
| ```apacheconf | ``` | ||||||
| git.example.com { | git.example.com { | ||||||
|     route /git/* { |     route /git/* { | ||||||
|         uri strip_prefix /git |         uri strip_prefix /git | ||||||
| @@ -371,19 +393,3 @@ gitea: | |||||||
| This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik. | This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik. | ||||||
|  |  | ||||||
| Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration. | Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration. | ||||||
|  |  | ||||||
| ## General sub-path configuration |  | ||||||
|  |  | ||||||
| Usually it's not recommended to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases. |  | ||||||
|  |  | ||||||
| If you really need to do so, to make Gitea works with sub-path (eg: `http://example.com/gitea/`), here are the requirements: |  | ||||||
|  |  | ||||||
| 1. Set `[server] ROOT_URL = http://example.com/gitea/` in your `app.ini` file. |  | ||||||
| 2. Make the reverse-proxy pass `http://example.com/gitea/foo` to `http://gitea-server:3000/foo`. |  | ||||||
| 3. Make sure the reverse-proxy not decode the URI, the request `http://example.com/gitea/a%2Fb` should be passed as `http://gitea-server:3000/a%2Fb`. |  | ||||||
|  |  | ||||||
| ## Docker / Container Registry |  | ||||||
|  |  | ||||||
| The container registry uses a fixed sub-path `/v2` which can't be changed. |  | ||||||
| Even if you deploy Gitea with a different sub-path, `/v2` will be used by the `docker` client. |  | ||||||
| Therefore you may need to add an additional route to your reverse proxy configuration. |  | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ func IsRelativeURL(s string) bool { | |||||||
| 	return err == nil && urlIsRelative(s, u) | 	return err == nil && urlIsRelative(s, u) | ||||||
| } | } | ||||||
|  |  | ||||||
| func guessRequestScheme(req *http.Request, def string) string { | func getRequestScheme(req *http.Request) string { | ||||||
| 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto | 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto | ||||||
| 	if s := req.Header.Get("X-Forwarded-Proto"); s != "" { | 	if s := req.Header.Get("X-Forwarded-Proto"); s != "" { | ||||||
| 		return s | 		return s | ||||||
| @@ -49,10 +49,10 @@ func guessRequestScheme(req *http.Request, def string) string { | |||||||
| 	if s := req.Header.Get("X-Forwarded-Ssl"); s != "" { | 	if s := req.Header.Get("X-Forwarded-Ssl"); s != "" { | ||||||
| 		return util.Iif(s == "on", "https", "http") | 		return util.Iif(s == "on", "https", "http") | ||||||
| 	} | 	} | ||||||
| 	return def | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
| func guessForwardedHost(req *http.Request) string { | func getForwardedHost(req *http.Request) string { | ||||||
| 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host | 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host | ||||||
| 	return req.Header.Get("X-Forwarded-Host") | 	return req.Header.Get("X-Forwarded-Host") | ||||||
| } | } | ||||||
| @@ -63,15 +63,24 @@ func GuessCurrentAppURL(ctx context.Context) string { | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return setting.AppURL | 		return setting.AppURL | ||||||
| 	} | 	} | ||||||
| 	if host := guessForwardedHost(req); host != "" { | 	// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one. | ||||||
| 		// if it is behind a reverse proxy, use "https" as default scheme in case the site admin forgets to set the correct forwarded-protocol headers | 	// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong. | ||||||
| 		return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/" | 	// There are some cases: | ||||||
| 	} else if req.Host != "" { | 	// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly. | ||||||
| 		// if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible | 	// 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx. | ||||||
| 		defaultScheme := util.Iif(setting.Protocol == "http", "http", "https") | 	// 3. There is no reverse proxy. | ||||||
| 		return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/" | 	// Without an extra config option, Gitea is impossible to distinguish between case 2 and case 3, | ||||||
|  | 	// then case 2 would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users. | ||||||
|  | 	// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL. | ||||||
|  | 	reqScheme := getRequestScheme(req) | ||||||
|  | 	if reqScheme == "" { | ||||||
|  | 		return setting.AppURL | ||||||
| 	} | 	} | ||||||
| 	return setting.AppURL | 	reqHost := getForwardedHost(req) | ||||||
|  | 	if reqHost == "" { | ||||||
|  | 		reqHost = req.Host | ||||||
|  | 	} | ||||||
|  | 	return reqScheme + "://" + reqHost + setting.AppSubURL + "/" | ||||||
| } | } | ||||||
|  |  | ||||||
| func MakeAbsoluteURL(ctx context.Context, s string) string { | func MakeAbsoluteURL(ctx context.Context, s string) string { | ||||||
|   | |||||||
| @@ -41,19 +41,19 @@ func TestIsRelativeURL(t *testing.T) { | |||||||
|  |  | ||||||
| func TestMakeAbsoluteURL(t *testing.T) { | func TestMakeAbsoluteURL(t *testing.T) { | ||||||
| 	defer test.MockVariableValue(&setting.Protocol, "http")() | 	defer test.MockVariableValue(&setting.Protocol, "http")() | ||||||
| 	defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")() | 	defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")() | ||||||
| 	defer test.MockVariableValue(&setting.AppSubURL, "/sub")() | 	defer test.MockVariableValue(&setting.AppSubURL, "/sub")() | ||||||
|  |  | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 	assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, "")) | 	assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, "")) | ||||||
| 	assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo")) | 	assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo")) | ||||||
| 	assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | 	assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | ||||||
| 	assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo")) | 	assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo")) | ||||||
|  |  | ||||||
| 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | ||||||
| 		Host: "user-host", | 		Host: "user-host", | ||||||
| 	}) | 	}) | ||||||
| 	assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | 	assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | ||||||
|  |  | ||||||
| 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | ||||||
| 		Host: "user-host", | 		Host: "user-host", | ||||||
| @@ -61,7 +61,7 @@ func TestMakeAbsoluteURL(t *testing.T) { | |||||||
| 			"X-Forwarded-Host": {"forwarded-host"}, | 			"X-Forwarded-Host": {"forwarded-host"}, | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| 	assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | 	assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo")) | ||||||
|  |  | ||||||
| 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | 	ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ | ||||||
| 		Host: "user-host", | 		Host: "user-host", | ||||||
|   | |||||||
| @@ -116,6 +116,8 @@ func apiErrorDefined(ctx *context.Context, err *namedError) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func apiUnauthorizedError(ctx *context.Context) { | func apiUnauthorizedError(ctx *context.Context) { | ||||||
|  | 	// TODO: it doesn't seem quite right but it doesn't really cause problem at the moment. | ||||||
|  | 	// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed, ideally. | ||||||
| 	ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`) | 	ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`) | ||||||
| 	apiErrorDefined(ctx, errUnauthorized) | 	apiErrorDefined(ctx, errUnauthorized) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -87,6 +87,6 @@ func TestSelfCheckPost(t *testing.T) { | |||||||
| 	err := json.Unmarshal(resp.Body.Bytes(), &data) | 	err := json.Unmarshal(resp.Body.Bytes(), &data) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, []string{ | 	assert.Equal(t, []string{ | ||||||
| 		ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://host/sub/"), | 		ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://config/sub/"), | ||||||
| 	}, data.Problems) | 	}, data.Problems) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Giteabot
					Giteabot