Nginx 配置文檔基本結構:從 nginx.conf 到請求匹配流程
前言
前一篇筆記我們簡單認識了 Nginx 是什麼以及它能做什麼,這一篇將聚焦於 Nginx 設定檔的基本結構,以及 Nginx 處理一個 HTTP Request 的流程。為了避免在還沒建立起設定檔結構概念之前就被各種複雜的設定淹沒,我們先理解 main、events、http、server、location 的分工,以及 Nginx 選擇 server block 與 location block 的順序,後續學習反向代理、靜態檔案、HTTPS、redirect、cache 的應用時,我們就能比較清楚知道該把哪些設定放在哪個 context 裡。
從最小 nginx.conf 看懂設定檔的骨架
Nginx 設定檔像是一份分層規則表:不同規則必須放在不同層級,Nginx 啟動或 reload 時會讀取這些規則,之後 request 進來時再依照規則做匹配與處理。
Directive、Block 與 Context 是什麼
Nginx 設定檔裡最基本的單位叫做 directive。一個 directive 通常由名稱、參數與分號組成,例如:
worker_processes auto;
error_log /var/log/nginx/error.log warn;
這種單行設定可以先理解成「設定某個值」。
worker_processes auto;是告訴 Nginx worker process 數量由系統自動決定error_log /var/log/nginx/error.log warn;則是設定錯誤日誌的位置與等級。
另一種 directive 會用 {} 包住更多設定,稱為 block directive。如果一個 block 裡面可以繼續放 directive,這個 block 所形成的作用範圍就稱為 context。例如 events、http、server、location 都是常見 context。
http {
server {
location / {
return 200 "hello nginx\n";
}
}
}
是把 HTTP 相關規則放進 http context,再把某個網站入口規則放進 server context,最後把某段 URL path 的處理方式放進 location context。
Nginx 把設定分層,不只是為了格式好看,而是因為不同規則的影響範圍不同。
- 最外層的設定會影響整個 Nginx process,例如 worker process 數量、錯誤日誌、PID 檔位置。
events關心的是 Nginx 怎麼處理連線。http開始才進入 HTTP 世界,裡面可以放多個server。- 每個
server又可以放多個location。
這種設計讓共通設定可以放在外層,特定網站或特定路徑的設定可以放在內層。好處是:
- 設定更集中:想要改某個網站的 proxy 設定時,不用翻遍整個
nginx.conf,只要找到對應的server或location就可以了。 - 易於維護:每個 block 只負責自己的事情,不會因為修改一個地方就意外影響到其他地方。
- 方便 include:可以把不同 server 的設定拆成不同檔案(例如
sites-available與sites-enabled的常見做法),在http層只用include引入,main file 保持簡潔。
一份最小 nginx.conf 需要包含什麼
如果目標是讓 Nginx 能以 HTTP server 的形式啟動,最 小結構通常會長得像這樣:
events {}
http {
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
}
這份設定非常小,但已經包含了 Nginx 作為 HTTP server 時最重要的骨架:
| 區塊 | 角色 |
|---|---|
events | 告訴 Nginx 連線處理相關設定放在哪裡 |
http | HTTP/HTTPS 流量規則的總容器 |
server | 一組入口規則,通常對應某個 port、domain 或預設站台 |
location | 在某個 server 裡,根據 URL path 決定實際處理方式 |
真正的正式環境通常會再加上 user、worker_processes、error_log、access_log、include mime.types、sendfile、gzip 等設定。不過那些都可以視為在這個骨架上繼續補細節。初學時最重要的是先知道:Nginx 的 HTTP 規則不是散落在任意位置,而是有固定的層級。
Nginx 對 directive 的位置很嚴格。例如 server block 必須放在 http block 裡,location block 必須放在 server block 裡。如果把整段 server { ... } 放到最外層,或把 location { ... } 直接 include 到 http 層,Nginx 會在啟動或 nginx -t 時回報類似 directive is not allowed here 的錯誤。
Main、Events、HTTP:進入網站規則之前的外層設定
Main Context:影響整個 Nginx Process 的設定
所謂 main context,其實就是 nginx.conf 中不在任何 {} 裡面的最外層位置。它沒有一個叫做 main {} 的 block,而是所有頂層 directive 的共同作用範圍。
常見的 main context 設定包含:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
這些設定通常不是在描述某個 domain 或某段 URL path,而是在描述 Nginx process 本身。例如 worker process 要用什麼使用者執行、開幾個 worker、錯誤日誌寫到哪裡、master process 的 PID 放在哪裡。
初學階段不需要急著調整這些值,但要知道它們的層級比 HTTP 規則更外面。也就是說,worker_processes 不是某個網站的設定,而是整個 Nginx 程式的設定。
Events Block:Nginx 如何處理連線
events block 和 Nginx 如何處理 connection 有關,常見設定像是:
events {
worker_connections 1024;
}
worker_connections 可以先理解成每個 worker process 可以同時處理的連線數上限。Nginx 之所以常被稱為高效能 Web Server,很大一部分和它的事件驅動模型有關。不過這篇的目標不是深入 Nginx event loop 或作業系統 I/O 模型,因此目前我們只需要記住一件事:events 是連線處理層,不是網站路由層。
也因為它不是網站路由層,所以一般部署靜態網站或反向代理時,很少需要一開始就修改 events。大多數情況下,保留預設值或只設定 worker_connections 就已經足夠。
HTTP Block:HTTP 世界的總入口
到了 http block,才正式進入 HTTP/HTTPS request 的設定範圍。常見的 nginx.conf 會長得像這樣:
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
include /etc/nginx/conf.d/*.conf;
}
http block 裡可以放所有 HTTP 相關規則,例如 MIME type、access log、gzip、client body 大小限制、proxy 共用設定,也可以放一個或多個 server block。
這裡最容易忽略的是 include。很多 Nginx 安裝方式的主設定檔裡,不一定直接寫很多 server { ... },而是在 http block 裡寫:
include /etc/nginx/conf.d/*.conf;
這代表 /etc/nginx/conf.d/ 底下的 .conf 檔案會被讀進目前的 http context。也就是說,如果 default.conf 裡直接寫 server { ... },它實際上會被插入到 http { ... } 裡,因此語法是合法的。這個觀念我們之後會再回來看,因為它正是 Docker 掛載 default.conf 時最容易搞混的地方。
Server Block:一組入口規則如何被選中
前面已經釐清 http 是 HTTP 世界的總容器。接下來的自然問題是:如果 http 裡可以放很多個 server block,那一個 request 進來時,Nginx 到底會選哪一個?
Server Block 代表什麼
我會把 server block 理解成「一組入口規則」。這組入口規則通常會描述:
server {
listen 80;
server_name example.com www.example.com;
location / {
root /var/www/example;
index index.html;
}
}
這段設定不是在建立一台新的實體 server,也不是在建立一個新的 process。它是在告訴 Nginx:如果有 request 進到 port 80,且 Host header 對應 example.com 或 www.example.com,就使用這一組規則處理。
因此,一個 server block 可以對應一個 domain,也可以對應一個 port,或作為某個 port 的預設網站。當同一台 VPS 上有多個網站時,通常會在同一個 http block 裡放多個 server block,讓 Nginx 根據 request 的入口資訊決定要套用哪一組。
listen:先看請求進到哪個 IP 與 Port
Nginx 選擇 server block 的第一步,是看 request 進到哪個 IP 與 port。這由 listen directive 描述:
server {
listen 80;
server_name example.com;
}
server {
listen 443 ssl;
server_name example.com;
}
listen 80 表示這個 server block 參與 port 80 的 HTTP 流量處理。listen 443 ssl 則表示這個 server block 參與 port 443 的 HTTPS 流量處理,並且會啟用 SSL/TLS 相關處理。
這裡有一個重要的順序:Nginx 不是先看 domain,再看 port。網路連線本身一定是先打到某個 IP 與 port,因此 Nginx 會先找到符合這個 listen socket 的候選 server blocks,再繼續看 server_name。
server_name:再看 Host Header 對應哪個 Domain
找到同一個 IP/port 的候選 server blocks 後,Nginx 會根據 request 裡的 Host header 選擇 server_name 相符的 server block。
例如瀏覽器請求:
GET / HTTP/1.1
Host: blog.example.com
若設定檔裡有:
server {
listen 80;
server_name example.com;
}
server {
listen 80;
server_name blog.example.com;
}
Nginx 會選到 server_name blog.example.com; 的那個 server block。
server_name 不是 DNS 設定。DNS 的工作是把 domain 解析到 IP;而 server_name 是 request 已經抵達 Nginx 之後,Nginx 用來判 斷「這個 Host 應該交給哪個 server block」的規則。DNS 決定流量能不能到這台機器,server_name 決定到達這台機器後由哪組 Nginx 規則接手。
沒有匹配到 server_name 時會怎樣
如果同一個 port 底下沒有任何 server_name 匹配,Nginx 不會因此不知道怎麼辦,而是會把 request 交給該 IP/port 的 default server。
可以明確指定 default server:
server {
listen 80 default_server;
server_name _;
return 404;
}
如果沒有使用 default_server 明確指定,Nginx 通常會使用同一個 listen address/port 中第一個出現的 server block 作為預設值。這也是為什麼實務上常常會刻意寫一個明確的 default server,避免陌生 Host header 意外落到正式網站。
default_server 不是整台 Nginx 只有一個,而是跟特定 listen address/port 綁在一起。listen 80 default_server; 只處理 port 80 的預設流量,port 443 仍然需要自己的 default server 規則。
server_name _ 代表什麼意思?
很多範例會看到:
server {
listen 80 default_server;
server_name _;
return 444;
}
我一開始以為 _ 是 Nginx 的萬用字元,後來才發現這是個很常見的誤解。server_name _; 本身沒有特殊魔法,它只是放了一個正常情況下不會被真實 domain 使用的名稱,用來表達「這不是給任何正式 domain 用的 server block」。真正讓這個 server block 接住未匹配請求的,是 listen 80 default_server;,不是 _。
多 Domain 範例:同一台 Nginx 管多個網站
把 listen 與 server_name 串起來後,多 domain 設定就比較好理解:
server {
listen 80 default_server;
server_name _;
return 404;
}
server {
listen 80;
server_name example.com www.example.com;
location / {
root /var/www/site;
index index.html;
}
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
這份設定背後的思路是:所有 port 80 流量先進 Nginx。若 Host 是 example.com 或 www.example.com,就回靜態網站;若 Host 是 api.example.com,就轉給本機的後端服務;若 Host 是陌生 domain 或直接用 IP 存取,則落到 default server 回 404。
這正是 Nginx 作為網站入口的核心價值之一:外部看起來都是同一台機器上的 port 80,但內部可以根據 Host 分成不同網站與服務。
Location Block:同一個網站裡如何選擇處理規則
理解 server block 後,request 已經被分配到某一組網站入口規則。但一個網站裡還會有很多路徑:首頁、圖片、API、後台、下載檔案。這時候 Nginx 需要在選中的 server block 裡,再用 location block 決定實際處理方式。
Location Block 代表什麼
location 是 server block 內部的路徑規則。
例如:
server {
listen 80;
server_name example.com;
location / {
root /var/www/frontend;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
}
當 request 是 /assets/app.js,可能由 location / 處理並回傳靜態檔案。當 request 是 /api/users,則會命中 /api/,並被轉發到後端服務。
location 的匹配對象是 URI path。/api/users?page=1 在做 location 選擇時,主要看的是 /api/users,query string 不會拿來決定 location。
Prefix Location:最常見的路徑前綴匹配
最常見的 location 是 prefix location,也就是根據路徑前綴匹配:
location / {
root /var/www/site;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
location /assets/ {
root /var/www/site;
}
Prefix location 的核心規則是「找最長的匹配前綴」。如果 request path 是 /api/users,/ 和 /api/ 都能匹配,但 /api/ 比 / 更具體,所以 Nginx 會先記住 /api/ 這個結果。
location / 幾乎可以視為 fallback,因為所有以 / 開頭的 URI 都會匹配它。這也是為什麼很多設定檔都會放一個 location /,作為網站的主要處理規則。