Skip to main content

Nginx Header 筆記(反向代理 / 安全性 / 除錯)

這份筆記整理 Nginx 在「設定與轉送 HTTP Header」時最常遇到的情境:反向代理必備的轉發 Header、安全性 Header(瀏覽器端保護)、以及常見問題與檢查方式。

反向代理必備:把正確的 Client 資訊傳給上游

當 Nginx 站在最前面做 Reverse Proxy 時,上游(Node/Nuxt、Python、Go…)看到的來源 IP、Host、scheme 會變成 Nginx 自己。通常你需要在 location 裡補齊「轉發 Header」。

常見建議設定:

location / {
proxy_pass http://nodenuxt;

# 讓上游知道原本的 Host 與協定
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;

# 讓上游知道原本的來源 IP 與 chain
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 若上游需要辨識這段請求是從 Nginx 轉發而來
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}

WebSocket / SSE 需要的 Header(常見踩坑)

若你的上游有 WebSocket,必須允許升級連線:

location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}

SSE(Server-Sent Events)或長連線,常見還會加:

proxy_buffering off;

安全性 Header:讓瀏覽器幫你防禦

以下是「前端資源由瀏覽器載入」才有意義的 Header。通常加在 server 或特定 location,並建議加上 always 讓錯誤狀態碼也會帶出。

HSTS(只在 HTTPS 下使用)

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  • 注意:HSTS 只在你確定全站都能 HTTPS 時再開,否則可能把自己鎖死(瀏覽器會記住)。

點擊劫持防護(frame 限制)

add_header X-Frame-Options "SAMEORIGIN" always;

MIME sniffing 防護

add_header X-Content-Type-Options "nosniff" always;

Referrer Policy

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Permissions Policy(取代舊的 Feature-Policy)

add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

CSP(Content-Security-Policy)

CSP 很強也很容易弄壞網站。建議先用 Content-Security-Policy-Report-Only 觀察再正式上線。

# 先用 Report-Only,確認不會擋到必要資源
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https:;" always;

CORS:跨網域存取(API 常見)

如果你的 API 需要被不同網域的前端呼叫,CORS Header 必須由「API 回應方」送出。簡化版本(示意):

location /api/ {
# 預檢請求(preflight)
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization,Content-Type" always;
add_header Access-Control-Allow-Credentials "true" always;
return 204;
}

add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials "true" always;

proxy_pass http://backend;
}
  • 注意Access-Control-Allow-Origin 若搭配 Allow-Credentials: true,就不能用 *

與 Basic Auth(.htpasswd)搭配時的注意事項

你 repo 目前有用 auth_basic 的筆記(05-set_nginx_login.md)。如果是 API + Basic Auth,瀏覽器會跳登入框;若是程式呼叫(curl/SDK),要記得帶 Authorization: Basic ...

典型設定(放在要保護的 location):

auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;

除錯與驗證(確認 Header 有沒有真的送出/被轉發)

在本機看回應 Header(Client 端看到的)

curl -I http://your-domain.example

檢查 Nginx 設定與重載

nginx -t
nginx -s reload

常見問題清單

  • 上游拿不到真實 IP
    • 檢查是否有設定 X-Forwarded-For / X-Real-IP
    • 若還有上一層 LB/CDN,要再搭配 real_ip_header / set_real_ip_from(依你的架構調整)
  • HTTPS → 上游判斷成 HTTP
    • 檢查是否有 proxy_set_header X-Forwarded-Proto $scheme
  • CORS 明明設了但還是被擋
    • 確認 OPTIONS preflight 有回 204/200 且 header 齊全
    • 確認沒有同時回 Access-Control-Allow-Origin: * + Allow-Credentials: true