日B视频 亚洲,啪啪啪网站一区二区,91色情精品久久,日日噜狠狠色综合久,超碰人妻少妇97在线,999青青视频,亚洲一区二卡,让本一区二区视频,日韩网站推荐

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Nginx典型配置錯(cuò)誤復(fù)盤與優(yōu)化

馬哥Linux運(yùn)維 ? 來源:馬哥Linux運(yùn)維 ? 2026-05-12 09:40 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

問題背景

Nginx 是互聯(lián)網(wǎng)生產(chǎn)環(huán)境中使用最廣泛的反向代理和 Web 服務(wù)器之一。不管是做靜態(tài)資源服務(wù)、API 網(wǎng)關(guān),還是負(fù)載均衡器,Nginx 幾乎是標(biāo)準(zhǔn)配置。很多運(yùn)維工程師日常和它打交道,但真正能把配置細(xì)節(jié)說清楚的人不多——不是因?yàn)?Nginx 復(fù)雜,而是因?yàn)樗暮芏嘈袨椴环现庇X,配置項(xiàng)之間的相互作用容易被忽略。

實(shí)際生產(chǎn)環(huán)境里,大量線上故障的直接原因就是 Nginx 配置不當(dāng):有人把請求代理到了已經(jīng)宕機(jī)的 upstream 導(dǎo)致 502,有人因?yàn)?location 匹配優(yōu)先級(jí)問題導(dǎo)致靜態(tài)資源 404,有人文件上傳一直報(bào) 413 查了半天是 client_max_body_size 沒配,有人升級(jí) TLS 證書后瀏覽器報(bào)不安全是因?yàn)橹虚g證書沒一起部署。每一個(gè)坑都是真實(shí)踩出來的。

本文從一線運(yùn)維工程師的視角,系統(tǒng)梳理 Nginx 配置中最容易出問題的十個(gè)場景。每個(gè)坑都按照「現(xiàn)象描述 → 根因分析 → 正確配置 → 驗(yàn)證方法 → 風(fēng)險(xiǎn)提醒」的閉環(huán)結(jié)構(gòu)來講,讓你不僅知道怎么修,還知道為什么這樣修,以及修完之后怎么確認(rèn)生效。

適用場景

日常運(yùn)維:接手新服務(wù)器、變更配置前檢查、線上故障排查

上線變更:每次 Nginx 配置變更后的驗(yàn)證

面試準(zhǔn)備:理解 Nginx 核心配置原理

性能優(yōu)化:定位 Nginx 導(dǎo)致的響應(yīng)慢或資源消耗異常

通用排查思路

在逐個(gè)拆解具體踩坑點(diǎn)之前,先建立一個(gè)通用的排查框架。每次遇到 Nginx 配置問題,按這個(gè)順序走一遍,能排除大部分問題。

第一步:驗(yàn)證配置語法

Nginx 提供了一個(gè)內(nèi)置的配置檢查命令,任何修改配置之后、上線之前,必須先跑這一條:

nginx -t
# 典型輸出:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

如果輸出報(bào)錯(cuò),它會(huì)指出具體文件和行號(hào)。常見錯(cuò)誤比如拼寫錯(cuò)誤、分號(hào)缺失、括號(hào)不匹配等。

但要注意:-t只檢查語法和基本結(jié)構(gòu),不檢查邏輯錯(cuò)誤。比如你把 proxy_pass 指向了一個(gè)不存在的 upstream,-t依然會(huì)通過,但實(shí)際跑起來會(huì) 502。所以-t通過只是必要條件,不是充分條件。

第二步:查看 error_log

error_log 是排查 Nginx 問題的第一手資料。很多工程師配置了日志卻從來不看,等到出問題了才想起來去翻。

# 查看 error_log 位置(默認(rèn)在 nginx.conf 里配置)
grep -r"error_log"/etc/nginx/

# 實(shí)時(shí)跟蹤最新錯(cuò)誤
tail -f /var/log/nginx/error.log

# 按時(shí)間過濾最近 5 分鐘的日志
find /var/log/nginx/ -name"error.log"-mmin -5 | xargs tail -n 50

error_log 的級(jí)別可以在配置里指定,常見的有 debug、info、notice、warn、error、crit。建議生產(chǎn)環(huán)境用 warn 或 error,太細(xì)的級(jí)別會(huì)產(chǎn)生大量 IO 開銷。

第三步:檢查進(jìn)程和連接狀態(tài)

# 看 Nginx master 和 worker 進(jìn)程
ps aux | grep nginx

# 看 Nginx 監(jiān)聽的端口
ss -tlnp | grep nginx

# 看當(dāng)前連接數(shù)狀態(tài)
netstat -an | awk'/:80s/ {s[$NF]++} END {for(k in s) print k, s[k]}'
# 或用 ss(更現(xiàn)代)
ss -s

# 看每個(gè) worker 的連接數(shù)(看負(fù)載是否均衡)
ps -o pid,ppid,comm,%cpu,%mem --no-headers -e | grep nginx

如果 worker 進(jìn)程 CPU 或內(nèi)存異常高,往往意味著配置里有性能問題,比如 regex location 匹配或過于頻繁的日志寫操作。

第四步:reload 而非 restart

Nginx 支持不停機(jī) reload 配置,這應(yīng)該是線上變更的標(biāo)準(zhǔn)操作:

nginx -s reload
# 或者向 master 進(jìn)程發(fā)信號(hào)
kill-HUP $(cat /var/run/nginx.pid)

reload 的邏輯是:master 進(jìn)程加載新配置,啟動(dòng)新的 worker 進(jìn)程處理新請求,舊的 worker 優(yōu)雅退出(處理完現(xiàn)有請求后自動(dòng)關(guān)閉)。這個(gè)過程不會(huì)中斷現(xiàn)有連接。

但要注意:有些配置變更不支持 reload,必須 restart,比如綁定到新端口、修改 SSL 證書路徑等。這類操作需要提前申請維護(hù)窗口。

第五步:建立配置變更管理機(jī)制

生產(chǎn)環(huán)境的 Nginx 配置變更是高風(fēng)險(xiǎn)操作,建議遵循以下原則:

所有配置變更走代碼倉庫(Git),禁止直接在生產(chǎn)環(huán)境手工改

變更前先在測試環(huán)境驗(yàn)證nginx -t通過

變更時(shí)先 push 配置文件,再用 ansible/salt 或者直接 cp 部署

部署后立即nginx -t && nginx -s reload,不要離開,等觀察幾分鐘確認(rèn)正常再離開

準(zhǔn)備好回滾腳本,保留上一版配置文件的備份

踩坑點(diǎn)一:location 匹配優(yōu)先級(jí)混亂

現(xiàn)象

用戶訪問/api/users返回 404,或者請求本該走/api卻走到了/的處理邏輯,導(dǎo)致返回了 HTML 頁面而不是 JSON。這種問題在團(tuán)隊(duì)多人維護(hù) Nginx 配置時(shí)特別常見,某人在 http 段新增了一個(gè) catch-all location,導(dǎo)致原有的精細(xì)化匹配全部失效。

根因

Nginx 的 location 匹配規(guī)則是按優(yōu)先級(jí)遞進(jìn)匹配的,不是按配置順序,很多工程師以為「寫在后面的 location 覆蓋前面的」,這是一個(gè)根本性誤解。Nginx location 匹配優(yōu)先級(jí)從高到低如下:

location = /path—— 精確匹配,優(yōu)先級(jí)最高

location ^~ /path—— 前綴匹配,找到最長匹配后不再做正則匹配

location ~ /path或location ~* /path—— 正則匹配(~區(qū)分大小寫,~*不區(qū)分大小寫),按配置順序匹配,第一個(gè)命中的就停止

location /path—— 普通前綴匹配,最長匹配原則

典型錯(cuò)誤配置:

server {
  listen 80;

  location / {
    root /usr/share/nginx/html;
    index index.html;
  }

  location /api {
    proxy_pass http://127.0.0.1:8080;
  }

  # 下面的 catch-all 會(huì)導(dǎo)致 /api 也走這個(gè)分支,因?yàn)闆]有加 ^~
  location ~* .php$ {
    proxy_pass http://127.0.0.1:9000;
  }
}

正確配置

根據(jù)實(shí)際需求選擇正確的匹配類型:

server {
  listen 80;

  # 精確匹配,首頁
  location = / {
    root /usr/share/nginx/html;
    index index.html;
  }

  # 前綴匹配,不允許正則覆蓋,用于靜態(tài)資源目錄
  location ^~ /static/ {
    root /data/www;
    expires 30d;
    add_header Cache-Control "public, immutable";
  }

  # 前綴匹配,用于 API 代理
  location /api/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }

  # 正則匹配,按順序排在普通前綴匹配之后
  location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
    root /data/www;
    expires 7d;
    access_log off;
  }

  # 默認(rèn) catch-all
  location / {
    root /usr/share/nginx/html;
    index index.html;
  }
}

