TL;DR
如果你在處理 Nginx 重定向時要獲取原請求的域名(比如 HTTP 到 HTTPS),請用 $host 而不是 $server_name 。
問題和解決方案
今天碰到一個問題,服務器上一個子域名的請求重定向到另一個子域名上面去了。查了一段時間發現這個問題只有在 HTTP 到 HTTPS 跳轉的時候才會發生。大概是這樣:
從 HTTP 的 sub2 子域名跳轉到 HTTPS 的 sub1 子域名
http://sub2.example.com/more_things -> https://sub1.example.com/more_things
我用的 Nginx ,當初為了讓 HTTP 請求跳轉到同名的 HTTPS 請求,配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
http { server { listen 80; server_name sub1.example.com sub2.example.com; return 301 https: // $server_name$request_uri; } server { listen 443 ssl spdy; server_name sub1.example.com sub2.example.com; # ... } } |
因為 301 是永久重定向,某些瀏覽器的緩存會記住重定向,下次訪問原地址就會直接向新地址發請求,所以這個問題在瀏覽器里面不一定重現得了(包括 Chrome 的 Incognito Window),能每次完整重現的方式只有 curl 。
1
2
3
4
5
6
7
8
9
|
$ curl -I http://sub2.example.com/ HTTP/1.1 301 Moved Permanently Server: nginx/1.9.3 (Ubuntu) Date: Tue, 23 Feb 2016 06:06:30 GMT Content-Type: text/html Content-Length: 193 Connection: keep-alive Location: https://sub1.example.com/ |
查了一下,發現問題出在 $server_name
變量上。這個變量會始終返回 server_name 中第一個名字。這里其實應該用 $host
變量。修改后的配置如下:
1
2
3
4
5
6
7
|
http { server { listen 80; server_name sub1.example.com sub2.example.com; return 301 https: // $host$request_uri; } } |
$host
變量會按照以下優先級獲取域名:
- Request-Line 中的域名信息。Request-Line 包含 method, uri 和 HTTP 版本。
- 請求頭信息中的 "Host" 。
- Nginx 中匹配的 server_name 配置。
這幾乎可以保證在任何環境下正確地得到域名。如果是同域名下的重定向最好都用 $host 。
參考資料
Nginx Wiki - $host
Nginx 官方文檔。其中對 $host 講的比較詳細,但 $server_name 只是一筆帶過。
StackOverflow - What is the difference between Nginx variables $host, $http_host, and $server_name?
StackOverflow 上關于三個變量區別的討論。里面提到了為什么 $host 是適用于所有場景的唯一選擇。
HTTP/1.1 : Request-Line
HTTP/1.1 規范中對 Request-Line 的描述。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000004477106