NGINX 完整教學:從靜態網站到反向代理、HTTPS 與除錯
部分資料由 AI 生成。
NGINX 常放在網站最前面,負責接收瀏覽器送來的 HTTP/HTTPS 請求,再依網址、網域與路徑決定要回傳靜態檔案,或把請求轉交給 Python、Node.js、PHP 等後端服務。它也能處理 TLS 憑證、壓縮、快取、限流與負載平衡。
本文以 Ubuntu/Debian 與 NGINX Open Source 為主要示範環境。不同 Linux 發行版、套件來源與 NGINX 版本的預設路徑可能不同,修改前請先用文中的檢查指令確認實際環境。
本文最後更新於 2026 年 9 月 1 日。第一次設定網站時,可先讀「最安全的修改流程」「建立第一個靜態網站」與「設定反向代理」;遇到錯誤時,再從目錄跳到對應章節。
目錄
展開目錄
- NGINX 完整教學:從靜態網站到反向代理、HTTPS 與除錯
- 目錄
- 先看懂 NGINX 位於哪裡
- NGINX 的程序與設定概念
- 安裝前先確認環境
- 常見路徑與檔案配置
- 最安全的修改流程
- 建立第一個靜態網站
- server_name 與多站台
- location 的比對順序
- 設定反向代理
- WebSocket 反向代理
- 用 upstream 管理後端與負載平衡
- 設定 HTTPS
- 網域與網址轉址
- 靜態資源快取
- 壓縮回應
- 上傳大小與請求逾時
- 限制請求速率與連線數
- CDN 或上游代理後的真實 IP
- 基本存取控制
- 安全標頭
- 日誌與觀測
- 常用控制指令
- 常見 HTTP 狀態碼與排查方向
- 403 Forbidden
- 404 Not Found
- 502 Bad Gateway
- 504 Gateway Timeout
- 轉址循環
- 設定測試成功,但網站仍失敗
- 使用 Docker 時的差異
- PHP-FPM 基本概念
- 設定維護方式
- 上線前檢查表
- 一份可直接改寫的反向代理範本
- 延伸閱讀
先看懂 NGINX 位於哪裡
瀏覽器通常不會直接連到應用程式。公開網站常見的請求路徑如下:
NGINX 常見的工作包括:
| 工作 | 說明 |
|---|---|
| Web 伺服器 | 直接傳送 HTML、CSS、JavaScript、圖片與下載檔案 |
| 反向代理 | 把公開請求轉交給只監聽本機連接埠的應用程式 |
| TLS 終止 | 使用憑證處理 HTTPS,再把請求交給後端 |
| 負載平衡 | 把請求分配給多個後端服務 |
| 快取與壓縮 | 減少重複運算、頻寬與傳輸時間 |
| 存取控制 | 依 IP、密碼、請求速率或檔案路徑限制存取 |
| 日誌 | 記錄請求、狀態碼、耗時與錯誤原因 |
反向代理和正向代理的方向不同。反向代理代表伺服器,外部使用者不必知道後端程式的實際位址;正向代理則代表用戶端去存取外部網站。一般網站架設使用的是反向代理。
NGINX 的程序與設定概念
NGINX 啟動後,通常會有一個 master process 與數個 worker process。master process 讀取設定、管理 worker;worker 實際處理連線。重新載入設定時,NGINX 會嘗試啟動使用新設定的 worker,舊 worker 則在完成既有請求後結束。
設定檔由 directive(指令)與 context(區塊)組成。簡單指令以分號結尾,區塊則用大括號包住其他指令:
user www-data;
worker_processes auto;
events {
worker_connections 768;
}
http {
server {
listen 80;
server_name example.com;
location / {
root /var/www/example;
}
}
}
主要層級如下:
| Context | 用途 |
|---|---|
| main | 設定 worker、PID、錯誤日誌等全域行為 |
events |
連線處理方式與每個 worker 的連線數 |
http |
HTTP 共用設定、日誌格式、壓縮、快取與虛擬主機 |
server |
一組監聽位址與網域規則,也稱 server block |
location |
依 URI 路徑選擇靜態檔案、代理或其他處理方式 |
upstream |
定義一組後端伺服器,供反向代理或負載平衡使用 |
指令能放在哪一層,必須查該指令的官方文件。把 server 放進另一個 server,或把只允許出現在 http 的指令放到 location,nginx -t 會直接報錯。
安裝前先確認環境
不要先假設 NGINX 尚未安裝。先檢查執行檔、版本、編譯參數、服務狀態與實際設定檔位置:
command -v nginx
nginx -v
nginx -V
sudo systemctl status nginx --no-pager
sudo nginx -T
各指令用途:
| 指令 | 用途 |
|---|---|
nginx -v |
顯示版本 |
nginx -V |
顯示版本、編譯模組與編譯參數 |
nginx -t |
檢查設定語法,並確認引用檔案能否開啟 |
nginx -T |
檢查後輸出完整有效設定,包含 include 載入的檔案 |
systemctl status nginx |
查看 systemd 服務狀態與近期訊息 |
nginx -T 可能印出網域、內部位址與憑證路徑。貼到公開討論區前先刪除敏感資料,且不要把私鑰內容貼出。
Ubuntu/Debian
從發行版套件庫安裝:
sudo apt update
sudo apt install nginx
安裝後檢查:
nginx -v
sudo nginx -t
sudo systemctl status nginx --no-pager
如果需要特定新版本或官方套件,請依 NGINX 官方安裝文件 設定套件來源,不要混用多個來源。切換套件來源前應備份設定並確認模組相容性。
RHEL/Rocky Linux/AlmaLinux/Fedora
套件名稱通常同樣是 nginx:
sudo dnf install nginx
sudo systemctl enable --now nginx
sudo nginx -t
SELinux 啟用時,即使檔案權限看似正確,NGINX 仍可能無法讀檔或連到後端。不要用停用 SELinux 當作長期解法;應檢查安全脈絡與布林值。
Windows
NGINX 提供 Windows 版本,但官方文件把它定位為測試用途,功能與效能限制也和 Unix 版本不同。正式環境建議使用 Linux、WSL 內的測試環境,或容器。Windows 的控制指令可參考 NGINX 官方 Windows 文件。
常見路徑與檔案配置
Ubuntu/Debian 套件常見的結構:
/etc/nginx/
├── nginx.conf
├── conf.d/
│ └── *.conf
├── sites-available/
│ └── example.com
├── sites-enabled/
│ └── example.com -> ../sites-available/example.com
├── snippets/
│ └── *.conf
└── mime.types
/var/log/nginx/
├── access.log
└── error.log
sites-available 與 sites-enabled 是 Debian 系套件慣例,不是 NGINX 核心功能。是否生效,仍要看 /etc/nginx/nginx.conf 裡是否有對應的 include:
grep -n "include" /etc/nginx/nginx.conf
sudo nginx -T
常見載入方式:
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
不要在 sites-available 和 sites-enabled 各放一份會同時被載入的實體檔案,否則容易出現重複 server_name、重複監聽或修改錯檔。較清楚的做法是在 sites-available 保存實體檔案,再從 sites-enabled 建立符號連結。
最安全的修改流程
網站正在對外服務時,修改 NGINX 應遵守固定順序:
1. 找出真正生效的設定
sudo nginx -T | less
也可搜尋網域與連接埠:
sudo nginx -T 2>&1 | grep -n "example.com"
sudo nginx -T 2>&1 | grep -n "listen 443"
2. 備份要修改的檔案
sudo cp -a /etc/nginx/sites-available/example.com \
/etc/nginx/sites-available/example.com.bak-20260901-1530
備份名稱應含日期與時間,且不要覆蓋上一份備份。若設定由 Git、Ansible、Docker 映像檔或部署系統管理,應在來源端修改,避免伺服器上的手動變更被下一次部署蓋掉。
3. 編輯後先測試
sudo nginx -t
成功時會看到類似訊息:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
4. 重新載入,不必先重啟
sudo systemctl reload nginx
reload 會平順載入新設定。restart 會停止後再啟動,設定錯誤、連接埠衝突或啟動失敗時可能造成中斷。一般設定變更優先使用 reload。
5. 從不同層級驗證
curl -I http://127.0.0.1/
curl -I -H 'Host: example.com' http://127.0.0.1/
curl -I https://example.com/
sudo tail -n 100 /var/log/nginx/error.log
若同一台主機放了多個網站,直接連 127.0.0.1 可能打到預設站台。加入 Host 標頭,才能測到指定的 server_name。HTTPS 站台若要略過外部 DNS,可用:
curl --resolve example.com:443:127.0.0.1 https://example.com/ -I
nginx -t 只證明語法與引用檔案能被讀取,不會證明 DNS、憑證鏈、後端應用程式、檔案權限、防火牆或公開網路都正常。
建立第一個靜態網站
假設網站檔案放在 /var/www/example.com/public:
sudo mkdir -p /var/www/example.com/public
建立 index.html,並讓 NGINX 的執行帳號能沿路讀取目錄與檔案。不要為了省事把整個網站設成 777。靜態檔案常用目錄 755、檔案 644,實際擁有者則依部署流程決定。
建立 /etc/nginx/sites-available/example.com:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
啟用站台:
sudo ln -s /etc/nginx/sites-available/example.com \
/etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl reload nginx
若符號連結已存在,先檢查它指向哪裡,不要直接用 ln -sf 蓋掉:
readlink -f /etc/nginx/sites-enabled/example.com
root 如何對應檔案
location /images/ {
root /var/www/example.com/public;
}
請求 /images/logo.png 會對應:
/var/www/example.com/public/images/logo.png
root 會把完整 URI 接在指定目錄後面。這是最常見也最容易讀懂的寫法。
alias 和 root 的差別
location /downloads/ {
alias /srv/shared-files/;
}
請求 /downloads/manual.pdf 會對應:
/srv/shared-files/manual.pdf
alias 會以指定路徑取代符合的 location 前綴。尾端斜線不一致時很容易得到錯誤路徑。能用 root 清楚表達時優先用 root;必須把 URI 前綴映射到不同目錄時再用 alias。
try_files 的用途
location / {
try_files $uri $uri/ =404;
}
NGINX 會依序檢查實體檔案、目錄,最後回傳 404。它能避免所有不存在的路徑都誤落到首頁。
單頁應用程式(SPA)通常改成:
location / {
try_files $uri $uri/ /index.html;
}
這讓 /profile 等前端路由回到 index.html。API 路徑應另外建立 location /api/,否則 API 的 404 可能被誤回成 HTML。
server_name 與多站台
NGINX 先依監聽位址、連接埠與 Host 選擇 server,再在該 server 裡比對 location。
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
}
default_server 屬於 listen,不是 server_name。它決定找不到合適 Host 時由哪個站台接手。return 444 是 NGINX 的非標準狀態,會直接關閉連線;若需要標準 HTTP 回應,可改用 return 404;。
檢查 Host 比對:
curl -I -H 'Host: example.com' http://127.0.0.1/
curl -I -H 'Host: unknown.invalid' http://127.0.0.1/
location 的比對順序
常見形式:
location = /health { }
location ^~ /assets/ { }
location /api/ { }
location ~ \.php$ { }
location ~* \.(jpg|jpeg|png|gif)$ { }
location / { }
| 形式 | 意義 |
|---|---|
location = /path |
完全相符,優先權最高 |
location ^~ /path/ |
前綴相符,選中後不再檢查正規表示式 |
location /path/ |
一般前綴;先找最長的相符前綴 |
location ~ pattern |
區分大小寫的正規表示式 |
location ~* pattern |
不分大小寫的正規表示式 |
location / |
最後的通用前綴 |
簡化後的選擇流程:
正規表示式 location 依出現順序比對,第一個相符者勝出。一般前綴則看長度,不看書寫順序。複雜規則可用小型測試檔與 curl 逐條驗證,不要只靠目測。
設定反向代理
假設 Python 或 Node.js 應用程式只監聽 127.0.0.1:8000:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
建議讓後端只監聽 loopback 位址,避免同一服務同時從公開連接埠與 NGINX 暴露:
127.0.0.1:8000 建議,只有本機可連
0.0.0.0:8000 會監聽所有 IPv4 介面,需另行限制
確認後端是否真的在監聽:
sudo ss -ltnp
curl -I http://127.0.0.1:8000/
代理標頭
| 標頭 | 後端常見用途 |
|---|---|
Host |
保留使用者請求的網域 |
X-Real-IP |
傳遞 NGINX 看到的來源 IP |
X-Forwarded-For |
保存經過多層代理的來源 IP 鏈 |
X-Forwarded-Proto |
告知後端外部請求是 HTTP 或 HTTPS |
後端框架通常要設定可信任代理,才會採用這些標頭。若後端本身能被外部直接連線,攻擊者可自行偽造 X-Forwarded-For;不要未經限制就把它當成權限或稽核依據。
proxy_pass 尾端斜線
下列兩段行為不同:
location /api/ {
proxy_pass http://127.0.0.1:8000;
}
請求 /api/users 會把原 URI 傳給後端,後端收到 /api/users。
location /api/ {
proxy_pass http://127.0.0.1:8000/;
}
proxy_pass 位址含 URI /,相符的 /api/ 前綴會被取代,後端收到 /users。修改斜線前先確認後端路由期待哪一種 URI。
逾時與串流
長時間工作、Server-Sent Events(SSE)或逐步產生回應的服務,可能需要調整:
location /events/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 1h;
}
不要全站關閉 buffering 或把逾時無限制拉長。先確認該路徑的傳輸模式,再只調整需要的 location。
WebSocket 反向代理
WebSocket 的 Upgrade 與 Connection 是 hop-by-hop 標頭,反向代理時要明確傳遞。將 map 放在 http context:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
站台設定:
server {
listen 80;
server_name ws.example.com;
location /socket/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 60s;
}
}
若連線會長時間沒有資料,需讓應用程式定期傳送 ping,或依需求調整 proxy_read_timeout。測試時應使用真正的 WebSocket 用戶端,curl -I 不能完整證明升級後的雙向連線正常。
用 upstream 管理後端與負載平衡
upstream app_backend {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
預設會以加權 round-robin 分配請求。常見方法:
upstream app_backend {
least_conn;
server 127.0.0.1:8001 weight=2;
server 127.0.0.1:8002;
server 127.0.0.1:8003 backup;
}
| 設定 | 用途 |
|---|---|
| 預設 round-robin | 依權重輪流分配 |
least_conn |
優先交給目前連線較少的後端 |
ip_hash |
依來源 IP 盡量送到相同後端 |
weight=N |
調整後端接收請求的比例 |
backup |
主要後端不可用時才接收請求 |
max_fails、fail_timeout |
設定被動失敗判斷 |
開源版 NGINX 的基本 HTTP upstream 健康判斷多半是被動式:請求失敗後才暫時避開後端。不要把它誤認成完整的主動健康檢查系統。應用程式若保存登入狀態,優先把 session 放進共享儲存空間,而不是長期依賴黏著連線。
設定 HTTPS
公開登入、表單、API 與一般網站都應使用 HTTPS。流程分成 DNS、連接埠、防火牆、憑證與 NGINX 設定幾層;任何一層錯誤都可能讓簽發或連線失敗。
先完成 HTTP 與 DNS
申請憑證前確認:
dig +short example.com
curl -I http://example.com/
sudo ss -ltnp | grep -E ':80|:443'
網域的 A/AAAA 記錄要指向正確主機,且驗證方式所需的連接埠必須能從外部連入。若使用 CDN 代理,還要分清楚訪客到 CDN 與 CDN 到來源站的 TLS 設定。
使用 Certbot
Certbot 的安裝方式會隨作業系統與版本變動。先到 Certbot 官方指引 選擇作業系統與 NGINX,再依當前步驟安裝。
常見用法:
sudo certbot --nginx -d example.com -d www.example.com
只取得憑證、不讓 Certbot 改站台設定:
sudo certbot certonly --nginx -d example.com -d www.example.com
檢查自動更新:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
renew --dry-run 會聯絡測試環境,適合驗證更新流程,但不能取代正式憑證到期日與服務載入狀態的監控。
手動引用憑證的基本結構
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
私鑰不能放進公開 Git 儲存庫,也不要把權限改成所有人可讀。TLS 版本、cipher 與 session 設定應採用目前套件或 Certbot 產生的安全預設,再依支援的用戶端範圍調整,不要照抄多年以前的 cipher 清單。
HSTS 要延後啟用
add_header Strict-Transport-Security "max-age=31536000" always;
HSTS 會讓瀏覽器在指定期間強制使用 HTTPS。先確認所有子網域、憑證更新與 HTTP 轉址都穩定,再考慮 includeSubDomains 或提交到 preload 清單。錯誤啟用可能讓尚未支援 HTTPS 的子網域無法存取。
驗證憑證與轉址
curl -I http://example.com/
curl -I https://example.com/
openssl s_client -connect example.com:443 -servername example.com </dev/null
檢查憑證到期日:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
網域與網址轉址
把 www 統一轉到裸網域:
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
固定路徑搬移:
location = /old-page {
return 301 /new-page;
}
測試階段若不確定規則,可先用 302,確認網址、查詢字串與方法符合預期後再改成可快取的永久轉址。避免 rewrite 與多個 server 互相轉回,造成 ERR_TOO_MANY_REDIRECTS。
靜態資源快取
帶內容雜湊的資源,例如 app.8f3a1c.js,可以設定長時間瀏覽器快取:
location ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|ico|woff2)$ {
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;
}
若檔名不會隨內容改變,使用 immutable 可能讓使用者長時間拿到舊檔。HTML 通常不設同樣的長快取,因為它要負責指向新版資源。
代理快取
在 http context 定義快取區:
proxy_cache_path /var/cache/nginx/app
levels=1:2
keys_zone=app_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
站台中啟用:
location /public-api/ {
proxy_pass http://127.0.0.1:8000;
proxy_cache app_cache;
proxy_cache_valid 200 10m;
add_header X-Cache-Status $upstream_cache_status always;
}
登入後頁面、購物車、個人資料、帶 Authorization 或 Set-Cookie 的回應不能未經設計就共用快取。先定義快取鍵、略過條件與清除方式,再啟用代理快取。
壓縮回應
基本 gzip 設定通常放在 http context:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types
text/plain
text/css
application/json
application/javascript
application/xml
image/svg+xml;
JPEG、PNG、WebP、MP4 與 ZIP 等格式通常已壓縮,再用 gzip 效益有限且會增加 CPU 負擔。Brotli 並非所有 NGINX 套件都內建,先用 nginx -V 確認模組,不能只把網路上的 brotli on; 貼進設定。
上傳大小與請求逾時
限制請求本文大小:
server {
client_max_body_size 20m;
}
超過限制通常回傳 413。若應用程式、容器入口、CDN 或負載平衡器也有限制,所有層級都要一起檢查。不要把限制直接改成極大值;先依實際檔案用途設定合理上限。
常見逾時:
client_header_timeout 10s;
client_body_timeout 30s;
send_timeout 60s;
keepalive_timeout 65s;
proxy_connect_timeout 是連到後端的時間,proxy_read_timeout 是兩次讀取動作間等待後端資料的時間。504 不代表只要把數字加大就能解決;也可能是後端卡住、資料庫過慢或網路不通。
限制請求速率與連線數
在 http context 定義共享區:
limit_req_zone $binary_remote_addr zone=login_rate:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=per_ip_conn:10m;
在特定路徑套用:
location = /login {
limit_req zone=login_rate burst=5 nodelay;
limit_conn per_ip_conn 10;
proxy_pass http://127.0.0.1:8000;
}
限流鍵必須真的是使用者來源 IP。網站若在 CDN 或其他反向代理後面,NGINX 看到的 $remote_addr 可能只是上游代理。必須先安全設定 real IP,否則所有訪客可能共用同一個限額,或攻擊者能偽造來源。
先在測試環境觀察正常流量,再決定 rate 與 burst。登入、API、圖片與整頁瀏覽需要的速率不同,不適合套同一組數字。
CDN 或上游代理後的真實 IP
只有在請求確定來自可信任代理時,才能採用它提供的來源 IP 標頭。概念如下:
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
192.0.2.0/24 是文件示範網段,不能直接用於正式站台。若使用 Cloudflare,應從 Cloudflare 官方公布的 IP 範圍建立 set_real_ip_from,並安排更新。來源站也應限制只有可信任代理能連入;否則外部使用者可能繞過代理,自己送出偽造標頭。
套用後檢查 access log 的 $remote_addr、代理標頭與來源連線,確認沒有把任意外部標頭當成真實 IP。
基本存取控制
依 IP 允許或拒絕
location /admin/ {
allow 192.0.2.10;
allow 2001:db8::/32;
deny all;
proxy_pass http://127.0.0.1:8000;
}
規則依順序判斷。文件示範位址 192.0.2.0/24 與 2001:db8::/32 不可直接當成正式允許名單。
HTTP Basic Authentication
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd admin
location /private/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
不要把 .htpasswd 放在網站根目錄。Basic Authentication 的認證資料只是編碼,不是加密;必須搭配 HTTPS。
隱藏敏感檔案
location ~ /\. {
deny all;
}
location ~* \.(env|ini|log|sql|bak)$ {
deny all;
}
這類規則只是防線之一。真正的密鑰、備份與 .git 不應部署在可公開讀取的網站根目錄。加入規則後還要測試 ACME challenge、well-known 路徑與應用程式是否被誤擋。
安全標頭
可從低風險標頭開始:
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
Content-Security-Policy(CSP)必須依網站實際載入的 JavaScript、CSS、字型、圖片與外部服務設計。直接貼一份嚴格 CSP 可能讓登入、MathJax、分析工具、圖片或內嵌程式全部失效。先用 Content-Security-Policy-Report-Only 蒐集違規,再逐步收緊。
add_header 有繼承規則:子層若重新定義任一 add_header,可能不再繼承上一層同類指令。完成後要用 curl -I 檢查一般回應、錯誤頁、轉址與靜態檔案。
日誌與觀測
Ubuntu/Debian 常見日誌:
/var/log/nginx/access.log
/var/log/nginx/error.log
即時查看:
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
sudo journalctl -u nginx -f
依時間查看 systemd 日誌:
sudo journalctl -u nginx --since "30 minutes ago" --no-pager
自訂代理耗時日誌
在 http context 定義格式:
log_format upstream_timing
'$remote_addr - $host "$request" $status $body_bytes_sent '
'request_time=$request_time '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_connect_time=$upstream_connect_time '
'upstream_response_time=$upstream_response_time';
站台中使用:
access_log /var/log/nginx/app.access.log upstream_timing;
error_log /var/log/nginx/app.error.log warn;
$request_time 是 NGINX 處理整個請求的時間;$upstream_response_time 是等待後端回應的時間。兩者一起看,較容易分辨慢在用戶端傳輸、NGINX 還是後端。
日誌可能包含 IP、網址查詢字串、使用者代理與其他個人資料。設定保留期限、存取權限與輪替,且不要把密碼、token 或敏感表單資料放進 URL。
常用控制指令
| 指令 | 用途 |
|---|---|
sudo nginx -t |
測試設定語法與引用檔案 |
sudo nginx -T |
測試並輸出完整有效設定 |
sudo systemctl status nginx |
查看服務狀態 |
sudo systemctl reload nginx |
平順重新載入設定 |
sudo systemctl restart nginx |
停止後重啟服務 |
sudo systemctl enable nginx |
設定開機自動啟動 |
sudo systemctl disable nginx |
取消開機自動啟動 |
sudo journalctl -u nginx |
查看 systemd 日誌 |
sudo nginx -s reload |
直接向 NGINX master 發送 reload 訊號 |
sudo nginx -s quit |
平順停止,完成既有請求後離開 |
在 systemd 管理的主機上,日常操作優先用 systemctl,讓服務狀態與日誌維持一致。kill -9 只適合程序完全無法正常結束、且已理解後果的特殊情況。
常見 HTTP 狀態碼與排查方向
| 狀態碼 | 常見原因 | 先檢查 |
|---|---|---|
| 301/302 | 正常轉址或規則循環 | Location 標頭、HTTP/HTTPS 與 CDN 規則 |
| 400 | Host、請求格式、TLS 到錯誤連接埠 | access log、請求標頭、監聽設定 |
| 403 | 權限、deny、目錄缺少索引、SELinux |
error log、路徑權限、location 規則 |
| 404 | root/alias 對應錯誤或後端路由不存在 |
$uri 對應路徑、try_files、後端回應 |
| 413 | 請求本文超過限制 | client_max_body_size 與上游限制 |
| 429 | 限流規則觸發 | limit_req、真實 IP 與正常流量 |
| 499 | 用戶端在 NGINX 回應前斷線 | 後端耗時、用戶端逾時、網路品質 |
| 500 | NGINX、FastCGI 或應用程式內部錯誤 | error log 與應用程式 traceback |
| 502 | NGINX 無法從後端取得有效回應 | 後端是否監聽、socket 權限、協定 |
| 503 | 服務不可用、upstream 無可用成員 | upstream 狀態、維護模式、容量 |
| 504 | 等待後端回應逾時 | 後端耗時、資料庫、網路、代理逾時 |
狀態碼顯示的是回應結果,不一定指出最初原因。例如 502 可能來自後端未啟動、位址填錯、Unix socket 權限不足、容器網路不同,或後端送出無效 HTTP。
403 Forbidden
先找 NGINX error log 的同一時間:
sudo tail -n 100 /var/log/nginx/error.log
檢查完整路徑的每一層權限:
namei -l /var/www/example.com/public/index.html
sudo -u www-data test -r /var/www/example.com/public/index.html \
&& echo readable
常見原因:
- 目錄沒有可讀的索引檔,且
autoindex關閉。 location中有deny all或 IP 規則。- NGINX 執行帳號無法穿越上層目錄。
- 檔案可讀,但 SELinux 或 AppArmor 阻擋。
- CDN/WAF 回傳 403,請求尚未到來源站。
先看回應標頭與各層日誌,分清楚 403 是 CDN、NGINX 還是應用程式產生。
404 Not Found
用 nginx -T 確認真正載入的 server 與 location,再把 URI 按 root 或 alias 規則換算成檔案路徑:
sudo nginx -T 2>&1 | grep -n -A20 -B5 "server_name example.com"
namei -l /var/www/example.com/public/path/to/file
反向代理站台還要直接連後端:
curl -i http://127.0.0.1:8000/path/to/file
如果 NGINX 回 404 而後端正常,多半是 location、try_files 或 URI 改寫;如果後端直接回 404,應檢查應用程式路由。
502 Bad Gateway
依序檢查:
sudo ss -ltnp
curl -v http://127.0.0.1:8000/
sudo systemctl status my-app --no-pager
sudo journalctl -u my-app -n 100 --no-pager
sudo tail -n 100 /var/log/nginx/error.log
使用 Unix socket 時:
namei -l /run/my-app/app.sock
sudo -u www-data test -r /run/my-app/app.sock
socket 通常還需要 NGINX 執行帳號具備連線所需權限。不要用 chmod 777 掩蓋擁有者、群組或服務啟動流程的錯誤。
504 Gateway Timeout
先測後端實際耗時:
time curl -v http://127.0.0.1:8000/slow-path
再對照 access log 的 $request_time 與 $upstream_response_time。若後端本身已超時,應查資料庫查詢、外部 API、鎖定、CPU、記憶體與工作佇列。只有確認該工作本來就合理地需要較長時間,才調高特定路徑的 proxy_read_timeout。
轉址循環
常見情況是 CDN 對來源站使用 HTTP,但來源站依自身看到的 $scheme 強制轉回 HTTPS,或後端不信任 X-Forwarded-Proto,一直產生同一個轉址。
curl -IL --max-redirs 10 http://example.com/
curl -IL --max-redirs 10 https://example.com/
觀察每一步 Location。同時檢查 CDN TLS 模式、NGINX 的 HTTP 轉址與後端框架的 HTTPS 強制設定,只保留清楚且一致的責任分工。
設定測試成功,但網站仍失敗
每一層都保留測試證據。不要看到公開網址錯誤就立刻修改 NGINX,也不要看到 nginx -t 成功就假定外部網站一定正常。
使用 Docker 時的差異
最小設定:
services:
nginx:
image: nginx:stable
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./public:/usr/share/nginx/html:ro
容器中的 127.0.0.1 指向容器自己,不是主機或另一個容器。若後端服務名稱是 app:
location / {
proxy_pass http://app:8000;
}
測試容器內設定:
docker compose exec nginx nginx -t
docker compose exec nginx nginx -T
重新載入:
docker compose exec nginx nginx -s reload
若設定檔是映像檔的一部分,正式變更應重新建置與部署映像檔,避免容器重建後遺失手動修改。固定映像標籤或 digest,並在升級前閱讀版本變更。
PHP-FPM 基本概念
PHP 通常透過 FastCGI 交給 PHP-FPM,不使用 proxy_pass:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
socket 名稱會隨 PHP 版本與套件改變,先確認:
systemctl list-units 'php*-fpm.service'
find /run/php -maxdepth 1 -type s -ls
不要把使用者可上傳的目錄任意交給 PHP 執行。SCRIPT_FILENAME、try_files 與 location 比對若設定錯誤,可能造成檔案不存在、原始碼外洩或執行不該執行的檔案。優先從發行版提供的安全範本開始,再依專案調整。
設定維護方式
大型設定可拆成數個用途明確的 snippet:
/etc/nginx/snippets/
├── proxy-headers.conf
├── ssl-example.com.conf
└── security-headers.conf
引用:
location / {
include /etc/nginx/snippets/proxy-headers.conf;
proxy_pass http://127.0.0.1:8000;
}
拆檔不是越多越好。同一指令若在 http、server、location 與 snippet 重複出現,繼承關係會變得難以追蹤。用 nginx -T 查看最後載入結果,並把設定納入版本控制;私鑰、密碼檔與含秘密的設定另行管理。
設定差異與自動測試
修改前後可保存完整設定:
sudo nginx -T > /tmp/nginx-before.txt 2>&1
# 編輯並測試設定
sudo nginx -T > /tmp/nginx-after.txt 2>&1
diff -u /tmp/nginx-before.txt /tmp/nginx-after.txt
這些檔案可能含內部資訊,使用完應妥善清除。持續整合可在容器內執行 nginx -t,再以測試請求檢查轉址、標頭、靜態檔案與代理路由。
上線前檢查表
設定與服務
- 已確認修改的是實際載入的檔案。
- 已保留可辨識時間的備份或 Git 提交。
-
sudo nginx -t成功。 - 使用
reload後,systemctl status nginx仍為 active。 -
ss -ltnp顯示預期的 IPv4/IPv6 監聽位址。
網站與後端
- 伺服器本機帶正確 Host 測試成功。
- 公開 HTTP 會轉到正確 HTTPS 網址,沒有循環。
- 首頁、靜態檔案、API、404 與上傳都已測試。
- 後端只暴露在預期的介面與連接埠。
- WebSocket、SSE 或大型下載已用真正用戶端測試。
TLS 與安全
- 憑證網域、完整鏈與到期日正確。
- 自動更新已用 dry run 驗證,且有到期監控。
- 私鑰與密碼檔不在網站根目錄或公開儲存庫。
- CDN 後方的真實 IP 只信任已知代理來源。
- 限流、上傳大小與安全標頭沒有擋到正常功能。
- CSP 已用實際頁面及瀏覽器開發者工具測試。
觀測與回復
- access log、error log 與 systemd 日誌都能讀取。
- 日誌有輪替與合理保留期限。
- 已知道發生錯誤時要回復哪個檔案或版本。
- 回復後仍會再次執行
nginx -t、reload 與 HTTP 驗證。
一份可直接改寫的反向代理範本
下列範本適合單一後端應用程式。使用前請替換網域、連接埠、憑證路徑與需求相關設定:
upstream example_app {
server 127.0.0.1:8000;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://example.com$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
client_max_body_size 20m;
location /static/ {
alias /srv/example-app/static/;
expires 7d;
}
location / {
proxy_pass http://example_app;
proxy_http_version 1.1;
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;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
這份範本不包含 WebSocket、CDN real IP、代理快取、CSP 或特定框架設定。只加入網站真正需要、而且已測試的功能。
延伸閱讀
- NGINX Beginner’s Guide:程序控制、設定結構、靜態檔案與基本代理。
- NGINX 如何處理請求:
server_name與location選擇。 - NGINX HTTP Proxy Module:
proxy_pass、標頭、buffering 與逾時的完整指令說明。 - NGINX Reverse Proxy 指南:反向代理的官方管理指南。
- NGINX WebSocket Proxying:WebSocket 升級所需設定。
- NGINX HTTP Load Balancing:upstream 與負載平衡方法。
- NGINX SSL Module:HTTPS 與 TLS 指令參考。
- Certbot 官方文件:憑證申請、安裝與更新。
正式修改前,先保留現有設定,確認 nginx -T 的實際載入結果。每次變更都先通過 nginx -t,再 reload,最後從來源站與公開網址分層驗證。