Skip to content

Commit a58498c

Browse files
GiteaBotwxiaoguangKN4CK3R
authored
Improve reverse proxy documents and clarify the AppURL guessing behavior (#31003) (#31020)
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>
1 parent 8eac16d commit a58498c

File tree

5 files changed

+78
-61
lines changed

5 files changed

+78
-61
lines changed

docs/content/administration/reverse-proxies.en-us.md

+49-43
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,35 @@ menu:
1717

1818
# Reverse Proxies
1919

20+
## General configuration
21+
22+
1. Set `[server] ROOT_URL = https://git.example.com/` in your `app.ini` file.
23+
2. Make the reverse-proxy pass `https://git.example.com/foo` to `http://gitea:3000/foo`.
24+
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`.
25+
4. Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea to make Gitea see the real URL being visited.
26+
27+
### Use a sub-path
28+
29+
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.
30+
31+
To make Gitea work with a sub-path (eg: `https://common.example.com/gitea/`),
32+
there are some extra requirements besides the general configuration above:
33+
34+
1. Use `[server] ROOT_URL = https://common.example.com/gitea/` in your `app.ini` file.
35+
2. Make the reverse-proxy pass `https://common.example.com/gitea/foo` to `http://gitea:3000/foo`.
36+
3. The container registry requires a fixed sub-path `/v2` at the root level which must be configured:
37+
- Make the reverse-proxy pass `https://common.example.com/v2` to `http://gitea:3000/v2`.
38+
- Make sure the URI and headers are also correctly passed (see the general configuration above).
39+
2040
## Nginx
2141

22-
If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`:
42+
If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`.
2343

24-
```
25-
server {
26-
listen 80;
27-
server_name git.example.com;
44+
Make sure `client_max_body_size` is large enough, otherwise there would be "413 Request Entity Too Large" error when uploading large files.
2845

46+
```nginx
47+
server {
48+
...
2949
location / {
3050
client_max_body_size 512M;
3151
proxy_pass http://localhost:3000;
@@ -39,37 +59,35 @@ server {
3959
}
4060
```
4161

42-
### Resolving Error: 413 Request Entity Too Large
43-
44-
This error indicates nginx is configured to restrict the file upload size,
45-
it affects attachment uploading, form posting, package uploading and LFS pushing, etc.
46-
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).
47-
4862
## Nginx with a sub-path
4963

50-
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`:
64+
In case you already have a site, and you want Gitea to share the domain name,
65+
you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section
66+
into the `http` section of `nginx.conf`:
5167

52-
```
68+
```nginx
5369
server {
54-
listen 80;
55-
server_name git.example.com;
56-
57-
# Note: Trailing slash
58-
location /gitea/ {
70+
...
71+
location ~ ^/(gitea|v2)($|/) {
5972
client_max_body_size 512M;
6073
61-
# make nginx use unescaped URI, keep "%2F" as is
74+
# make nginx use unescaped URI, keep "%2F" as-is, remove the "/gitea" sub-path prefix, pass "/v2" as-is.
6275
rewrite ^ $request_uri;
63-
rewrite ^/gitea(/.*) $1 break;
76+
rewrite ^(/gitea)?(/.*) $2 break;
6477
proxy_pass http://127.0.0.1:3000$uri;
6578
6679
# other common HTTP headers, see the "Nginx" config section above
67-
proxy_set_header ...
80+
proxy_set_header Connection $http_connection;
81+
proxy_set_header Upgrade $http_upgrade;
82+
proxy_set_header Host $host;
83+
proxy_set_header X-Real-IP $remote_addr;
84+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
85+
proxy_set_header X-Forwarded-Proto $scheme;
6886
}
6987
}
7088
```
7189

72-
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/git/` correctly in your configuration.
90+
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/gitea/` correctly in your configuration.
7391

7492
## Nginx and serve static resources directly
7593

@@ -93,7 +111,7 @@ or use a cdn for the static files.
93111

94112
Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
95113

96-
```apacheconf
114+
```nginx
97115
server {
98116
listen 80;
99117
server_name git.example.com;
@@ -112,7 +130,7 @@ server {
112130

113131
Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration.
114132

115-
```apacheconf
133+
```nginx
116134
# application server running Gitea
117135
server {
118136
listen 80;
@@ -124,7 +142,7 @@ server {
124142
}
125143
```
126144

127-
```apacheconf
145+
```nginx
128146
# static content delivery server
129147
server {
130148
listen 80;
@@ -151,6 +169,8 @@ If you want Apache HTTPD to serve your Gitea instance, you can add the following
151169
ProxyRequests off
152170
AllowEncodedSlashes NoDecode
153171
ProxyPass / http://localhost:3000/ nocanon
172+
ProxyPreserveHost On
173+
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
154174
</VirtualHost>
155175
```
156176

@@ -172,6 +192,8 @@ In case you already have a site, and you want Gitea to share the domain name, yo
172192
AllowEncodedSlashes NoDecode
173193
# Note: no trailing slash after either /git or port
174194
ProxyPass /git http://localhost:3000 nocanon
195+
ProxyPreserveHost On
196+
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
175197
</VirtualHost>
176198
```
177199

@@ -183,7 +205,7 @@ Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`.
183205

184206
If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile:
185207

186-
```apacheconf
208+
```
187209
git.example.com {
188210
reverse_proxy localhost:3000
189211
}
@@ -193,7 +215,7 @@ git.example.com {
193215

194216
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:
195217

196-
```apacheconf
218+
```
197219
git.example.com {
198220
route /git/* {
199221
uri strip_prefix /git
@@ -371,19 +393,3 @@ gitea:
371393
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
372394
373395
Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.
374-
375-
## General sub-path configuration
376-
377-
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.
378-
379-
If you really need to do so, to make Gitea works with sub-path (eg: `http://example.com/gitea/`), here are the requirements:
380-
381-
1. Set `[server] ROOT_URL = http://example.com/gitea/` in your `app.ini` file.
382-
2. Make the reverse-proxy pass `http://example.com/gitea/foo` to `http://gitea-server:3000/foo`.
383-
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`.
384-
385-
## Docker / Container Registry
386-
387-
The container registry uses a fixed sub-path `/v2` which can't be changed.
388-
Even if you deploy Gitea with a different sub-path, `/v2` will be used by the `docker` client.
389-
Therefore you may need to add an additional route to your reverse proxy configuration.

modules/httplib/url.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func IsRelativeURL(s string) bool {
3232
return err == nil && urlIsRelative(s, u)
3333
}
3434

35-
func guessRequestScheme(req *http.Request, def string) string {
35+
func getRequestScheme(req *http.Request) string {
3636
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
3737
if s := req.Header.Get("X-Forwarded-Proto"); s != "" {
3838
return s
@@ -49,10 +49,10 @@ func guessRequestScheme(req *http.Request, def string) string {
4949
if s := req.Header.Get("X-Forwarded-Ssl"); s != "" {
5050
return util.Iif(s == "on", "https", "http")
5151
}
52-
return def
52+
return ""
5353
}
5454

55-
func guessForwardedHost(req *http.Request) string {
55+
func getForwardedHost(req *http.Request) string {
5656
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
5757
return req.Header.Get("X-Forwarded-Host")
5858
}
@@ -63,15 +63,24 @@ func GuessCurrentAppURL(ctx context.Context) string {
6363
if !ok {
6464
return setting.AppURL
6565
}
66-
if host := guessForwardedHost(req); host != "" {
67-
// 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
68-
return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/"
69-
} else if req.Host != "" {
70-
// if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible
71-
defaultScheme := util.Iif(setting.Protocol == "http", "http", "https")
72-
return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/"
66+
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
67+
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
68+
// There are some cases:
69+
// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
70+
// 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.
71+
// 3. There is no reverse proxy.
72+
// Without an extra config option, Gitea is impossible to distinguish between case 2 and case 3,
73+
// then case 2 would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users.
74+
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
75+
reqScheme := getRequestScheme(req)
76+
if reqScheme == "" {
77+
return setting.AppURL
78+
}
79+
reqHost := getForwardedHost(req)
80+
if reqHost == "" {
81+
reqHost = req.Host
7382
}
74-
return setting.AppURL
83+
return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
7584
}
7685

7786
func MakeAbsoluteURL(ctx context.Context, s string) string {

modules/httplib/url_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,27 @@ func TestIsRelativeURL(t *testing.T) {
4141

4242
func TestMakeAbsoluteURL(t *testing.T) {
4343
defer test.MockVariableValue(&setting.Protocol, "http")()
44-
defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")()
44+
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
4545
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
4646

4747
ctx := context.Background()
48-
assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, ""))
49-
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
50-
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
48+
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
49+
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
50+
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
5151
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
5252

5353
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
5454
Host: "user-host",
5555
})
56-
assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
56+
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
5757

5858
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
5959
Host: "user-host",
6060
Header: map[string][]string{
6161
"X-Forwarded-Host": {"forwarded-host"},
6262
},
6363
})
64-
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
64+
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
6565

6666
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
6767
Host: "user-host",

routers/api/packages/container/container.go

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
116116
}
117117

118118
func apiUnauthorizedError(ctx *context.Context) {
119+
// TODO: it doesn't seem quite right but it doesn't really cause problem at the moment.
120+
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed, ideally.
119121
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`)
120122
apiErrorDefined(ctx, errUnauthorized)
121123
}

routers/web/admin/admin_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,6 @@ func TestSelfCheckPost(t *testing.T) {
8787
err := json.Unmarshal(resp.Body.Bytes(), &data)
8888
assert.NoError(t, err)
8989
assert.Equal(t, []string{
90-
ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://host/sub/"),
90+
ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://config/sub/"),
9191
}, data.Problems)
9292
}

0 commit comments

Comments
 (0)