Pages

Thursday, 5 August 2021

解決使用 NGINX proxy_pass 反向代理網站被重定向到錯誤的端口

在使用 NGINX 反代 Gitbook 的時候遇到一個很草的問題,搜遍全網不論中英文都沒有人給出有效的解決辦法。那個問題具體來說是:使用 proxy_pass 進行反代時,若網址尾端不加 /,會被重定向到反代地址的監聽端口 ( NGINX redirect url to the listening port except the correct port of destination without slash ) 經過一番摸索,被我試出一個不太優雅但可行的解決方法。

問題描述

假設我們今天要利用 www.example.com,反代 some.gitbook.io/pages。而為了方便,我們設定 www.example.com/* 的請求會自動重定向到 some.gitbook.io/pages/$1 上。配置文件寫法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
listen 127.0.0.1:8443 ssl http2;
server_name example.com;

ssl_certificate /path/to/fullchain.crt;
ssl_certificate_key /path/to/key.key;
ssl_protocols TLSv1.2 TLSv1.3;

location = / {
return 302 https://www.example.com/pages/;
}


location / {
return 302 https://www.example.com/pages$request_uri;
}

location /pages {
proxy_set_header Host some.gitbook.io:443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_ssl_name some.gitbook.io;
proxy_ssl_server_name on;

proxy_pass https://some.gitbook.io;
}
}

可以看到這裡監聽了 8443 而非 443 端口,這是為了當 Trojan + NGINX Stream 模塊分流的 backend 使用,也是造成報錯的主要原因。

這時候打開以下網址,會發現瀏覽器出現各種截然不同的行為

  1. 訪問 https://www.example.com,會跳到 https://wwww.example.com:8443// ( 錯誤的端口 )
  2. 訪問 https://www.example.com/,會跳到 https://wwww.example.com/pages/ ( 正常跳轉,第一次加戴不出畫面 )
  3. 訪問 https://www.example.com//,會跳到 https://wwww.example.com/pages/ ( 正常跳轉 )
  4. 訪問 https://www.example.com/pages,會跳到 https://wwww.example.com:8443/pages/ ( 錯誤的端口 )
  5. 訪問 https://www.example.com/pages/,可以正常訪問
  6. 訪問 https://www.example.com/pages/*,可以正常訪問

1、4 網址尾端沒有 / 就會報錯,3、5、6 正常訪問,2 正常跳轉但不正常訪問。而且不同瀏覽器對 URL 的作法還不一樣,例如 Firefox / Brave / Safari 會自動補上網址尾部的 slash 所以不會觸發這個 bug;在 Edge Chromium / Safari for iOS 上就會發生。

解決方法

Google 上給出的各種參數,例如 absolute_redirectproxy_redirectproxy_bindport_in_redirectproxy_set_header X-Forwarded-Port 全部沒有用。最後我的解決方法是在 8443 端口設定 301 跳轉,只要導到錯誤端口就跳回去。經過測試整個跳轉過程幾乎是透明的,除了我們自己知道 8443 端口是開的以外,在網速夠快的情況下,使用者很難看出來中間經過了跳轉。完整配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server {
listen 127.0.0.1:8443 ssl http2;
server_name example.com;

ssl_certificate /path/to/fullchain.crt;
ssl_certificate_key /path/to/key.key;
ssl_protocols TLSv1.2 TLSv1.3;

location = / {
return 302 https://www.example.com/pages/;
}


location / {
return 302 https://www.example.com/pages$request_uri;
}

location /pages {
proxy_set_header Host some.gitbook.io:443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_ssl_name some.gitbook.io;
proxy_ssl_server_name on;

proxy_pass https://some.gitbook.io;
}

server {
listen 8443 ssl http2;
server_name www.example.com;

ssl_certificate /path/tofullchain.crt;
ssl_certificate_key /path/to/key.key;
ssl_protocols TLSv1.2 TLSv1.3;

location / {
return 301 https://www.example.com$request_uri;
}
}
}

雖然要多開一個 8443 感覺不夠優雅,不過這樣可以有效解決奇怪的端口跳轉問題,訪問體驗勘稱完美。若有人有更好的解決辦法,歡迎提出。注意,有些瀏覽器需要清除緩存才會看到效果

如果 Web 直接監聽 443 呢

照以上辦法修改完成後,我開始想,既然 http 模塊間支持分開監聽 0.0.0.0:80 和 127.0.0.1:80 ( 其他端口同理 ),那 stream 模塊(分流模塊)和 http 模塊(我們的反代網站),能不能一個監聽 0.0.0.0:443、另一個藍聽127.0.0.1:443呢?測試結果是不行的,stream 模塊和 http 模塊監聽同一個端口時,NGINX 會報錯 [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)

結論

要觸發這個問題,從我的情況來看要滿足三個條件

  1. 網站做為 stream 模塊的 upstream,監聽與被反代網站不同的端口
  2. 網址尾端不加 slash
  3. 特定瀏覽器,己知目前版本 Edge Chromium 和 Safari for iOS 有可能觸發(草的是 Edge Chromium 無痕模式正常)

解決這個問題的思路是,在網站端口的0.0.0.0 建立 301 跳轉,當出現問題時自動跳轉回正確的端口。

Reference

No comments:

Post a Comment