實(shí)踐:從反向代理到平滑升級(jí))
聊到 Nginx很多剛接觸服務(wù)端的朋友第一反應(yīng)是“這不就是個(gè) Web 服務(wù)器嘛”等真正把它丟進(jìn)生產(chǎn)環(huán)境才發(fā)現(xiàn)Nginx 的作用和應(yīng)用場(chǎng)景比想象中大得多靜態(tài)資源服務(wù)、反向代理、負(fù)載均衡、HTTPS 證書卸載、緩存加速、限流防刷……幾乎每一層都能看到它的身影。這篇文章我想從一個(gè)“普通問(wèn)題”聊起把 Nginx 的核心作用一條條拆開再結(jié)合我這些年實(shí)際踩過(guò)的坑從安裝配置到平滑升級(jí)、問(wèn)題排查給你一份可以直接抄作業(yè)的完整參考。整套內(nèi)容適合剛?cè)腴T的人也適合已經(jīng)部署過(guò) Nginx 但沒系統(tǒng)梳理過(guò)的同學(xué)。我盡量不說(shuō)廢話全部以實(shí)際場(chǎng)景和可復(fù)現(xiàn)的配置為準(zhǔn)。1. Nginx 到底在解決什么問(wèn)題先說(shuō)一個(gè)最容易被忽略的事實(shí)Nginx 最初解決的是 C10K 問(wèn)題也就是單機(jī)能不能扛住一萬(wàn)個(gè)并發(fā)連接。2004 年它剛出來(lái)的時(shí)候市面上的主流做法還是“每個(gè)請(qǐng)求一個(gè)進(jìn)程”的 Apache 模型。連接一多內(nèi)存和 CPU 就被進(jìn)程調(diào)度吃干凈機(jī)器直接卡死。Nginx 的思路完全不同它用事件驅(qū)動(dòng)、異步非阻塞的模型用少量 worker 進(jìn)程就能撐住海量連接。一個(gè)進(jìn)程可以同時(shí)處理成千上萬(wàn)個(gè)請(qǐng)求就像餐廳里一個(gè)優(yōu)秀的排號(hào)員同時(shí)在服務(wù)幾十桌客人而不是每個(gè)客人配一個(gè)專屬服務(wù)員。放到今天Nginx 的核心功能已經(jīng)發(fā)展成四塊靜態(tài)資源服務(wù)圖片、CSS、JS、HTML、音視頻交給它又穩(wěn)又快。反向代理把請(qǐng)求轉(zhuǎn)發(fā)到后端的應(yīng)用服務(wù)器比如 Java 的 Spring Boot、Node.js、PHP-FPM。負(fù)載均衡把流量分?jǐn)偟蕉嗯_(tái)后端機(jī)器避免一臺(tái)被壓垮。安全與加速SSL/TLS 證書卸載、HTTP/2、HTTP/3QUIC、限流、緩存、訪問(wèn)控制。所以你看很多團(tuán)隊(duì)把 Nginx 放在所有流量的最前面它不是簡(jiǎn)單的“網(wǎng)頁(yè)服務(wù)器”而是整個(gè)系統(tǒng)的入口網(wǎng)關(guān)。我個(gè)人的理解是Nginx 是“連接用戶和后端服務(wù)之間的那雙手”。用戶請(qǐng)求進(jìn)來(lái)它決定把人帶到哪個(gè)頁(yè)面、哪個(gè)后端接口、哪臺(tái)服務(wù)器如果后端掛了它還能幫忙擋一下。這個(gè)角色決定了它的配置方式五花八門但底層邏輯始終只有一條——把請(qǐng)求處理到正確的地方。下面我就逐個(gè)拆開講。2. 拆開 Nginx 的四個(gè)核心作用2.1 靜態(tài)資源服務(wù)最基礎(chǔ)也最容易被忽視靜態(tài)資源服務(wù)是 Nginx 的基本功也是很多人第一次接觸它的原因。你本地跑了一個(gè) Vue 或 React 項(xiàng)目執(zhí)行pnpm run build之后生成一個(gè) dist 目錄想讓別人能訪問(wèn)最簡(jiǎn)單的辦法就是讓 Nginx 直接托管這個(gè)目錄。配合熱詞里看到的“pnpm run build 的包怎么 nginx 啟動(dòng)”其實(shí)就是把構(gòu)建產(chǎn)物丟到 Nginx 的 root 路徑下。一個(gè)最簡(jiǎn)單的托管配置server { listen 80; server_name example.com; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }注意最后那個(gè)try_filesSPA 項(xiàng)目基本都靠它。前端路由是 history 模式時(shí)比如/user/123服務(wù)器上根本不存在這個(gè)物理文件如果不加try_files直接刷新頁(yè)面會(huì) 404。try_files $uri $uri/ /index.html的含義是先找這個(gè)路徑有沒有對(duì)應(yīng)文件沒有再找有沒有對(duì)應(yīng)目錄都沒有就統(tǒng)一返回index.html讓前端路由自己去處理。靜態(tài)資源這塊有幾個(gè)關(guān)鍵性能參數(shù)值得單獨(dú)說(shuō)sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; gzip_types text/plain text/css application/javascript application/json image/svgxml; gzip_min_length 1k; location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control public, immutable; }sendfile讓文件從磁盤到網(wǎng)卡的拷貝過(guò)程由內(nèi)核直接完成減少用戶態(tài)切換。gzip壓縮文本類文件實(shí)測(cè)靜態(tài)資源體積能減少 60% 以上。expires 30d給靜態(tài)資源設(shè)置瀏覽器緩存二次訪問(wèn)幾乎無(wú)延遲。我見過(guò)很多團(tuán)隊(duì)花大價(jià)錢優(yōu)化后端接口結(jié)果前端靜態(tài)資源一個(gè) gzip 都沒開首屏加載能慢三倍。靜態(tài)資源托管是最簡(jiǎn)單的優(yōu)化起點(diǎn)。2.2 反向代理讓請(qǐng)求去它該去的地方反向代理是 Nginx 使用頻率最高的功能。所謂反向代理就是用戶請(qǐng)求先到 NginxNginx 再按照規(guī)則轉(zhuǎn)發(fā)到后端的應(yīng)用服務(wù)器。用戶可以感知到的只有 Nginx后端服務(wù)器具體在哪、有多少臺(tái)對(duì)用戶是透明的。對(duì)應(yīng)的還有正向代理那是替客戶端轉(zhuǎn)發(fā)請(qǐng)求的常用于內(nèi)網(wǎng)訪問(wèn)外網(wǎng)。Nginx 做的是反過(guò)來(lái)的事替服務(wù)器收請(qǐng)求所以叫反向代理。一個(gè)典型的 API 轉(zhuǎn)發(fā)配置server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:8080; 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; } }這里有幾個(gè)容易忽略的點(diǎn)第一proxy_set_header Host $host非常重要。后端很多框架會(huì)根據(jù) Host 頭生成跳轉(zhuǎn)鏈接或判斷域名如果不帶上后端拿到的一律是 Nginx 的內(nèi)網(wǎng)地址簽名校驗(yàn)、單點(diǎn)登錄、跨域這些全都會(huì)出問(wèn)題。第二X-Real-IP和X-Forwarded-For是為了讓后端拿到用戶真實(shí) IP。如果沒有這兩個(gè)配置后端日志里看到的所有請(qǐng)求 IP 都是 Nginx 的地址一旦要做封禁、限流、審計(jì)完全沒法搞。第三proxy_pass后面有沒有子路徑行為完全不一樣。比如location /api/ { proxy_pass http://backend/; }這種寫法會(huì)把/api/前綴去掉再轉(zhuǎn)發(fā)。而location /api/ { proxy_pass http://backend; }這種不帶尾部/的會(huì)把完整的/api/...路徑直接拼到后端地址后面。這個(gè)細(xì)節(jié)是大坑我見過(guò)不下五次因?yàn)檫@里多一個(gè)斜杠少一個(gè)斜杠導(dǎo)致接口 404。熱詞里有一條“nginx 限制只轉(zhuǎn)發(fā)帶參數(shù)的 url”這個(gè)需求本質(zhì)就是按照查詢參數(shù)決定要不要轉(zhuǎn)發(fā)。常見做法是在 location 里判斷$arg_或$query_stringlocation /api/ { if ($args ~ token.) { proxy_pass http://backend; break; } return 404; }意思很直白請(qǐng)求里帶了 token 參數(shù)才轉(zhuǎn)發(fā)否則直接返回 404。break的作用是命中 if 之后不再繼續(xù)走后續(xù) rewrite 規(guī)則。需要注意的是Nginx 的if指令在很多場(chǎng)景下有坑官方文檔只建議在 return、rewrite 這類場(chǎng)景用整體轉(zhuǎn)發(fā)邏輯盡量謹(jǐn)慎能用location或map實(shí)現(xiàn)就不要硬寫一堆 if。2.3 負(fù)載均衡把流量攤到多臺(tái)機(jī)器上當(dāng)單臺(tái)后端扛不住并發(fā)你就需要橫向擴(kuò)容前面放一個(gè) Nginx 做負(fù)載均衡。Nginx 的upstream模塊就是干這個(gè)的配合請(qǐng)求量把流量分發(fā)到不同后端。最小的負(fù)載均衡配置upstream backend_cluster { server 192.168.1.10:8080 weight3; server 192.168.1.11:8080 weight1; server 192.168.1.12:8080 backup; } server { listen 80; server_name app.example.com; location / { proxy_pass http://backend_cluster; proxy_set_header Host $host; } }這里我用了三個(gè)節(jié)點(diǎn)前兩臺(tái)權(quán)重是 3:1意思是每 4 個(gè)請(qǐng)求里約 3 個(gè)打到 10 這臺(tái)1 個(gè)打到 11 這臺(tái)。權(quán)重適合兩臺(tái)機(jī)器配置不一樣的場(chǎng)景配置高的多加一點(diǎn)流量。第三臺(tái)打了backup標(biāo)記平時(shí)不參與服務(wù)只有前面兩臺(tái)都掛了才啟用。這相當(dāng)于一個(gè)災(zāi)備節(jié)點(diǎn)自動(dòng)化切換。除了權(quán)重輪詢Nginx 還支持ip_hash按用戶 IP 的哈希結(jié)果分配同一 IP 固定打到同一臺(tái)后端。適合需要 session 保持的老項(xiàng)目。least_conn優(yōu)先發(fā)給當(dāng)前連接數(shù)最少的后端適合請(qǐng)求處理時(shí)長(zhǎng)差異較大的場(chǎng)景。負(fù)載均衡不是單純“把請(qǐng)求發(fā)出去”還要考慮后端健康狀態(tài)。Nginx 有被動(dòng)健康檢查即請(qǐng)求轉(zhuǎn)發(fā)后如果連續(xù)失敗max_fails次就把這臺(tái)服務(wù)器臨時(shí)標(biāo)記為不可用等f(wàn)ail_timeout時(shí)間后再重試。常用配置upstream backend_cluster { server 192.168.1.10:8080 max_fails2 fail_timeout30s; server 192.168.1.11:8080 max_fails2 fail_timeout30s; }意思是 30 秒內(nèi)失敗 2 次就摘掉這個(gè)節(jié)點(diǎn)30 秒后再試探。這種機(jī)制應(yīng)對(duì)日常宕機(jī)足夠了但它屬于“事后發(fā)現(xiàn)”請(qǐng)求已經(jīng)轉(zhuǎn)發(fā)過(guò)去并失敗了。如果要求更主動(dòng)的健康探測(cè)得用商業(yè)版 Plus 或者配合第三方模塊也可以用腳本定時(shí)探測(cè)后動(dòng)態(tài)修改 upstream。關(guān)于高可用線上一般會(huì)再加一層 keepalived把 Nginx 本身做成雙機(jī)熱備用虛擬 IPVIP對(duì)外提供服務(wù)。一臺(tái) Nginx 掛了VIP 自動(dòng)漂移到另一臺(tái)對(duì)用戶完全無(wú)感知。這里不展開講 keepalived 的配置但方向是明確的Nginx 做流量入口keepalived 做入口的 “保險(xiǎn)絲”。2.4 SSL/TLS 終端證書卸載與安全加速現(xiàn)在大部分網(wǎng)站都是 HTTPS證書配置是每個(gè) Nginx 用戶繞不開的活。Nginx 在 SSL 這塊的位置也非常特殊它通常是 TLS 連接的“終點(diǎn)站”外網(wǎng)客戶端和 Nginx 之間走 HTTPSNginx 和后端之間可以走內(nèi)網(wǎng) HTTP。這樣做的原因很實(shí)際TLS 握手和加解密都是 CPU 密集操作把這事集中在 Nginx 這一層做后端應(yīng)用就能省出大量 CPU 去處理業(yè)務(wù)邏輯。一段常規(guī) HTTPS 配置server { listen 443 ssl; http2 on; server_name www.example.com; ssl_certificate /etc/nginx/certs/example.com.pem; ssl_certificate_key /etc/nginx/certs/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://backend; } } server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }第一個(gè)server塊監(jiān)聽 443 端口加載證書處理加密流量第二個(gè)server塊把 80 端口的 HTTP 請(qǐng)求全部 301 跳轉(zhuǎn)到 HTTPS。很多小項(xiàng)目直接用這一套就完成了 HTTPS 改造。ssl_protocols建議只保留 TLSv1.2 和 TLSv1.3老舊的 TLSv1.0、TLSv1.1 都有已知漏洞沒必要為了兼容十幾年前的瀏覽器留著。順帶說(shuō)一下熱詞里的 “docker pull nginx quic 協(xié)議”。QUIC 是 HTTP/3 的底層傳輸協(xié)議Nginx 從 1.25.0 開始對(duì) HTTP/3 的支持逐漸成熟。要啟用 HTTP/3Nginx 編譯時(shí)需要加--with-http_v3_module參數(shù)然后在 listen 指令里加上http3listen 443 quic reuseport; listen 443 ssl; http3 on;如果你的 Nginx 是官方通過(guò) yum/apt 安裝的版本先確認(rèn)版本和編譯參數(shù)是否帶了 HTTP/3 模塊可以用nginx -V看。Docker 鏡像docker pull nginx拉下來(lái)之后官方主線版鏡像是否包含 v3 模塊取決于鏡像構(gòu)建參數(shù)需要先nginx -V 21 | grep http_v3驗(yàn)證。如果沒帶就考慮用源碼編譯或換成帶模塊的鏡像。QUIC 確實(shí)能顯著改善弱網(wǎng)環(huán)境下的連接成功率但部署復(fù)雜度也更高不是所有項(xiàng)目都急著上。3. 應(yīng)用場(chǎng)景與選型什么時(shí)候該用 Nginx3.1 前端接入層統(tǒng)一入口大多數(shù) Web 項(xiàng)目的第一層入口就是 Nginx。它的作用相當(dāng)于一個(gè)“前臺(tái)接待”所有外部請(qǐng)求先到這里再根據(jù)域名、路徑、請(qǐng)求頭分發(fā)到不同服務(wù)按域名區(qū)分api.example.com走 API 服務(wù)admin.example.com走管理后臺(tái)。按路徑區(qū)分/api/*走后端接口/static/*走靜態(tài)資源/websocket走長(zhǎng)連接服務(wù)。按請(qǐng)求方法區(qū)分讀接口和寫接口分到不同的上游。有了這一層后端的任何服務(wù)都不需要直接暴露公網(wǎng) IP只需要監(jiān)聽內(nèi)網(wǎng)端口整個(gè)入口的收口和安全控制都變得很輕松。限流也是入口層常見的需求。比如給登錄接口加限制limit_req_zone $binary_remote_addr zonelogin_limit:10m rate10r/m; location /api/login { limit_req zonelogin_limit burst5 nodelay; proxy_pass http://backend; }這里rate10r/m表示每分鐘最多 10 個(gè)請(qǐng)求burst5表示允許突發(fā) 5 個(gè)進(jìn)入排隊(duì)隊(duì)列。對(duì)登錄、短信驗(yàn)證碼這類高風(fēng)險(xiǎn)接口限流是必須的。3.2 動(dòng)靜分離前端靜態(tài)資源與后端動(dòng)態(tài)接口解耦傳統(tǒng)后端渲染的項(xiàng)目尤其是 PHP、Java 單體應(yīng)用靜態(tài)資源和動(dòng)態(tài)接口都混在一起。用戶訪問(wèn)一個(gè)頁(yè)面服務(wù)器既要讀模板文件又要查數(shù)據(jù)庫(kù)全部串行處理慢且耗資源。用 Nginx 做動(dòng)靜分離之后靜態(tài)資源直接走 Nginx 文件系統(tǒng)動(dòng)態(tài)請(qǐng)求才轉(zhuǎn)發(fā)給后端location ~* \.(html|css|js|png|jpg|gif|ico|svg|woff2?)$ { root /data/static; expires 7d; } location / { proxy_pass http://backend; }動(dòng)靜分離對(duì)混合架構(gòu)特別有用。比如前端用 React 構(gòu)建靜態(tài)頁(yè)面后端用 Java 提供 API整體結(jié)構(gòu)就是Nginx 托管前端靜態(tài)文件同時(shí)把/api/請(qǐng)求轉(zhuǎn)發(fā)到 Java 服務(wù)。這也是現(xiàn)在最常見的前后端分離部署形態(tài)。3.3 微服務(wù)與 API 網(wǎng)關(guān)場(chǎng)景微服務(wù)架構(gòu)里每個(gè)服務(wù)可能單獨(dú)部署在一組機(jī)器上客戶端不可能記住每個(gè)服務(wù)的地址。Nginx 可以作為輕量 API 網(wǎng)關(guān)按路徑把請(qǐng)求分發(fā)到不同的微服務(wù)upstream order_service { server 10.0.0.11:8080; server 10.0.0.12:8080; } upstream user_service { server 10.0.1.11:8080; server 10.0.1.12:8080; } server { listen 80; server_name gateway.example.com; location /api/order/ { proxy_pass http://order_service/; } location /api/user/ { proxy_pass http://user_service/; } }服務(wù)規(guī)模不大時(shí)這種輕量網(wǎng)關(guān)方案比引入全套微服務(wù)網(wǎng)關(guān)框架要簡(jiǎn)單得多。它沒有臃腫的依賴規(guī)則就是純文本配置文件Git 管理、版本回滾都方便。只有當(dāng)你需要復(fù)雜的服務(wù)發(fā)現(xiàn)、動(dòng)態(tài)路由、熔斷、灰度發(fā)布時(shí)才應(yīng)該考慮更重的網(wǎng)關(guān)方案。3.4 Nginx、Apache、HAProxy 怎么選這是一個(gè)被問(wèn)爛了但又必須回答的問(wèn)題。我習(xí)慣用下面這張表總結(jié)對(duì)比項(xiàng)NginxApacheHAProxyEnvoy并發(fā)模型事件驅(qū)動(dòng)異步非阻塞進(jìn)程/線程模型事件驅(qū)動(dòng)事件驅(qū)動(dòng)靜態(tài)資源處理強(qiáng)一般不支持一般七層路由能力強(qiáng)強(qiáng)較弱強(qiáng)四層轉(zhuǎn)發(fā)TCP/UDP支持stream較弱非常強(qiáng)支持動(dòng)態(tài)配置需 reload需 reload需 reload支持 API 熱更新上手成本低低中高生態(tài)成熟度極高高高快速增長(zhǎng)簡(jiǎn)單說(shuō)需要同時(shí)處理靜態(tài)文件和動(dòng)態(tài)反代首選 Nginx純四層高并發(fā)流量轉(zhuǎn)發(fā)HAProxy 更專業(yè)在 Kubernetes 里做 Ingress Controller常見的 nginx-ingress 或 Envoy 都比較合適需要?jiǎng)討B(tài)路由和灰度發(fā)布Envoy 這類云原生網(wǎng)關(guān)更應(yīng)景。我這幾年線上項(xiàng)目基本都跑 Nginx只有在一臺(tái)機(jī)器上要對(duì)大量 TCP 端口做負(fù)載均衡時(shí)才考慮 HAProxy。Nginx 的最大優(yōu)勢(shì)是“中庸且全面”大部分場(chǎng)景一個(gè)它就能全包。4. 親手搭一套安裝、配置與實(shí)操細(xì)節(jié)4.1 安裝 Nginx從包管理到 Docker 到源碼不同的部署環(huán)境安裝方式不一樣。我這里列三種最常用的。包管理器安裝是最快的# Debian / Ubuntu apt update apt install -y nginx # CentOS / RedHat / Rocky yum install -y nginx # 或者 dnf install -y nginx包管理器安裝的好處是省事版本隨系統(tǒng)源走能用 systemd 管理。缺點(diǎn)是版本通常偏舊可能缺少新特性比如 HTTP/3 模塊。如果是內(nèi)網(wǎng)環(huán)境沒有外網(wǎng)訪問(wèn)就需要離線安裝。思路是找一臺(tái)同系統(tǒng)版本的機(jī)器可以聯(lián)網(wǎng)裝好 Nginx 和依賴用 rpm 或 deb 包導(dǎo)出再拷貝。CentOS 下# 能聯(lián)網(wǎng)的機(jī)器上 mkdir nginx-packages yum install --downloadonly --downloaddirnginx-packages nginx然后把整個(gè)目錄拷到內(nèi)網(wǎng)機(jī)器rpm -ivh nginx-packages/*.rpm即可。這里容易踩依賴坑Nginx 依賴的 pcre、openssl、zlib 可能也被裝到下載目錄里了拷過(guò)去一起裝通常沒問(wèn)題。離線安裝前最好先確認(rèn)系統(tǒng)版本完全一致我遇到過(guò)開發(fā)機(jī)是 CentOS 7.9、生產(chǎn)機(jī)是 Rocky 9rpm 包互相不兼容白折騰一小時(shí)。Docker 方式適合容器化部署docker pull nginx:stable docker run -d --name my-nginx \ -p 80:80 -p 443:443 \ -v /data/www:/usr/share/nginx/html \ -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ nginx:stableDocker 鏡像的好處是環(huán)境隔離升級(jí)和回滾都方便。但要注意容器里的 Nginx 配置文件是短路徑和宿主機(jī)不一定完全對(duì)應(yīng)日志最好也掛載出來(lái)不然docker logs看起來(lái)費(fèi)勁。另外如果想用 QUIC/HTTP/3得先確認(rèn)鏡像里的 Nginx 是否帶http_v3_module。源碼編譯安裝是自由度最高的方式也是平滑升級(jí)的前提wget https://nginx.org/download/nginx-1.26.2.tar.gz tar xzf nginx-1.26.2.tar.gz cd nginx-1.26.2 ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --with-stream \ --with-stream_ssl_module make make install--prefix決定安裝路徑后續(xù)升級(jí)、回滾都要依賴這個(gè)路徑一定要記住。--with-http_ssl_module是 HTTPS 必需的--with-stream是四層 TCP/UDP 轉(zhuǎn)發(fā)用的--with-http_v3_module是為了 HTTP/3。裝完之后先驗(yàn)證版本nginx -v nginx -Vnginx -V會(huì)輸出完整的編譯參數(shù)這個(gè)信息在升級(jí)時(shí)必須保留后面講平滑升級(jí)時(shí)會(huì)用到。4.2 配置文件結(jié)構(gòu)與關(guān)鍵參數(shù)Nginx 主配置文件默認(rèn)在/etc/nginx/nginx.conf源碼安裝則在--prefix下的conf/nginx.conf。核心結(jié)構(gòu)如下user nginx; worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 4096; } http { include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; } }幾個(gè)關(guān)鍵參數(shù)worker_processes auto通常設(shè)置為 CPU 核數(shù)Nginx 每個(gè) worker 進(jìn)程可以充分利用一個(gè)核。設(shè)多了反而引起上下文切換開銷。worker_connections 4096每個(gè) worker 進(jìn)程最多同時(shí)處理的連接數(shù)。最大并發(fā)連接數(shù)約等于worker_processes * worker_connections。如果這個(gè)值太小高并發(fā)時(shí)日志里會(huì)出現(xiàn) worker_connections are not enough。worker_rlimit_nofile單個(gè)進(jìn)程可以打開的最大文件數(shù)。因?yàn)槊恳粭l TCP 連接都對(duì)應(yīng)一個(gè)文件描述符這個(gè)值太小并發(fā)一高就報(bào) too many open files。include把主配置拆分成多個(gè)子配置文件方便管理。推薦每個(gè)站點(diǎn)或每個(gè)應(yīng)用單獨(dú)建一個(gè) conf 文件放在/etc/nginx/conf.d/下而不是全部堆在一個(gè)文件里。4.3 反向代理 負(fù)載均衡完整示例我把兩個(gè)功能合在一起給一份可以直接用的完整配置upstream app_backend { least_conn; server 10.0.0.10:8080 max_fails2 fail_timeout30s; server 10.0.0.11:8080 max_fails2 fail_timeout30s; } server { listen 80; server_name app.example.com; access_log /var/log/nginx/app.access.log; error_log /var/log/nginx/app.error.log; 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; proxy_connect_timeout 5s; proxy_read_timeout 30s; } location /static/ { alias /data/static/; expires 7d; } }這里把/static/請(qǐng)求直接交給 Nginx 讀文件系統(tǒng)其他請(qǐng)求全部負(fù)載均衡到后端。alias和root的區(qū)別值得單獨(dú)強(qiáng)調(diào)root會(huì)把 location 的路徑拼接在根目錄后面比如root /data/static; location /static/時(shí)請(qǐng)求/static/a.png會(huì)找/data/static/static/a.pngalias /data/static/時(shí)則找/data/static/a.png。用錯(cuò)這兩個(gè)指令靜態(tài)資源會(huì)全部 404這是新手最容易踩的坑之一。proxy_connect_timeout 5s是 Nginx 與后端建立 TCP 連接的超時(shí)時(shí)間設(shè)太短后端偶爾忙一下就會(huì) 502。proxy_read_timeout 30s是讀取后端響應(yīng)的超時(shí)時(shí)間如果后端有長(zhǎng)任務(wù)接口比如導(dǎo)出報(bào)表要跑一分鐘這里得對(duì)應(yīng)調(diào)大。4.4 HTTPS 證書配置實(shí)戰(zhàn)這里以已有證書文件為前提不展開怎么申請(qǐng)證書直接說(shuō)配置server { listen 443 ssl; http2 on; server_name www.example.com; ssl_certificate /etc/nginx/certs/www.example.com.pem; ssl_certificate_key /etc/nginx/certs/www.example.com.key; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; # 安全協(xié)議配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }ssl_session_cache shared:SSL:10m是很多團(tuán)隊(duì)容易漏的配置。TLS 握手是乘法運(yùn)算每次建立新連接都要重新跑一次代價(jià)很高。開了 session cache 之后同一臺(tái)客戶端一段時(shí)間內(nèi)可以復(fù)用會(huì)話密鑰握手開銷大幅下降。10m 大概能緩存幾萬(wàn)個(gè) session足夠日常使用。證書到期是一個(gè)高頻事故。建議配一個(gè) crontab 定時(shí)任務(wù)檢查證書有效期0 0 * * * /usr/bin/openssl x509 -enddate -noout -in /etc/nginx/certs/www.example.com.pem每個(gè)月跑一次看到快到期就提前換。我見過(guò)太多次證書過(guò)期導(dǎo)致線上全站報(bào)錯(cuò)起因就是大家都不記得證書是去年哪一天配的。4.5 前端構(gòu)建產(chǎn)物的部署與“401 驗(yàn)證身份”配置熱詞里的 “pnpm run build 的包怎么 nginx 啟動(dòng)”我再展開一下。前端項(xiàng)目構(gòu)建完得到 dist 目錄部署到服務(wù)器/data/wwwNginx 配置server { listen 80; server_name front.example.com; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }前端路由如果是 hash 模式try_files那行其實(shí)不加也能跑。但 history 模式必須加否則用戶點(diǎn)擊瀏覽器刷新、或直接訪問(wèn)二級(jí)路由時(shí)會(huì) 404。有些后臺(tái)頁(yè)面需要訪問(wèn)控制Nginx 自帶最簡(jiǎn)單的 HTTP Basic Auth配置兩個(gè)指令就行l(wèi)ocation /admin/ { alias /data/www/admin/; auth_basic Restricted Area; auth_basic_user_file /etc/nginx/.htpasswd; }然后用工具生成密碼文件htpasswd -c /etc/nginx/.htpasswd admin這個(gè)命令會(huì)提示輸入密碼生成的文件里存的是用戶名和密碼哈希。之后訪問(wèn)/admin/就會(huì)彈瀏覽器原生認(rèn)證框輸入賬號(hào)密碼才能訪問(wèn)。熱詞里提到的 “index.php 401 驗(yàn)證身份” 和這個(gè)類似如果后端是 PHP 并且接口返回 401要么是auth_basic導(dǎo)致的安全攔截要么是后端代碼里自己做了登錄校驗(yàn)。先用curl -I看 401 來(lái)自哪個(gè)響應(yīng)頭如果響應(yīng)頭里有WWW-Authenticate: Basic realm...基本就是 Nginx 的auth_basic在攔。5. 平滑升級(jí)、版本管理與踩坑記錄5.1 Nginx 平滑升級(jí)到底怎么操作為什么要單獨(dú)講平滑升級(jí)因?yàn)楹芏嗳酥苯佑冒芾韞um update nginx版本是升了但線上連接會(huì)被切斷運(yùn)氣不好配置項(xiàng)兼容性還會(huì)出問(wèn)題。尤其熱詞里提到“Nginx 升級(jí)到新版本要注意什么”這絕對(duì)是運(yùn)維里一個(gè)高風(fēng)險(xiǎn)動(dòng)作。源碼編譯的 Nginx 平滑升級(jí)標(biāo)準(zhǔn)步驟如下第一步查看當(dāng)前版本和編譯參數(shù)nginx -V把輸出的 configure arguments 完整記錄下來(lái)新版本編譯時(shí)參數(shù)要和原來(lái)一致不然升級(jí)完某些模塊就沒了。第二步下載新版源碼用同樣的 prefix 和 configure 參數(shù)編譯wget https://nginx.org/download/nginx-1.26.2.tar.gz tar xzf nginx-1.26.2.tar.gz cd nginx-1.26.2 ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --with-stream make注意這里只執(zhí)行make不要執(zhí)行make install否則會(huì)直接覆蓋老版本少了回滾機(jī)會(huì)。第三步備份舊二進(jìn)制并替換mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old cp objs/nginx /usr/local/nginx/sbin/nginx第四步向舊 master 進(jìn)程發(fā)送 USR2 信號(hào)啟動(dòng)新 masterkill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)這時(shí)新舊 master 會(huì)同時(shí)存在新 worker 進(jìn)程已接管配置。再發(fā)送 WINCH 信號(hào)給舊 master讓它優(yōu)雅關(guān)閉舊 workerkill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)升級(jí)完成后新版本就用原 pid 文件舊進(jìn)程信息在nginx.pid.oldbin里。確認(rèn)一切正常后可以把舊的二進(jìn)制文件收起來(lái)避免誤用。如果新版本有問(wèn)題想回滾mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.new mv /usr/local/nginx/sbin/nginx.old /usr/local/nginx/sbin/nginx kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid.oldbin) kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid)這套流程在熱詞里對(duì)應(yīng)“nginx 平滑升級(jí)指南”是線上升級(jí)的必修課。核心原則是能熱切換就不要冷重啟能回滾就不要硬著頭皮修。5.2 日常運(yùn)維reload、stop、日志切割日常管理命令不多但每一條都要記清楚命令作用nginx -t檢查配置語(yǔ)法并顯示測(cè)試結(jié)果nginx -s reload平滑重載配置不中斷服務(wù)nginx -s stop快速停止服務(wù)nginx -s quit優(yōu)雅停止處理完當(dāng)前請(qǐng)求再退出systemctl status nginx查看服務(wù)狀態(tài)和最近日志nginx -V查看編譯參數(shù)和版本W(wǎng)indows 下關(guān)閉 Nginx 是nginx.exe -s stop或nginx.exe -s quit對(duì)應(yīng)熱詞里的 “cmd 關(guān)閉 nginx”。注意 Windows 下 nginx 不推薦二進(jìn)制的生產(chǎn)部署但本地調(diào)試完全沒問(wèn)題。日志切割也經(jīng)常踩坑。Nginx 默認(rèn)把日志寫在一個(gè)文件里時(shí)間一長(zhǎng)就是幾十 GB。標(biāo)準(zhǔn)做法是用 logrotate/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 640 nginx adm sharedscripts postrotate [ -f /var/run/nginx.pid ] kill -USR1 cat /var/run/nginx.pid endscript }關(guān)鍵是postrotate里的kill -USR1這個(gè)信號(hào)會(huì)讓 Nginx 重新打開日志文件。如果不發(fā)這個(gè)信號(hào)你就算把舊日志改名了Nginx 還在往舊文件里寫等于沒切割。5.3 上線前我必做的“三查”配置上線前我給自己定了一個(gè)固定流程到今天還在用第一查nginx -t。這步不用多說(shuō)語(yǔ)法錯(cuò)誤必須在這層攔掉。但我見過(guò)有人配完忘了跑直接 reload結(jié)果 reload 失敗線上老配置還在跑新配置根本沒生效排查半天。第二查檢查權(quán)限和端口。Nginx 報(bào)Permission denied時(shí)多半是靜態(tài)文件目錄沒有讀權(quán)限或者 SELinux 沒放行。檢查端口占用ss -lntp | grep :80如果端口被其他進(jìn)程占了Nginx 會(huì)報(bào)bind() to 0.0.0.0:80 failed。第三查做一次真實(shí)請(qǐng)求驗(yàn)證。用 curl 測(cè)curl -I http://127.0.0.1:80 curl -I -k https://127.0.0.1:443加上-I只看響應(yīng)頭能快速判斷 HTTP 狀態(tài)碼是不是預(yù)期。如果 502去看 Nginx error.log 和后端服務(wù)狀態(tài)如果 403去看目錄權(quán)限和 index 文件如果 404先確認(rèn) root/alias 路徑對(duì)不對(duì)。6. 常見問(wèn)題與排查技巧6.1 Nginx 狀態(tài)碼速查表排查問(wèn)題時(shí)狀態(tài)碼是第一手信號(hào)。我把最常見的整理成一張表狀態(tài)碼含義常見原因301永久重定向http 跳 https 配置302臨時(shí)重定向登錄跳轉(zhuǎn)、鑒權(quán)跳轉(zhuǎn)304未修改命中本地緩存Nginx 返回 not modified400請(qǐng)求錯(cuò)誤請(qǐng)求頭格式異常、參數(shù)非法401未認(rèn)證auth_basic 或后端登錄校驗(yàn)失敗403禁止訪問(wèn)目錄權(quán)限不足、無(wú) index 文件、IP 被封404未找到root/alias 路徑錯(cuò)誤、SPA try_files 缺失405方法不允許靜態(tài)文件上 POST 請(qǐng)求未處理413請(qǐng)求體過(guò)大client_max_body_size 設(shè)置過(guò)小429請(qǐng)求過(guò)多l(xiāng)imit_req 限流觸發(fā)500服務(wù)器內(nèi)部錯(cuò)誤后端應(yīng)用異常502網(wǎng)關(guān)錯(cuò)誤后端服務(wù)未啟動(dòng)、端口不通、超時(shí)503服務(wù)不可用后端無(wú)可用節(jié)點(diǎn)、正在維護(hù)504網(wǎng)關(guān)超時(shí)后端處理超時(shí)proxy_read_timeout 太小6.2 高頻故障排查實(shí)錄502 Bad Gateway 是最常見的故障。排查順序systemctl status nginx確認(rèn) Nginx 本身活著。檢查后端服務(wù)是否啟動(dòng)ss -lntp | grep 8080。在后端機(jī)器上直接curl http://127.0.0.1:8080/health確認(rèn)后端本身能不能訪問(wèn)。如果后端正常但 Nginx 還是 502看 Nginx error.logtail -f /var/log/nginx/error.log常見報(bào)錯(cuò)是connect() failed (111: Connection refused)或connect() failed (110: Connection timed out)。前者說(shuō)明端口沒開或者 IP 不通后者說(shuō)明防火墻或網(wǎng)絡(luò)策略攔了。504 是另一個(gè)高發(fā)問(wèn)題。典型場(chǎng)景是后端接口本身要跑很久比如導(dǎo)出大量數(shù)據(jù)Nginx 默認(rèn)proxy_read_timeout 60s后端 60 秒內(nèi)沒返回Nginx 就主動(dòng)斷開返回 504。解決辦法是給長(zhǎng)任務(wù)接口單獨(dú)配一個(gè) location調(diào)大超時(shí)時(shí)間location /api/export/ { proxy_pass http://backend; proxy_read_timeout 300s; }403 往往不是權(quán)限問(wèn)題就是索引問(wèn)題。我遇到最多次的是兩種一是 root 目錄下的文件權(quán)限不是 nginx 用戶可讀二是autoindex off且目錄下沒有 index.html。先ls -l看權(quán)限再確認(rèn)目錄下有沒有 index 文件。另外 CentOS 系統(tǒng)還要注意 SELinuxgetenforce一下如果是 Enforcing試試setsebool -P httpd_can_network_connect 1放行。404 分清是 Nginx 的還是后端的。如果請(qǐng)求打到后端接口返回 404那是后端路由問(wèn)題如果是 Nginx 直接返回 404 的頁(yè)面多半是 root 或 alias 配錯(cuò)。判斷方法是看錯(cuò)誤日志tail -f /var/log/nginx/error.log日志里會(huì)寫清楚 “open() “/data/www/xxx” failed (2: No such file or directory)”告訴你 Nginx 實(shí)際在找哪個(gè)文件對(duì)照一下就明白了。413 Request Entity Too Large是老生常談。上傳文件時(shí)報(bào)這個(gè)錯(cuò)就是client_max_body_size沒配或太小client_max_body_size 20m;放在http、server或location塊里都行l(wèi)ocation里的優(yōu)先級(jí)最高。6.3 我踩過(guò)的一些坑先說(shuō)一個(gè)關(guān)于if的大坑。Nginx 的if指令被官方稱為 “evil”因?yàn)樗趌ocation里和proxy_pass同時(shí)出現(xiàn)時(shí)行為很容易不符合直覺。不是不能用而是只用它做改路徑或 return不要在里面寫復(fù)雜邏輯。有一次我在if里設(shè)置變量再proxy_pass結(jié)果每次 reload 都報(bào)錯(cuò)最后改成用map做變量映射才解決。再說(shuō) reload 不是“不會(huì)斷”。nginx -s reload理論上不會(huì)中斷現(xiàn)有連接但如果你的配置里改動(dòng)了 upstream 地址正在處理的長(zhǎng)連接會(huì)被切掉。所以我建議不要在業(yè)務(wù)高峰期做這種改動(dòng)盡量安排在凌晨低峰期。還有一個(gè)大家都容易忽略的點(diǎn)改了配置一定要先nginx -t再 reload。這個(gè)習(xí)慣我強(qiáng)調(diào)過(guò)無(wú)數(shù)次但每個(gè)月依然能遇到?jīng)]跑測(cè)試直接 reload 導(dǎo)致線上配置狀態(tài)混亂的情況。反正就一條命令多敲一下不虧。最后是日志。排查問(wèn)題第一件事永遠(yuǎn)是看日志很多人習(xí)慣先猜。Nginx 的 access_log 和 error_log 分開看error.log記錄錯(cuò)誤比如連接失敗、權(quán)限不足、配置文件報(bào)錯(cuò)。access.log記錄每次請(qǐng)求狀態(tài)碼、耗時(shí)、來(lái)源 IP 都在里面。分析慢請(qǐng)求時(shí)在 Nginx 配置里加上$request_time字段就能從 access log 里看出哪些接口響應(yīng)慢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 urt$upstream_response_time;這里$request_time是 Nginx 處理請(qǐng)求的總時(shí)間$upstream_response_time是后端響應(yīng)時(shí)間。兩者差值大說(shuō)明瓶頸在 Nginx 到后端這一段或網(wǎng)絡(luò)兩者都大基本就是后端接口本身慢。7. 最后分享一點(diǎn)個(gè)人心得Nginx 這個(gè)工具文檔寫得很全配置語(yǔ)法也不算難真正的難度在于你對(duì)自己系統(tǒng)的請(qǐng)求鏈路有沒有想清楚。我在生產(chǎn)環(huán)境折騰 Nginx 這幾年最大的體會(huì)是配置之前先畫清楚“用戶請(qǐng)求從哪進(jìn)來(lái)、經(jīng)過(guò)哪些層、最后到哪臺(tái)機(jī)器哪個(gè)接口”再動(dòng)手寫配置文件基本不會(huì)出大錯(cuò)。每次上線前我會(huì)強(qiáng)制自己做三件事備份當(dāng)前配置、執(zhí)行nginx -t、把 access log 打開看兩分鐘真實(shí)請(qǐng)求。這套流程看起來(lái)土但比任何花哨的監(jiān)控面板都好使。另外雖然我前面講了不少進(jìn)階功能但如果不是業(yè)務(wù)需要不要為了炫技硬加功能。配置每多一層故障面就大一分保持“夠用且可維護(hù)”才是最好的狀態(tài)。如果后面有機(jī)會(huì)我打算再單獨(dú)寫一篇關(guān)于 Nginx 與 keepalived 高可用、以及 HTTP/3 QUIC 實(shí)戰(zhàn)部署的內(nèi)容。你們?cè)谂渲?Nginx 時(shí)遇到過(guò)最詭異的問(wèn)題是什么歡迎留言交流說(shuō)不定下一篇文章就是專門為你排坑寫的。