Nginx 是一個高效能的 HTTP 和反向代理伺服器,也是一個 IMAP/POP3/SMTP 代理伺服器,以高併發處理能力和低資源消耗著稱。安裝 Nginx 只需要幾條命令,幾分鐘就能跑起來;但一個網站真正的行為——它怎麼分發請求、怎麼代理到後端、怎麼處理舊連結——全都寫在設定檔裡。本文只會簡單帶過安裝,把大部分篇幅留給設定:設定檔是怎麼組織的、location 匹配的優先順序是什麼、反向代理怎麼寫、靜態資源怎麼快取、舊的 URL 怎麼遷移。最後一節會給出本站生產環境實際在用的設定,作為一個可以直接對照閱讀的完整例子。
1. 安裝 Nginx
日常使用直接裝包管理版本就夠了:
sudo apt install nginx && sudo systemctl enable --now nginx # Debian/Ubuntu
sudo dnf install nginx && sudo systemctl enable --now nginx # CentOS/Fedora
設定檔在 /etc/nginx/nginx.conf,站點相關的設定通常放在 /etc/nginx/conf.d/*.conf(RPM 系)或 sites-available/ + sites-enabled/(Debian 系)。只有需要特定版本、或者需要包管理版本沒編譯進去的模組時,才值得從原始碼編譯:裝好編譯依賴後 ./configure 加上要開啟的模組、make && make install 即可,./configure 的參數只在編譯時生效,以後想加模組就得重新編譯整個二進位制:
sudo apt install build-essential libpcre3-dev zlib1g-dev libssl-dev
wget http://nginx.org/download/nginx-<version>.tar.gz && tar -zxvf nginx-<version>.tar.gz
cd nginx-<version> && ./configure --prefix=/usr/local/nginx --with-http_ssl_module && make && sudo make install
2. 設定檔是怎麼組織的
Nginx 的設定由一系列**指令(directive)和塊(context)**構成,塊之間是巢狀關係,內層塊會繼承外層的設定,也可以覆蓋它。核心的幾層是:
main(全局)
├─ events { ... } 连接处理相关,比如 worker_connections
├─ http { ... } HTTP 服务的总配置
│ ├─ server { ... } 一个虚拟主机
│ │ └─ location { ... } 一条 URL 匹配规则
│ └─ server { ... } 另一个虚拟主机
├─ mail { ... } 邮件代理相关(用得较少)
└─ stream { ... } TCP/UDP 四层代理
- main 層的指令直接寫在檔案最外層,例如
worker_processes(worker 行程數,一般設成auto讓 Nginx 按 CPU 核心數自動決定)、pid(主行程 PID 檔案位置)。 - events 塊只關心連線層面的參數,最常調的是
worker_connections,即單個 worker 能同時維持的連線數上限。 - http 塊是絕大多數設定真正發生的地方:日誌格式、gzip、超時、以及下面掛的所有
server。 - 一個
http塊裡可以有多個server塊,靠listen和server_name區分不同的虛擬主機;一個server塊裡可以有多個location塊,靠 URL 路徑區分不同的處理方式。
實際專案裡,http 塊通常不會把所有內容寫在一個檔案裡,而是用 include 把設定拆開:
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
...
}
這樣每個站點、每個功能可以單獨維護一個檔案,改動互不干擾,也方便按需 include 一些獨立的對映表(下面第 6 節會用到)。
3. server 塊:一個虛擬主機長什麼樣
server {
listen 80;
server_name example.com www.example.com;
location / {
root /var/www/html;
index index.html index.htm;
}
error_page 404 /404.html;
location = /404.html {
root /var/www/html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}
}
listen 決定監聽的埠(和可選的 IP),server_name 決定這個塊響應哪些域名——同一個埠上可以掛很多個 server_name 不同的 server 塊,Nginx 會按 Host 請求頭匹配到對應的一個。root 指定這個虛擬主機的檔案根目錄,index 指定目錄請求時依次嘗試的預設檔案。error_page 把特定狀態碼的響應重寫到指定路徑,常用來做統一的 404/50x 頁面。
4. location 匹配規則與優先順序
location 決定“這個 URL 該怎麼處理”,Nginx 支援好幾種匹配寫法,理解它們的優先順序比記住語法本身更重要:
location = /path精確匹配,只有 URL 完全等於/path才命中,優先順序最高。location ^~ /prefix字首匹配,一旦匹配上就不再往下檢查正則,優先順序高於普通正則。location ~ /pattern/location ~* /pattern正則匹配(~區分大小寫,~*不區分),按設定檔中出現的順序依次嘗試。location /prefix普通字首匹配,如果沒有更優先的規則命中,取匹配最長的那個字首。
也就是說,Nginx 不是“從上到下第一個匹配就用”,而是按這四類的優先順序依次篩選,只有在精確匹配和 ^~ 字首都沒命中時才會去看正則。寫多個 location 時最容易踩的坑就是以為它們是順序生效的——實際上除了同類正則之間是順序生效,其他型別之間是按優先順序,不是按書寫順序。
location = /healthz {
return 200 "ok\n";
}
location ~* \.(?:jpg|jpeg|png|svg|webp)$ {
expires 30d;
add_header Cache-Control "public";
}
location / {
try_files $uri $uri/ =404;
}
try_files 是 location 裡常用的一個指令:按順序嘗試後面列出的路徑,第一個存在的就用它響應,全部不存在則用最後一個(這裡是返回 404)。單頁應用常見的寫法是 try_files $uri $uri/ /index.html;,找不到靜態檔案時統一 fallback 到入口頁面,交給前端路由處理。
5. 反向代理與 upstream
把 Nginx 放在後端服務前面做反向代理,核心指令是 proxy_pass:
upstream app_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
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;
}
}
upstream 塊定義一組後端,預設按順序輪詢(round-robin),也可以加權重(server 127.0.0.1:3000 weight=3;)或換成 least_conn 等策略。轉發請求時,後端預設只能看到來自 Nginx 的連線,看不到真實用戶端資訊,所以要靠 proxy_set_header 把原始的 Host、用戶端 IP(X-Real-IP/X-Forwarded-For)和協定(X-Forwarded-Proto)透傳過去,後端應用需要讀這些頭才能拿到真實的訪問者資訊,尤其是在做訪問日誌或按協定跳轉時。
TLS 終止通常也放在反向代理這一層:憑證設定在面向公網的 server 塊(listen 443 ssl;、ssl_certificate、ssl_certificate_key),Nginx 到後端之間可以繼續用明文 HTTP,減少後端管理憑證的負擔。
6. 靜態資源、壓縮與快取
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location ~* ^/(?:_astro|assets)/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
gzip 系列指令按型別壓縮響應體,gzip_min_length 避免對本來就很小的響應做無意義的壓縮。對於檔名裡帶 hash(構建產物常見做法)的靜態資源,可以放心地給一個很長的 expires 加上 immutable:既然內容變了檔名一定會變,瀏覽器長期快取舊檔名也不會拿到過期內容。沒有 hash 的資源就不能這樣設定,否則更新後使用者可能長期看到舊版本。
7. 重定向與遺留 URL 遷移
網站改版、URL 結構調整之後,舊的描述性 slug 需要遷移到新 slug。做法是根據構建產物的 canonical URL 生成“舊路徑 → 新路徑”對映表,再由 Nginx 統一返回 301。本站的 canonical-paths.map 也處理尾部斜線、index.html、語言字尾和已淘汰的列表路徑:
map $request_uri $request_path {
~^([^?]*) $1;
}
map $uri $canonical_path {
default $request_path;
include /etc/nginx/canonical-paths.map;
}
server {
...
if ($request_path != $canonical_path) {
return 301 https://aoi.ai$canonical_path$is_args$args;
}
}
map 本身不做跳轉,只是根據請求路徑算出 canonical 路徑;真正跳轉的是後面那句 return 301。對映表由實際生成的 HTML 自動建立,因此新增頁面不需要手寫 location。$is_args$args 保留原始查詢字串,絕對的 HTTPS apex 目標則同時收斂協議、主機名和路徑,避免重定向鏈。
8. 一份完整的生產設定
把上面幾節拼起來,就是本站生產環境實際在用的設定(略去了憑證相關設定):
map $request_uri $request_path {
~^([^?]*) $1;
}
map $uri $canonical_path {
default $request_path;
include /etc/nginx/canonical-paths.map;
}
server {
listen 8000;
listen [::]:8000;
server_name _;
root /usr/share/nginx/html;
index index.html;
charset utf-8;
absolute_redirect off;
server_tokens off;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/javascript application/json application/rss+xml image/svg+xml;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
if ($request_path != $canonical_path) {
return 301 https://aoi.ai$canonical_path$is_args$args;
}
location ~* ^/(?:_astro|article-assets)/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
幾個細節值得單獨說一下:
server_tokens off;讓錯誤頁面和響應頭不再暴露 Nginx 的具體版本號,減少一點被針對性掃描的資訊面。add_header ... always;裡的always表示即使響應是錯誤頁(4xx/5xx)也要帶上這個頭,預設情況下add_header在某些錯誤響應裡會被跳過。/healthz單獨關閉了access_log,因為健康檢查通常是幾秒一次的高頻請求,全部記錄只會讓日誌被噪音淹沒。- 最後的
try_files $uri $uri/ $uri/index.html =404;是靜態站點常見寫法:先找精確檔案,再找目錄,再找目錄下的index.html,都找不到才返回 404——這是為了相容 Astro 之類靜態站點生成器輸出的about/index.html這種目錄結構。
9. 校驗與重新載入
改完設定不要直接重啟,先校驗語法:
sudo nginx -t
確認沒有報錯之後,用 reload 而不是 restart:
sudo systemctl reload nginx
# 或者
sudo nginx -s reload
reload 會讓 Nginx 平滑地用新設定啟動新的 worker 行程,等舊 worker 處理完當前連線後再退出,中間不會丟請求;restart 則會先殺掉行程再重新啟動,短時間內會有服務中斷。日常改設定、換憑證、調整超時,都應該用 reload。