關(guān)鍵原則:正則 location(~或~*)會(huì)按配置文件中的順序匹配,一旦匹配就停止。普通前綴 location(/path)采用最長前綴匹配原則,即選擇匹配路徑最長的那個(gè)。如果某個(gè)前綴 location 不想被后續(xù)的正則匹配覆蓋,需要加^~修飾符。

驗(yàn)證方法

用curl模擬請求,觀察返回的響應(yīng)頭和狀態(tài)碼:

# 測試精確匹配
curl -I http://localhost/
# 期望:返回 index.html,狀態(tài)碼 200

# 測試 /api 路徑
curl -I http://localhost/api/users
# 期望:代理到 8080,返回 API 響應(yīng),不是 404

# 測試靜態(tài)資源(確認(rèn)走緩存路徑)
curl -I http://localhost/static/logo.png
# 期望:返回 200,且有 Cache-Control 頭

# 用 -v 查看詳細(xì)處理過程(需要開啟 rewrite log)
curl -v http://localhost/api/test2>&1 | head -30

如果配置了rewrite_log on和notice級(jí)別的 error_log,可以在 error.log 中看到詳細(xì)的 location 匹配過程:

error_log /var/log/nginx/error.log notice;
rewrite_log on;

風(fēng)險(xiǎn)提醒

修改 location 匹配規(guī)則是高風(fēng)險(xiǎn)操作,因?yàn)榫€上 API 路徑、靜態(tài)資源路徑可能在代碼層有依賴。變更前需要:

確認(rèn)所有請求路徑的用途

在測試環(huán)境全量回歸

保留舊配置文件備份

變更后密切監(jiān)控 404 和 502 錯(cuò)誤率

踩坑點(diǎn)二:proxy_pass 末尾帶不帶 / 的區(qū)別

現(xiàn)象

配置了proxy_pass http://127.0.0.1:8080;和proxy_pass http://127.0.0.1:8080/;,表面看起來一樣,但實(shí)際代理出去的請求路徑完全不同。

具體來說:如果原始請求是GET /api/users HTTP/1.1,訪問目標(biāo)是http://your-domain.com/api/users:

proxy_pass http://127.0.0.1:8080;(無尾部斜杠):代理后請求變成GET /api/users HTTP/1.1(完整路徑保留)

proxy_pass http://127.0.0.1:8080/;(有尾部斜杠):代理后請求變成GET /users HTTP/1.1(/api 部分被替換掉了)

這個(gè)差異會(huì)導(dǎo)致后端收到完全不同的路徑,引發(fā) 404 或業(yè)務(wù)邏輯錯(cuò)誤。

根因

這是 Nginx 中最反直覺的配置之一。Nginx 在處理 proxy_pass 時(shí),會(huì)根據(jù)是否指定了 URI(路徑部分)來決定是否做路徑替換:

proxy_pass http://127.0.0.1:8080;—— 沒有指定 URI,Nginx 將原始請求的完整路徑傳遞給 upstream

proxy_pass http://127.0.0.1:8080/;—— 指定了 URI(根路徑),Nginx 會(huì)把匹配 location 時(shí)使用的前綴部分從請求路徑中移除,剩下的部分拼接到新的 URI 上

用具體例子說明:

# 場景:原始請求 /api/users -> proxy_pass http://127.0.0.1:8080/

# 寫法一:不帶 /
location /api {
  proxy_pass http://127.0.0.1:8080;
  # 代理后:GET /api/users -> upstream 收到 /api/users
}

# 寫法二:帶 /
location /api {
  proxy_pass http://127.0.0.1:8080/;
  # 代理后:GET /api/users -> upstream 收到 /users
}

正確配置

根據(jù)業(yè)務(wù)需求選擇正確的寫法,并配合rewrite或proxy_redirect做路徑調(diào)整:

# 場景一:后端路徑與前端路徑一致,不需要替換
location /api {
  proxy_pass http://127.0.0.1:8080;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
}

# 場景二:后端路徑有前綴,且 location 使用了精確前綴匹配,需要替換
location /api/ {
  proxy_pass http://127.0.0.1:8080/;
  # /api/users -> /users
  # /api/login -> /login
}

# 場景三:需要保留部分路徑,或路徑映射關(guān)系復(fù)雜
location /app/v1/ {
  rewrite ^/app/v1/(.*) /$1 break;
  proxy_pass http://127.0.0.1:8080;
  # /app/v1/users -> /users
  # /app/v1/orders -> /orders
}

# 場景四:后端要求 Host 頭為特定值
location / {
  proxy_pass http://127.0.0.1:8080;
  proxy_set_header Host "backend.example.com";
}

驗(yàn)證方法

在后端服務(wù)上開啟請求日志,觀察收到的請求路徑:

# 在 upstream 服務(wù)的 access log 中查看
# 如果是 Node.js/Express:
console.log(req.method, req.url);

# 如果是 Python/Flask:
print(request.method, request.path, request.url)

# 用 curl 本地測試(同時(shí)在前端和后端抓包)
curl -v http://localhost/api/users 2>&1
# 看 Host 頭、X-Real-IP 頭、以及實(shí)際的 URL 是什么

一個(gè)完整的調(diào)試流程:

# 1. 檢查 proxy_pass 寫法
grep -n"proxy_pass"/etc/nginx/conf.d/*.conf

# 2. 開啟調(diào)試日志查看 rewrite 過程
tail -f /var/log/nginx/error.log

# 3. 用 nc/strace 在 upstream 端口抓包
# nc -l 8080 # 監(jiān)聽本地 8080,看收到的請求行

# 4. 確認(rèn) upstream 服務(wù)日志中的路徑
# 這是最直接的方式——upstream 日志里的 path 是什么,就是 Nginx 實(shí)際傳過去的路徑

風(fēng)險(xiǎn)提醒

修改 proxy_pass 的 URI 寫法會(huì)影響所有匹配該 location 的請求路徑。如果后端有多個(gè)微服務(wù)共用一個(gè) upstream 塊,尤其要注意。建議:

先在測試環(huán)境用完整請求路徑做回歸

確認(rèn)后端 API 是否做了路徑校驗(yàn)(比如 Spring Cloud Gateway 會(huì)嚴(yán)格校驗(yàn)路由前綴)

配合監(jiān)控,觀察上線后 404 錯(cuò)誤率變化

踩坑點(diǎn)三:try_files 使用不當(dāng)導(dǎo)致死循環(huán)

現(xiàn)象

訪問某些 URL 時(shí)瀏覽器報(bào) "Too many redirects"(重定向次數(shù)過多),或者直接返回 500 Internal Server Error。用戶在瀏覽器里看到的是一片空白或者錯(cuò)誤頁面。

根因

try_files是 Nginx 用來檢查文件是否存在的重要指令,但它和alias、rewrite、rewrite ^/(.*) $1 last等指令組合時(shí),行為往往出乎意料。

最常見的死循環(huán)配置:

location / {
  root /data/www;
  try_files $uri $uri/ /index.html;
}

這個(gè)配置的意圖是:先嘗試找對應(yīng)的文件,再找同名目錄下的 index.html,最后 fallback 到根目錄的 index.html??雌饋頉]問題。

但如果同時(shí)在另一個(gè) location 里用rewrite把請求重定向回去:

location / {
  try_files $uri $uri/ /index.html;
}

location = /index.html {
  root /data/www;
  rewrite ^ / permanent; # 這行導(dǎo)致 /index.html 永久重定向到 /,死循環(huán)
}

另一種常見錯(cuò)誤是 try_files 和 alias 混用時(shí)的路徑問題:

location /static/ {
  alias /data/static/;
  try_files $uri /static/404.html;
  # 當(dāng)文件不存在時(shí),try_files 會(huì)追加 /static/ 前綴去找 /static/404.html
  # 但 alias 指令已經(jīng)映射了路徑,導(dǎo)致路徑拼接混亂
}

正確配置

try_files 的正確用法,關(guān)鍵是理解它按順序嘗試每個(gè)參數(shù),最后一個(gè)參數(shù)是 fallback URI:

# 基礎(chǔ)用法:先找文件,再找目錄 index,再 fallback
location / {
  root /data/www;
  index index.html index.htm;
  try_files $uri $uri/ /fallback.html;
}

# PHP FastCGI 場景:找文件 -> 找目錄 -> 傳給 PHP
location / {
  try_files $uri $uri/ /index.php?$query_string;
}

location ~ .php$ {
  fastcgi_pass 127.0.0.1:9000;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  include fastcgi_params;
}

# 帶命名的 fallback
location / {
  try_files $uri @backend;
}

location @backend {
  proxy_pass http://127.0.0.1:8080;
}

與 alias 配合時(shí)需要注意路徑不要重疊:

# alias 會(huì)改變 root 的映射關(guān)系,try_files 的 $uri 是基于 alias 后的路徑
location /static/ {
  alias /data/static/;
  try_files $uri =404;
  # 不要寫成 try_files $uri /static/404.html,
  # 因?yàn)?/static/ 這個(gè)前綴會(huì)被 alias 再次處理
}

驗(yàn)證方法

用 curl 模擬各種文件存在/不存在的場景:

# 測試文件存在的情況
curl -I http://localhost/static/exists.png
# 期望:200 OK,有 Content-Type 頭

# 測試文件不存在的情況(看 fallback 是否生效)
curl -I http://localhost/nonexistent/path
# 期望:返回 fallback 的響應(yīng),狀態(tài)碼可能是 200 或 302

# 測試重定向循環(huán)(如果配置有誤,會(huì)在 curl 輸出中看到循環(huán))
curl -v http://localhost/ 2>&1 | grep -E"(< HTTP|< Location)"
# 如果看到連續(xù)多個(gè) 302 -> / 的重定向,就是死循環(huán)了

# 在錯(cuò)誤日志中搜索 redirect 循環(huán)
grep -i"redirect"/var/log/nginx/error.log | tail -20

風(fēng)險(xiǎn)提醒

try_files 的死循環(huán)問題在上線初期可能不會(huì)觸發(fā)——只有當(dāng)某些特定文件不存在、且 fallback 路徑又依賴于被 try_files 檢查的路徑時(shí)才會(huì)暴露。建議在上線前用不存在的路徑做一次全量測試:

# 批量測試不存在的路徑是否都有合理的 fallback
forpathin/api/noexist /static/noexist/file.js /images/noexist.png;do
  status=$(curl -s -o /dev/null -w"%{http_code}"http://localhost${path})
 echo"${path}->${status}"
done
# 期望:所有路徑都返回非 500、非 301 循環(huán)的有效響應(yīng)

踩坑點(diǎn)四:upstream keepalive 配置不足

現(xiàn)象

業(yè)務(wù)高峰期大量用戶反映接口響應(yīng)慢,甚至出現(xiàn) 502 Bad Gateway。但后端 Java/Python 服務(wù)的 CPU 和內(nèi)存使用率都不高,數(shù)據(jù)庫連接池也沒滿。用netstat查看網(wǎng)絡(luò)連接狀態(tài),發(fā)現(xiàn)服務(wù)器有大量 TIME_WAIT 連接:

netstat -an | awk'/:80s/ {print $NF}'| sort | uniq -c | sort -rn
# 輸出中 TIME_WAIT 數(shù)量成千上萬

同時(shí)后端 upstream 服務(wù)報(bào)錯(cuò)日志里出現(xiàn)了類似 "too many connections" 或 "connection refused" 的信息。

根因

這是 Nginx 與 upstream 之間使用短連接導(dǎo)致的問題。在沒有配置 keepalive 的情況下,Nginx 每轉(zhuǎn)發(fā)一個(gè)請求都會(huì)新建一個(gè) TCP 連接到 upstream,請求結(jié)束后連接立即關(guān)閉。這會(huì)產(chǎn)生兩個(gè)問題:

TIME_WAIT 堆積:大量短連接關(guān)閉后,端口會(huì)進(jìn)入 TIME_WAIT 狀態(tài)(默認(rèn)持續(xù) 60 秒),消耗文件描述符和端口號(hào)。在高并發(fā)場景下,如果 upstream 端口復(fù)用太快,可能出現(xiàn)端口耗盡。

后端連接數(shù)暴漲:Nginx worker 進(jìn)程數(shù) × 每個(gè) worker 的活躍請求數(shù) = 實(shí)際需要的 upstream 連接數(shù)。如果每個(gè)請求都新建連接,upstream 服務(wù)的連接池會(huì)很快耗盡。

典型錯(cuò)誤配置:

upstream backend {
  server 127.0.0.1:8080;
  # 沒有 keepalive 配置,每個(gè)請求都是新建連接
}

server {
  location / {
    proxy_pass http://backend;
    # 沒有配置 proxy_http_version 1.1,也沒有 proxy_set_header Connection ""
  }
}

正確配置

Nginx upstream keepalive 配置包含三個(gè)關(guān)鍵部分:

upstream backend {
  server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
  # weight: 權(quán)重
  # max_fails: 失敗多少次后認(rèn)為該 server 不可用
  # fail_timeout: 失敗后多少秒內(nèi)不再向該 server 發(fā)請求

  # 關(guān)鍵:開啟 keepalive 連接池
  # keepalive 連接數(shù)建議設(shè)置為 worker_connections 的 10%~20%
  keepalive 32;
  keepalive_requests 1000;
  keepalive_timeout 60s;
}

server {
  listen 80;
  # 必須使用 HTTP/1.1 才能支持 keepalive
  proxy_http_version 1.1;

  location / {
    proxy_pass http://backend;
    # 關(guān)鍵:清除 Connection 頭,讓連接保持 alive
    proxy_set_header Connection "";
    # 其他常用頭
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # 超時(shí)配置
    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
  }
}

keepalive 32表示 Nginx 會(huì)為這個(gè) upstream 保持 32 個(gè)空閑長連接。當(dāng)請求進(jìn)來時(shí),Nginx 優(yōu)先使用空閑連接,只有當(dāng)空閑連接不夠用時(shí)才新建連接。如果配置了keepalive 0或不配置 keepalive,則每個(gè)請求都走新建的短連接。

驗(yàn)證方法

查看 upstream 連接狀態(tài),確認(rèn)連接被復(fù)用:

# 方法一:在 upstream 服務(wù)端查看連接數(shù)(如果 upstream 是 Node.js/Java 等,可以查看其連接池狀態(tài))
# 以 Java Spring Boot 為例,看日志中 HikariCP 的連接池狀態(tài)

# 方法二:在 Nginx 端查看連接狀態(tài)
ss -tn | grep :8080 | awk'{print $4}'| sort | uniq -c
# keepalive 正常時(shí),8080 端口的連接數(shù)會(huì)比較穩(wěn)定,不會(huì)隨 QPS 線性增長

# 方法三:查看 error_log 中是否有 upstream 報(bào)錯(cuò)
grep -i"upstream|connection"/var/log/nginx/error.log | tail -50

# 方法四:對比修改前后的 TIME_WAIT 數(shù)量
# 修改前:大量 TIME_WAIT
netstat -an | grep TIME_WAIT | wc -l
# 修改后:TIME_WAIT 數(shù)量大幅下降

一個(gè)簡單的壓測對比(用 ab 或 wrk):

# 用 ab 壓測,觀察連接建立情況
ab -n 1000 -c 100 http://localhost/api/test

# 如果沒有 ab
wrk -t10 -c100 -d30s http://localhost/api/test

# 觀察結(jié)果中的 Time taken for tests、Requests per second
# keepalive 生效后,QPS 應(yīng)該明顯提升,延遲明顯下降

風(fēng)險(xiǎn)提醒

keepalive 配置如果設(shè)置過大,會(huì)占用過多 Nginx 內(nèi)存,因?yàn)槊總€(gè) keepalive 連接都會(huì)占用一個(gè) socket 緩存。一般建議 keepalive 數(shù)量設(shè)為 worker_connections 的 10%~20%。另外,如果 upstream 服務(wù)不支持 HTTP/1.1(比如一些老的 RPC 服務(wù)),keepalive 不會(huì)生效。

踩坑點(diǎn)五:client_max_body_size 未設(shè)置或設(shè)置過小

現(xiàn)象

用戶上傳文件時(shí),Nginx 直接返回 413 Request Entity Too Large,但后端服務(wù)的文件上傳限制其實(shí)很大。運(yùn)維工程師去查后端配置,發(fā)現(xiàn)后端沒有做任何限制,困惑不已。

這是 Nginx 默認(rèn)的請求體大小限制在作祟:client_max_body_size 默認(rèn)值是 1MB。任何超過這個(gè)大小的請求,Nginx 會(huì)在讀取請求體的階段就直接拒絕,根本不會(huì)向后端轉(zhuǎn)發(fā)。

根因

Nginx 在接收請求體的階段就會(huì)檢查大小限制,如果超過 client_max_body_size,直接返回 413,不會(huì)到 proxy_pass 或 fastcgi_pass 那一層。這個(gè)限制是在 Nginx 層面硬攔截的。

常見錯(cuò)誤場景:

根本沒有配置 client_max_body_size,用的是默認(rèn)值 1MB,上傳圖片或文檔立刻超限

配置了 client_max_body_size 但位置不對——放在了 http 段而不是 server 段或 location 段

配置了但值設(shè)置得過小,沒有預(yù)估業(yè)務(wù)增長

正確配置

根據(jù)業(yè)務(wù)需求設(shè)置合理的請求體大小限制:

http {
  # 全局默認(rèn)限制,可以設(shè)置得相對保守
  client_max_body_size 10m;

  server {
    listen 80;
    server_name example.com;

    # 對特定業(yè)務(wù)設(shè)置更大的限制
    location /upload/ {
      # 文件上傳業(yè)務(wù),需要較大限制
      client_max_body_size 100m;
      # 同時(shí)調(diào)整超時(shí),因?yàn)榇笪募蟼骱臅r(shí)長
      proxy_read_timeout 300s;
      proxy_send_timeout 300s;
      proxy_connect_timeout 75s;
      proxy_pass http://upload-backend;
    }

    location /api/ {
      # 普通 API 請求,1m 就夠了
      client_max_body_size 1m;
      proxy_pass http://api-backend;
    }

    # 也可以在 error_page 中自定義 413 錯(cuò)誤頁面
    error_page 413 = /413.html;
    location = /413.html {
      root /data/www/errors;
      internal;
    }
  }
}

驗(yàn)證方法

用 dd 生成指定大小的測試文件,然后上傳:

# 生成一個(gè) 2MB 的測試文件
ddif=/dev/zero of=/tmp/test_2mb.bin bs=1M count=2

# 生成一個(gè) 15MB 的測試文件
ddif=/dev/zero of=/tmp/test_15mb.bin bs=1M count=15

# 測試上傳 2MB 文件(應(yīng)該成功)
curl -X POST -F"file=@/tmp/test_2mb.bin"http://localhost/upload/ -w"
HTTP Status: %{http_code}
"

# 測試上傳 15MB 文件(如果限制是 10m,應(yīng)該返回 413)
curl -X POST -F"file=@/tmp/test_15mb.bin"http://localhost/upload/ -w"
HTTP Status: %{http_code}
"

# 觀察 error_log 中的 413 記錄
grep"client intended to send too large body"/var/log/nginx/error.log | tail -10

風(fēng)險(xiǎn)提醒

設(shè)置過大的 client_max_body_size 會(huì)帶來安全風(fēng)險(xiǎn)——攻擊者可能通過上傳超大文件來耗盡服務(wù)器磁盤或內(nèi)存。最佳實(shí)踐是:

根據(jù)實(shí)際業(yè)務(wù)需求設(shè)置,寧可保守也不要太大

配合后端做二次校驗(yàn),前端限制不可信

上傳目錄單獨(dú)掛載,限制磁盤配額

配合 rate limiting 防止頻繁上傳

# 配合 limit_rate 限制上傳速度,防止惡意占用帶寬
location /upload/ {
  client_max_body_size 100m;
  limit_rate 1m; # 限制上傳速度 1MB/s
  proxy_pass http://upload-backend;
}

踩坑點(diǎn)六:gzip 壓縮配置不當(dāng)

現(xiàn)象

服務(wù)器帶寬使用率很高,CPU 負(fù)載也上去了,但用戶反映頁面加載還是很慢。排查發(fā)現(xiàn)雖然配置了 gzip,但 response header 里沒有 Content-Encoding: gzip。用瀏覽器的開發(fā)者工具看 network 面板,發(fā)現(xiàn)傳輸?shù)奈募w積很大,沒有被壓縮。

或者反過來,gzip_comp_level 設(shè)置得太高,CPU 占用率直接拉滿,壓縮效果卻沒提升多少。

根因

gzip 是 Nginx 中提升傳輸效率的重要手段,但默認(rèn) gzip 是關(guān)閉的,而且 gzip_types 默認(rèn)只包含 text/html。如果只加了gzip on;就以為萬事大吉了,但實(shí)際上大部分請求的 MIME 類型沒有被加入壓縮列表。

另一個(gè)常見問題是 gzip_vary 頭沒有開啟,導(dǎo)致代理緩存(如 CDN、Varnish)給不同客戶端返回了不正確的壓縮版本。

正確配置

一個(gè)完整的 gzip 配置:

http {
  # 開啟 gzip
  gzip on;

  # 壓縮級(jí)別:1(最快,壓縮率低)到 9(最慢,壓縮率高),默認(rèn) 1
  # 生產(chǎn)環(huán)境建議 4~5,平衡 CPU 開銷和壓縮率
  gzip_comp_level 5;

  # gzip_buffers 定義壓縮時(shí)使用的緩存大小
  gzip_buffers 16 8k;

  # gzip_http_version 指定支持的 HTTP 版本
  gzip_http_version 1.1;

  # 開啟 Vary 頭,代理緩存要識(shí)別 User-Agent
  gzip_vary on;

  # 限制最小壓縮長度,太小的內(nèi)容壓縮反而增加開銷
  gzip_min_length 1024;

  # 對指定 MIME 類型啟用壓縮
  gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml
    application/xml+rss
    application/x-javascript
    application/octet-stream
    image/svg+xml;

  # gzip 對部分代理緩存的兼容性配置
  # 確保代理層能正確處理壓縮和非壓縮響應(yīng)

  server {
    listen 80;

    location /static/ {
      # 靜態(tài)資源在響應(yīng)頭里加 Expires,瀏覽器會(huì)緩存
      expires 7d;
      add_header Cache-Control "public, no-transform";
      # 關(guān)閉 access_log 減少 IO
      access_log off;
    }

    location /api/ {
      proxy_pass http://backend;
    }
  }
}

gzip 壓縮的效果通常非常顯著——JSON 響應(yīng)可以壓縮到原來的 20%~30%,CSS/JS 可以壓縮到 30%~40%。但注意:

圖片(PNG、JPG、WebP)和視頻、音頻不要壓縮——這些格式本身就是壓縮格式,gzip 反而徒增 CPU

太小的文件(< 1KB)不值得壓縮,壓縮開銷可能大于節(jié)省的傳輸量

不要對已壓縮的內(nèi)容二次壓縮

驗(yàn)證方法

# 用 curl 的 range 頭或 Accept-Encoding 測試壓縮
curl -I -H"Accept-Encoding: gzip"http://localhost/api/data 2>/dev/null
# 看響應(yīng)頭是否有:
# Content-Encoding: gzip
# Vary: Accept-Encoding

# 看原始響應(yīng)大小 vs 壓縮后大小
# 先獲取原始大小
curl -s http://localhost/api/data | wc -c

# 獲取 gzip 后大小
curl -s -H"Accept-Encoding: gzip"http://localhost/api/data | wc -c

# 用 ab 壓測,對比開啟/關(guān)閉 gzip 的 QPS
ab -n 1000 -c 10 -H"Accept-Encoding: gzip"http://localhost/api/data

# 查看 gzip 統(tǒng)計(jì)(需要編譯時(shí)帶了 --with-http_gzip_static_module)
# 在 error_log 中會(huì)有相關(guān)統(tǒng)計(jì)輸出

風(fēng)險(xiǎn)提醒

gzip_comp_level 超過 6 之后,壓縮率提升非常有限,但 CPU 消耗成倍增加。生產(chǎn)環(huán)境慎用高壓縮級(jí)別。

有些老版本 CDN 或代理服務(wù)不理解 gzip Vary 頭,可能導(dǎo)致緩存錯(cuò)亂。如果使用了 CDN,需要確認(rèn) CDN 是否支持 Vary: Accept-Encoding。

gzip 對 CPU 的消耗在高并發(fā)場景下不可忽視。如果服務(wù)器 CPU 本來就很緊張,可以適當(dāng)降低 gzip_comp_level 或減少 gzip_types。

踩坑點(diǎn)七:SSL/TLS 配置不完整

現(xiàn)象

用戶在瀏覽器訪問網(wǎng)站時(shí),看到「您的連接不是私密連接」或者證書錯(cuò)誤頁面。用curl -v https://example.com顯示證書鏈不完整,或者只配了證書文件沒有配中間證書(chain cert)。又或者雖然配置了 SSL,但用的加密套件是 TLS 1.0,已被主流瀏覽器標(biāo)記為不安全。

根因

HTTPS 配置看似簡單,但坑很多。常見的錯(cuò)誤包括:

證書鏈不完整:只配了公鑰證書(server.crt),沒有配中間證書(chain.cert)。瀏覽器在驗(yàn)證證書鏈時(shí)找不到中間 CA,就會(huì)報(bào)錯(cuò)。

私鑰和證書不匹配:證書是用另一個(gè)私鑰簽發(fā)的。

使用了不安全的協(xié)議版本:啟用了 SSLv3、TLS 1.0、TLS 1.1,這些已被廢棄。

使用了不安全的加密套件:比如 RC4、3DES,或者允許 NULL 加密。

證書文件路徑不存在:Let's Encrypt 證書續(xù)期后路徑變了,但 Nginx 配置沒更新。

正確配置

一個(gè)安全且完整的 HTTPS 配置:

server {
  listen 443 ssl http2;
  server_name example.com;

  # 證書文件(公鑰 + 中間證書要合并在一個(gè)文件里,或者分別指定)
  ssl_certificate /etc/nginx/ssl/example.com.fullchain.pem;
  # 私鑰文件
  ssl_certificate_key /etc/nginx/ssl/example.com.key;

  # TLS 版本控制,禁用不安全的舊版本
  ssl_protocols TLSv1.2 TLSv1.3;

  # 安全加密套件配置(推薦使用 Mozilla 的現(xiàn)代套件)
  ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256ECDHE-ECDSA-AES256-GCM-SHA384ECDHE-ECDSA-CHACHA20-POLY1305DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';

  ssl_prefer_server_ciphers on;

  # 開啟 OCSP Stapling,加快證書驗(yàn)證速度
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 5s;

  # 證書有效期提醒(可選項(xiàng),Nginx 1.19.10+ 支持)
  # ssl_conf_command CertificateSpkiCheck on;

  # HSTS 頭(嚴(yán)格傳輸安全,啟用前確保全站 HTTPS)
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

  # 其他安全響應(yīng)頭
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-XSS-Protection "1; mode=block" always;

  location / {
    root /data/www;
    index index.html;
  }
}

# HTTP 到 HTTPS 的強(qiáng)制跳轉(zhuǎn)
server {
  listen 80;
  server_name example.com;
  return 301 https://$server_name$request_uri;
}

關(guān)于證書鏈的合并:

# 正確的順序:服務(wù)器證書 -> 中間證書 -> 根證書(根證書可選)
# Let's Encrypt 證書合并方式:
cat /etc/letsencrypt/live/example.com/fullchain.pem > /etc/nginx/ssl/example.com.fullchain.pem
cat /etc/letsencrypt/live/example.com/privkey.pem > /etc/nginx/ssl/example.com.key.pem

# 驗(yàn)證證書鏈?zhǔn)欠裢暾?openssl s_client -connect example.com:443 -servername example.com
# 看 "Certificate chain" 部分是否完整

驗(yàn)證方法

# 檢查證書信息
openssl s_client -connect localhost:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer

# 檢查證書鏈
openssl s_client -connect localhost:443 -showcerts /dev/null | grep -E"subject=|issuer="

# 使用 curl 驗(yàn)證(curl 會(huì)檢查證書鏈完整性)
curl -v https://localhost/api/ 2>&1 | grep -E"SSL|HTTP|Server:

# 使用 testssl.sh(全量 TLS 安全檢測工具)檢查所有加密套件和協(xié)議版本
testssl.sh --protocols --ciphers --headers https://example.com

# 瀏覽器開發(fā)者工具 -> Security 面板 -> 查看證書詳情
# 在 Chrome 中訪問 chrome://flags 搜索 "Certificate" 看有沒有相關(guān)警告

# 檢查 SSL Labs 評(píng)分(在線工具)
# 訪問 https://www.ssllabs.com/ssltest/ 輸入域名查看評(píng)級(jí)

風(fēng)險(xiǎn)提醒

購買或續(xù)期證書后,一定要測試openssl s_client -connect確認(rèn)證書鏈完整再上線

啟用 HSTS(Strict-Transport-Security)后,用戶瀏覽器會(huì)強(qiáng)制 HTTPS,且 max-age 設(shè)置很長。如果證書有問題,影響范圍會(huì)很大。建議先用較短的 max-age 測試,確認(rèn)無誤后再放大。

TLS 1.3 在 Nginx 1.13+ 支持,但如果 client 是舊版 Android 或 Java 6/7,可能不支持。如果業(yè)務(wù)用戶群體中有這種情況,需要保留 TLS 1.2。

踩坑點(diǎn)八:worker_processes 與 worker_connections 配合錯(cuò)誤

現(xiàn)象

Nginx 配置里worker_connections設(shè)置得很大,比如 65535,但實(shí)際并發(fā)量稍微高一點(diǎn)就開始 502。用netstat或ss查看連接數(shù),明明遠(yuǎn)遠(yuǎn)沒到 65535,Nginx 卻報(bào) "too many connections"。這是系統(tǒng)層的文件描述符限制沒有同步調(diào)整導(dǎo)致的。

根因

Nginx 每個(gè) worker 進(jìn)程能夠處理的最大連接數(shù)由worker_connections控制(默認(rèn) 512)。但這個(gè)值受限于操作系統(tǒng)的文件描述符上限(ulimit -n)。如果系統(tǒng)的文件描述符上限只有 1024,而 Nginx 的 worker_connections 設(shè)置的是 65535,Nginx 實(shí)際能打開的連接數(shù)也只有 1024 左右。

同時(shí),Nginx 處理連接時(shí),每個(gè)連接除了占用一個(gè)文件描述符,還需要分配一定的內(nèi)存。如果內(nèi)存不足,大并發(fā)也會(huì)出問題。

正確配置

分兩層配置:

Nginx 配置層:

# /etc/nginx/nginx.conf

worker_processes auto; # 自動(dòng)等于 CPU 核心數(shù)
worker_rlimit_nofile 65535; # 允許每個(gè) worker 打開的最大文件描述符數(shù)

events {
  # 每個(gè) worker 的最大連接數(shù),受限于系統(tǒng) ulimit -n
  worker_connections 65535;
  # 使用 epoll(Linux)提高并發(fā)處理效率,F(xiàn)reeBSD 用 kqueue
  use epoll;
  # 允許一次接受多個(gè)連接
  multi_accept on;
}

http {
  # 打開文件緩存,減少磁盤 IO
  open_file_cache max=65535 inactive=60s;
  open_file_cache_valid 30s;
  open_file_cache_min_uses 2;
  open_file_cache_errors on;

  # 客戶端keepalive超時(shí)
  keepalive_timeout 65;
  # 單客戶端最大請求數(shù)(防止單個(gè)客戶端占用過多連接)
  keepalive_requests 1000;
}

系統(tǒng)配置層:

# 查看當(dāng)前文件描述符限制
ulimit-n

# 臨時(shí)修改(立即生效,但重啟后失效)
ulimit-n 65535

# 永久修改(編輯 /etc/security/limits.conf)
# 在文件末尾添加:
# * soft nofile 65535
# * hard nofile 65535
# root soft nofile 65535
# root hard nofile 65535

# 編輯 /etc/sysctl.conf(修改網(wǎng)絡(luò)參數(shù))
echo"fs.file-max = 1000000">> /etc/sysctl.conf
sysctl -p

# 編輯 /etc/pam.d/common-session(確保 PAM 讀取 limits.conf)
# 確認(rèn)有:session required pam_limits.so

# 修改 nginx systemd 服務(wù)文件(如果用 systemd 管理)
# /lib/systemd/system/nginx.service
# 在 [Service] 段添加:
# LimitNOFILE=65535
# 然后執(zhí)行:systemctl daemon-reload && systemctl restart nginx

系統(tǒng)層面的完整調(diào)優(yōu):

# /etc/sysctl.conf 網(wǎng)絡(luò)相關(guān)參數(shù)
cat >> /etc/sysctl.conf <

驗(yàn)證方法

# 查看 Nginx worker 進(jìn)程的文件描述符使用情況
ps -p $(pgrep nginx | head -1) -o pid,comm,nlwp,drs
# nlwp = number of light-weight processes/threads

# 查看實(shí)際打開的文件描述符數(shù)量
ls /proc/$(pgrep -f"nginx: worker"| head -1)/fd | wc -l

# 查看系統(tǒng)級(jí)別的文件描述符限制
cat /proc/sys/fs/file-max
ulimit-n

# 壓測驗(yàn)證配置生效
# 用 ss 觀察并發(fā)連接數(shù)上限
ss -s
# 或 ab 壓測
ab -n 10000 -c 5000 http://localhost/api/test

# 觀察 error_log 中是否有 "too many connections" 錯(cuò)誤
grep"too many connections"/var/log/nginx/error.log

風(fēng)險(xiǎn)提醒

文件描述符設(shè)置過大可能導(dǎo)致系統(tǒng)內(nèi)存泄漏或耗盡。每個(gè)文件描述符在 Linux 內(nèi)核中都有對應(yīng)的 struct file 結(jié)構(gòu),約占 2KB 內(nèi)存。設(shè)置 65535 個(gè)約需 130MB 物理內(nèi)存。

somaxconn 設(shè)置過大可能在遭受 SYN Flood 攻擊時(shí)放大攻擊效果。根據(jù)實(shí)際業(yè)務(wù)調(diào)整。

系統(tǒng)層面的修改需要 root 權(quán)限,修改前務(wù)必記錄原值,修改后立即驗(yàn)證,異常時(shí)能快速回滾。

踩坑點(diǎn)九:日志配置不當(dāng)導(dǎo)致磁盤爆滿

現(xiàn)象

服務(wù)器突然變得很慢,SSH 登錄后敲命令卡頓。用df -h一看,根分區(qū) 100% 滿了。進(jìn)一步排查發(fā)現(xiàn)/var/log/nginx/下的 access.log 或 error.log 達(dá)到了幾十 GB。上一次 logrotate 執(zhí)行不知道什么時(shí)候,或者是 logrotate 配置根本沒有生效。

根因

Nginx 的 access_log 默認(rèn)對每個(gè)請求都寫一行,高并發(fā)場景下日志量增長極快。如果 access_log 沒有配置合理的輪轉(zhuǎn)策略,磁盤空間遲早會(huì)被耗盡。

常見問題:

access_log 記錄了太多無用信息(特別是啟用了詳細(xì)的 log_format 包含大量 header)

error_log 級(jí)別設(shè)置為 debug,產(chǎn)生了巨量調(diào)試日志

logrotate 配置了但頻率太低(每天一次,而日志每小時(shí)就能寫滿磁盤)

日志被寫到了系統(tǒng)根分區(qū)而不是獨(dú)立掛載的日志分區(qū)

壓縮后的舊日志沒有被刪除

正確配置

Nginx 日志配置:

http {
  # 定義日志格式(不要記錄過長的 header 內(nèi)容)
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
          '$status $body_bytes_sent "$http_referer" '
          '"$http_user_agent" "$http_x_forwarded_for" '
          'rt=$request_time uct="$upstream_connect_time" '
          'uht="$upstream_header_time" urt="$upstream_response_time"';

  # 訪問日志(生產(chǎn)環(huán)境建議按虛擬主機(jī)拆分)
  access_log /var/log/nginx/access.log main buffer=16k flush=2m;

  # 錯(cuò)誤日志(生產(chǎn)環(huán)境不要用 debug,會(huì)寫大量日志)
  error_log /var/log/nginx/error.log warn;

  # Gzip 壓縮日志(減少日志體積)
  # 需要在 http 或 server 段開啟
  # gzip off; # 如果不需要 gzip 日志可以關(guān)閉
}

server {
  server_name example.com;
  access_log /var/log/nginx/example.com.access.log main;
  error_log /var/log/nginx/example.com.error.log;

  # 關(guān)閉不需要的日志,減少 IO
  location /health {
    access_log off;
    return 200 "OK";
  }

  # 靜態(tài)資源可以關(guān)閉 access_log
  location /static/ {
    root /data/www;
    access_log off;
    expires 7d;
  }
}

logrotate 配置:

# /etc/logrotate.d/nginx
/var/log/nginx/*.log{
  daily       # 每天輪轉(zhuǎn)一次
  missingok     # 日志不存在也不報(bào)錯(cuò)
  rotate 14     # 保留 14 份舊日志
  compress     # 壓縮舊日志(gzip)
  delaycompress   # 延遲壓縮,等下一次輪轉(zhuǎn)再壓縮
  notifempty    # 空日志不輪轉(zhuǎn)
  create 0640 nginx nginx # 輪轉(zhuǎn)后創(chuàng)建新文件的權(quán)限
  sharedscripts   # 所有日志輪轉(zhuǎn)完再執(zhí)行一次 postrotate

 # 向 master 進(jìn)程發(fā) reload 信號(hào),而不是 restart
  postrotate
   if[ -f /var/run/nginx.pid ];then
     kill-USR1 $(cat /var/run/nginx.pid)
   fi
  endscript
}

定時(shí)清理過大的日志文件(緊急處理用):

# 如果磁盤已經(jīng)滿了,先緊急清理日志釋放空間
# 1. 先找到最大的日志文件
du -sh /var/log/nginx/*.log| sort -rh | head -10

# 2. 截?cái)嗳罩疚募ú皇莿h除,避免 Nginx 還在寫這個(gè) inode)
# 危險(xiǎn)操作,先確認(rèn)你要截?cái)嗟氖悄膫€(gè)文件
truncate -s 0 /var/log/nginx/access.log

# 3. 如果 Nginx 還在寫,用 kill -USR1 通知它重新打開日志
kill-USR1 $(cat /var/run/nginx.pid)

# 4. 確認(rèn)磁盤空間釋放
df -h /var/log

驗(yàn)證方法

# 檢查日志輪轉(zhuǎn)是否正常工作
logrotate -d /etc/logrotate.d/nginx # 模擬執(zhí)行(不實(shí)際輪轉(zhuǎn))

# 檢查日志大小
ls -lh /var/log/nginx/

# 檢查磁盤使用情況(按大小排序)
du -ah /var/log/ | sort -rh | head -20

# 監(jiān)控系統(tǒng)日志目錄的大小增長
watch -n 5"df -h /var/log; ls -lhS /var/log/nginx/*.log"

# 開啟日志后定期檢查是否有異常(比如某個(gè) IP 頻繁請求導(dǎo)致日志暴增)
awk'{print $1}'/var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 統(tǒng)計(jì)訪問量最大的 IP,如果某個(gè) IP 請求量異常大,可能是被爬或被攻擊

風(fēng)險(xiǎn)提醒

絕對不要rm刪除正在被 Nginx 寫入的日志文件——?jiǎng)h除后 Nginx 會(huì)繼續(xù)寫這個(gè)已被刪除的 inode,導(dǎo)致磁盤空間持續(xù)消耗且無法通過刪除文件釋放。正確做法是truncate -s 0或> /var/log/nginx/access.log。

設(shè)置 logrotate 時(shí)確認(rèn)postrotate里發(fā)的是-USR1信號(hào)而非-HUP或 restart。-USR1讓 Nginx 重新打開日志文件,不中斷現(xiàn)有連接;-HUP會(huì)重載配置,可能觸發(fā) worker 進(jìn)程重啟。

access_log 關(guān)掉后無法做流量分析和異常排查,建議只關(guān)靜態(tài)資源的日志,保留 API 和核心業(yè)務(wù)的 access_log。

踩坑點(diǎn)十:隱藏 server 塊版本號(hào)未生效

現(xiàn)象

用curl -I http://example.com或?yàn)g覽器開發(fā)者工具查看響應(yīng)頭,發(fā)現(xiàn)Server: nginx/1.18.0(或具體版本號(hào))。滲透測試報(bào)告里指出這是信息泄露漏洞——攻擊者可以據(jù)此判斷 Nginx 版本,從而查找對應(yīng)的 CVE 漏洞。

你明明在 nginx.conf 里配置了server_tokens off;,但版本號(hào)還在顯示。

根因

server_tokens off;的作用范圍是有限的,它只關(guān)閉 Nginx 在 error_page 響應(yīng)和 Location 響應(yīng)頭中的版本號(hào)顯示,但如果你通過include /etc/nginx/conf.d/*.conf加載了其他 server 塊文件,或者使用了第三方模塊(如 OpenResty),配置可能不生效。

另一個(gè)常見問題是:server_tokens off 在 http 段配置了,但 server 段里又重寫了 error_page,或者 upstream 響應(yīng)頭里還帶著版本信息。

正確配置

基礎(chǔ)配置:

http {
  # 關(guān)閉版本號(hào)顯示(在 error 頁面和響應(yīng)頭中都生效)
  server_tokens off;

  # 如果需要完全自定義 Server 頭,可以用這個(gè)(需要 ngx_http_headers_more 模塊)
  # more_set_headers 'Server: MyServer';

  server {
    listen 80;
    server_name example.com;

    # 自定義錯(cuò)誤頁面,同時(shí)隱藏版本
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    location / {
      root /data/www;
    }

    # 自定義錯(cuò)誤頁面內(nèi)容,避免暴露 Nginx 版本
    location = /50x.html {
      root /data/www/errors;
    }
  }
}

完全隱藏 Nginx 標(biāo)識(shí)(需要編譯第三方模塊):

Nginx 默認(rèn)的 Server 頭由 HttpHeadersMore 模塊控制,但開源版 Nginx 本身不提供關(guān)閉 Server 頭的方法。如果需要完全隱藏,可以:

使用 OpenResty 的 header 電子:

# 需要安裝 OpenResty 或 ngx_http_headers_more 模塊
header_filter_by_lua_block {
  ngx.header.server = "MyServer"
}

或者在代碼層面——后端服務(wù)在響應(yīng)頭里覆蓋:

# 后端服務(wù)返回的響應(yīng)頭會(huì)帶上自定義 Server
proxy_set_header Server "MyServer";

編譯時(shí)修改:

# 編譯 Nginx 時(shí)指定默認(rèn) Server 頭
./configure --with-http_realip_module 
      --with-http_stub_status_module 
      --http-client-body-temp-path=/var/lib/nginx/client-body 
      --http-proxy-temp-path=/var/lib/nginx/proxy 
      --with-http_gzip_static_module 
      --add-module=# 第三方模塊路徑

# 編譯后修改 Objs/nginx.c 中的默認(rèn)字符串
# 查找 "ngx_http_server_string[]" 并修改 "nginx/" 為自定義值

驗(yàn)證方法

# 查看響應(yīng)頭中的 Server 字段
curl -I http://localhost/ 2>/dev/null | grep -i server
# 期望輸出只有 Server: nginx,沒有版本號(hào)

# 測試 error_page 響應(yīng)是否還帶版本號(hào)
curl -s http://localhost/nonexistent_path | grep -i"server|nginx"
# 期望:不包含 nginx 版本號(hào)

# 檢查是否有其他地方泄露了版本信息
# 比如 upstream 返回的響應(yīng)頭
curl -I http://localhost/api/ 2>/dev/null | grep -i server
curl -s http://localhost/api/ -H"Accept: text/html"| grep -i"nginx|server"

# 用 nikto 掃描(Nginx 指紋識(shí)別工具)
nikto -h http://localhost -Friendly

風(fēng)險(xiǎn)提醒

完全隱藏 Server 頭在實(shí)際安全收益上有限——有經(jīng)驗(yàn)的攻擊者通過 SSL 證書、對特定路徑的響應(yīng)特征等方式依然可以識(shí)別出 Nginx。但如果合規(guī)要求(如等保)要求隱藏版本號(hào),這是必要的配置。

如果使用 OpenResty 或第三方模塊,確保該模塊來源可信,避免引入新的安全風(fēng)險(xiǎn)。

修改 Server 頭后,如果業(yè)務(wù)依賴第三方安全掃描工具判斷 Web 服務(wù)器類型,可能導(dǎo)致掃描工具誤報(bào)。記得同步更新資產(chǎn)清單。

綜合排查路徑:502/504 故障排查流程

當(dāng) Nginx 返回 502 Bad Gateway 或 504 Gateway Timeout 時(shí),按以下路徑逐層排查:

排查路徑圖

Nginx 502/504
 ├── 1. 檢查 upstream 是否存活
 │   ├── curl 本地測試后端端口是否響應(yīng)
 │   │   curl http://127.0.0.1:8080/health
 │   └── 檢查 upstream 進(jìn)程狀態(tài)和端口監(jiān)聽
 │      ps aux | grep backend
 │      ss -tlnp | grep 8080
 │
 ├── 2. 檢查 Nginx 與 upstream 之間的網(wǎng)絡(luò)連通性
 │   ├── telnet 127.0.0.1 8080
 │   ├── ping 127.0.0.1
 │   └── iptables/selinux 是否攔截
 │
 ├── 3. 檢查 upstream 連接數(shù)和超時(shí)配置
 │   ├── upstream keepalive 是否足夠
 │   ├── proxy_connect_timeout 是否太小
 │   ├── proxy_read_timeout 是否太小
 │   └── upstream 服務(wù)的連接池是否耗盡
 │
 ├── 4. 檢查 upstream 進(jìn)程/容器狀態(tài)
 │   ├── 進(jìn)程是否 OOM 被殺
 │   │   dmesg | grep -i oom
 │   │   journalctl -u backend --since"10 minutes ago"
 │   ├── 容器是否被重啟
 │   │   docker ps -a | grep backend
 │   └── Kubernetes: pod 狀態(tài)
 │      kubectl get pods -n default | grep backend
 │
 ├── 5. 檢查 error_log 中的具體錯(cuò)誤信息
 │   ├──"connect() failed"
 │   ├──"Connection refused"
 │   ├──"Connection timed out"
 │   └──"no live upstreams"
 │
 └── 6. 檢查 Nginx upstream 配置是否正確
    ├── upstream 塊中的 server 地址是否正確
    ├── proxy_pass 是否指向了正確的 upstream
    └── upstream 是否所有 server 都 down 了

常用診斷命令匯總

# 一分鐘內(nèi)快速診斷 502 問題
echo"=== 1. Nginx error_log ==="&& tail -100 /var/log/nginx/error.log | grep -i"502|upstream|connect"

echo"=== 2. Nginx upstream 進(jìn)程狀態(tài) ==="&& ps aux | grep -E"java|node|python|php"| grep -v grep

echo"=== 3. 端口監(jiān)聽狀態(tài) ==="&& ss -tlnp | grep -E"8080|3000|5000|9000|3306"

echo"=== 4. 本地 upstream 健康檢查 ==="&& curl -s -o /dev/null -w"%{http_code}"http://127.0.0.1:8080/health

echo"=== 5. 系統(tǒng)資源 ==="&& free -h && df -h / && uptime

echo"=== 6. 近期系統(tǒng)日志(OOM 等)==="&& dmesg | tail -50 | grep -iE"oom|killed|nginx|java|node"

特殊狀態(tài)碼處理

狀態(tài)碼 含義 常見原因 排查命令
400 Bad Request 請求頭過大、語法錯(cuò)誤 grep "400" /var/log/nginx/error.log
413 Request Entity Too Large client_max_body_size 超限 增大 client_max_body_size
444 Nginx 特有,連接直接關(guān)閉 被配置攔截,未返回響應(yīng) 常見于配置了if (condition) { return 444; }
499 Client Closed Request 客戶端主動(dòng)斷開,upstream 處理過慢 增加 proxy_read_timeout
502 Bad Gateway upstream 進(jìn)程掛了或無響應(yīng) 逐層檢查 upstream 進(jìn)程和端口
503 Service Temporarily Unavailable upstream 達(dá)到 max_fails 暫時(shí)不可用 等 30s 自動(dòng)恢復(fù)或檢查 fail_timeout
504 Gateway Timeout upstream 處理超時(shí) 增加 proxy_read_timeout / proxy_send_timeout

生產(chǎn)環(huán)境變更 checklist

每次 Nginx 配置變更前,必須完成以下檢查項(xiàng):

變更前

# 1. 備份當(dāng)前配置
cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d%H%M%S)

# 2. 在測試環(huán)境驗(yàn)證語法
nginx -t -c /etc/nginx/nginx.conf

# 3. 確認(rèn)變更影響的站點(diǎn)和路徑
grep -n"proxy_pass|upstream|listen"/etc/nginx/conf.d/*.conf

# 4. 確認(rèn)新配置中的 IP、端口、路徑?jīng)]有錯(cuò)誤
# 特別是 upstream 的 server 地址和端口

# 5. 準(zhǔn)備回滾腳本
cp /etc/nginx/nginx.conf /tmp/nginx.conf.backup

變更中

# 1. 部署新配置文件
cp /path/to/new/nginx.conf /etc/nginx/nginx.conf

# 2. 驗(yàn)證語法
nginx -t

# 3. reload 而非 restart
nginx -s reload

# 4. 確認(rèn) master 進(jìn)程還在,worker 進(jìn)程正常
ps aux | grep nginx

# 5. 立即觀察 error_log
tail -f /var/log/nginx/error.log

變更后

# 1. 全量健康檢查(測試所有站點(diǎn))
curl -s -o /dev/null -w"%{http_code} %{url_effective}
"
  http://localhost/ 
  http://localhost/api/ 
  http://localhost/static/

# 2. 監(jiān)控 502/504 錯(cuò)誤率(變更后 5 分鐘內(nèi)每分鐘檢查一次)
foriin{1..5};do
 echo"=== Check$i==="
  grep -c"502|504"/var/log/nginx/access.log
  sleep 60
done

# 3. 確認(rèn)新舊 worker 進(jìn)程平滑切換(舊 worker 優(yōu)雅退出)
ps aux | grep"nginx: worker"| wc -l
# 確認(rèn) worker 數(shù)量等于配置中的 worker_processes * worker_connections 的 worker 數(shù)

# 4. 如果有問題,立即回滾
cp /tmp/nginx.conf.backup /etc/nginx/nginx.conf
nginx -t && nginx -s reload

回滾方案

#!/bin/bash
# rollback_nginx.sh - 緊急回滾腳本

BACKUP_FILE="/tmp/nginx.conf.backup"
NGINX_CONF="/etc/nginx/nginx.conf"

if[ ! -f"$BACKUP_FILE"];then
 echo"ERROR: Backup file not found:$BACKUP_FILE"
 exit1
fi

echo"Rolling back Nginx configuration..."
cp"$BACKUP_FILE""$NGINX_CONF"

ifnginx -t 2>&1 | grep -q"syntax is ok";then
  nginx -s reload
 echo"Rollback successful. Nginx reloaded."
else
 echo"ERROR: Rollback config failed syntax check. Manual intervention required."
 exit1
fi

總結(jié)

Nginx 配置的十個(gè)踩坑點(diǎn),總結(jié)起來有一個(gè)共同規(guī)律:Nginx 的配置項(xiàng)之間不是孤立的,而是相互影響、相互制約的。proxy_pass 的尾部斜杠影響路徑傳遞,location 的修飾符決定匹配順序和正則行為,worker_processes 和系統(tǒng) ulimit 共同決定實(shí)際并發(fā)上限,gzip 壓縮和 CPU 消耗是一對矛盾體。

一個(gè)合格的運(yùn)維工程師,不僅要記住每個(gè)配置項(xiàng)怎么寫,更要理解配置項(xiàng)之間的協(xié)作關(guān)系。以下幾點(diǎn)核心原則,貫穿了所有的踩坑場景:

原則一:先測試后上線。nginx -t能排除大部分語法錯(cuò)誤,但不是所有。上線前在測試環(huán)境完整跑一遍業(yè)務(wù)流程,用 curl 覆蓋所有關(guān)鍵路徑。

原則二:小步快走,留好回滾。每次變更只改一個(gè)配置項(xiàng),改完后立即驗(yàn)證。多個(gè)配置項(xiàng)一起改,出問題都不知道是哪一項(xiàng)導(dǎo)致的。

原則三:關(guān)注資源邊界,不只看配置值。worker_connections 再大,系統(tǒng) ulimit 不夠也用不上;client_max_body_size 再大,磁盤滿了也存不了。系統(tǒng)層和 Nginx 配置層要一起調(diào)。

原則四:日志是運(yùn)維的眼睛。access_log 和 error_log 不只是出問題時(shí)才看,日常要看基線、觀趨勢,發(fā)現(xiàn)異常苗頭及時(shí)處理。

原則五:安全配置要完整。HTTPS 證書鏈、TLS 版本、加密套件、版本隱藏,這些不只是安全合規(guī)要求,也是運(yùn)維的基本功。

Nginx 的配置不算復(fù)雜,指令也就幾十個(gè),但用對、用好、用穩(wěn),需要在實(shí)踐中不斷積累經(jīng)驗(yàn)。每一個(gè)踩過的坑,都是真實(shí)生產(chǎn)環(huán)境里的教訓(xùn)。希望這十個(gè)場景能幫你少走彎路,遇到問題時(shí)能快速定位、精準(zhǔn)修復(fù)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Web
    Web
    +關(guān)注

    關(guān)注

    2

    文章

    1311

    瀏覽量

    75089
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    14

    文章

    10399

    瀏覽量

    91801
  • nginx
    +關(guān)注

    關(guān)注

    0

    文章

    199

    瀏覽量

    13235

原文標(biāo)題:線上頻發(fā)故障:Nginx 典型配置錯(cuò)誤復(fù)盤與優(yōu)化

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    Linux運(yùn)維Nginx軟件優(yōu)化之安全優(yōu)化

    一、Nginx優(yōu)化分類安全優(yōu)化(提升網(wǎng)站安全性配置)性能優(yōu)化(提升用戶訪問網(wǎng)站效率)二、Nginx
    發(fā)表于 12-17 15:12

    Linux運(yùn)維Nginx軟件優(yōu)化Nginx性能優(yōu)化

    1. 優(yōu)化nginx worker進(jìn)行個(gè)數(shù)nginx服務(wù)主要有兩個(gè)重要進(jìn)程:01) master進(jìn)程:可以控制nginx服務(wù)的啟動(dòng) 停止 或重啟02) worker進(jìn)程:處理用戶請求信
    發(fā)表于 12-18 15:11

    Linux運(yùn)維Nginx軟件優(yōu)化之日志優(yōu)化

    1. 配置Nginx服務(wù)相關(guān)日志操作1) 進(jìn)行日志的切割[code][root@oldboy ~]# mkdir /server/scripts/ -p[root@oldboy ~]# cd
    發(fā)表于 12-18 15:17

    nginx中的sendfile配置說明

    nginx配置sendfile及詳細(xì)說明
    發(fā)表于 05-05 08:08

    nginx錯(cuò)誤頁面配置

    16、nginx 錯(cuò)誤頁面配置nginx錯(cuò)誤頁面包括404 403 500 502 503 504等頁面,只需要在server中增加以下
    發(fā)表于 07-26 06:54

    主要學(xué)習(xí)下nginx的安裝配置

    用于nginx編碼轉(zhuǎn)換的配置文件;/var/log/nginxnginx的訪問和錯(cuò)誤日志目錄;/var/cache/
    發(fā)表于 10-19 14:12

    運(yùn)行nginx所需的最低配置

    安全服務(wù)器是只允許所需數(shù)量的服務(wù)器。理想情況下,我們將通過單獨(dú)啟用其他功能來基于最小系統(tǒng)構(gòu)建服務(wù)器。進(jìn)行最少的配置也有助于調(diào)試。如果該錯(cuò)誤在最小系統(tǒng)中不可用,則分別添加功能,然后繼續(xù)搜索錯(cuò)誤。 這是
    的頭像 發(fā)表于 08-23 10:53 ?6103次閱讀

    每個(gè)人都要會(huì)的復(fù)知識(shí)

    什么是復(fù) 復(fù)本來是圍棋術(shù)語。指的是在對弈之后,棋手們會(huì)重演一遍對局,從中發(fā)現(xiàn)自己的錯(cuò)誤,理解對手的思路,研究更為妥善的走法。很多圍棋高手
    的頭像 發(fā)表于 03-18 11:47 ?1916次閱讀

    Nginx常用的配置和基本功能講解

    Nginx 已經(jīng)廣泛應(yīng)用于 J-one 和 Jdos 的環(huán)境部署上,本文對 Nginx 的常用的配置和基本功能進(jìn)行講解,適合 Nginx 入門學(xué)習(xí)。
    的頭像 發(fā)表于 05-04 10:25 ?1579次閱讀

    nginx負(fù)載均衡配置介紹

    目錄 nginx負(fù)載均衡 nginx負(fù)載均衡介紹 反向代理與負(fù)載均衡 nginx負(fù)載均衡配置 Keepalived高可用nginx負(fù)載均衡器
    的頭像 發(fā)表于 11-10 13:39 ?1765次閱讀
    <b class='flag-5'>nginx</b>負(fù)載均衡<b class='flag-5'>配置</b>介紹

    如何通過優(yōu)化Nginx配置來提高網(wǎng)絡(luò)環(huán)境的安全性

    。本文為系統(tǒng)管理員、開發(fā)者等提供詳盡的安全加固指南,涵蓋基礎(chǔ)到高級(jí)策略,包括隱藏版本號(hào)信息、限制敏感目錄訪問、啟用HTTPS、配置錯(cuò)誤頁面、應(yīng)用內(nèi)容安全策略(CSP)、設(shè)置正確文件權(quán)限、添加安全HTTP響應(yīng)頭、限制連接數(shù)、配置I
    的頭像 發(fā)表于 02-14 17:49 ?2285次閱讀

    Nginx服務(wù)優(yōu)化教程

    隱藏Nginx版本號(hào),避免安全漏洞泄漏:修改配置文件法;修改源碼法
    的頭像 發(fā)表于 03-12 15:57 ?1101次閱讀
    <b class='flag-5'>Nginx</b>服務(wù)<b class='flag-5'>優(yōu)化</b>教程

    Nginx配置終極指南

    更新。性能是 Nginx 最重要的考量,其占用內(nèi)存少、并發(fā)能力強(qiáng)、能支持高達(dá) 5w 個(gè)并發(fā)連接數(shù),最重要的是, Nginx 是免費(fèi)的并可以商業(yè)化,配置使用也比較簡單。
    的頭像 發(fā)表于 06-18 15:56 ?1255次閱讀
    <b class='flag-5'>Nginx</b><b class='flag-5'>配置</b>終極指南

    Nginx高并發(fā)優(yōu)化方案

    作為一名在生產(chǎn)環(huán)境中摸爬滾打多年的運(yùn)維工程師,我見過太多因?yàn)?b class='flag-5'>Nginx配置不當(dāng)導(dǎo)致的性能瓶頸。今天分享一套完整的Nginx高并發(fā)優(yōu)化方案,幫助你的系統(tǒng)從10萬QPS突破到百萬級(jí)別。
    的頭像 發(fā)表于 08-13 15:51 ?1280次閱讀

    Nginx 502 Bad Gateway錯(cuò)誤的成因和排查方法

    502 Bad Gateway 是 Nginx 作為反向代理服務(wù)器時(shí)最常遭遇的錯(cuò)誤狀態(tài)碼。這個(gè)錯(cuò)誤意味著 Nginx 作為網(wǎng)關(guān),成功與后端 upstream 建立了連接,但后端返回了一
    的頭像 發(fā)表于 05-06 11:13 ?363次閱讀
    营山县| 灵石县| 满洲里市| 五莲县| 军事| 伊宁市| 长葛市| 平凉市| 班玛县| 旌德县| 门头沟区| 东乌| 崇信县| 黄大仙区| 祁东县| 康保县| 保山市| 青铜峡市| 高台县| 霍州市| 井冈山市| 潜山县| SHOW| 自贡市| 双桥区| 洪洞县| 嘉义市| 吉隆县| 临桂县| 天台县| 新泰市| 三江| 芜湖市| 翼城县| 长治市| 精河县| 土默特右旗| 乌鲁木齐市| 天峻县| 平武县| 东源县|