线上 Nginx 配置这事,看着简单------官方文档给的示例改改就能跑。但跑起来和跑得好是两码事。很多人装完 Nginx 就拿默认配置上线,worker 进程开一个,连接数不调,gzip 不开,静态文件不缓存,等流量上来了才发现各种 502 和 504。这篇就把生产环境 Nginx 的调优经验从头到尾过一遍。
先说版本。2026 年 7 月,Nginx 稳定版到了 1.30.4(mainline 1.31.3)。1.30 这个版本带来了不少实用特性:Early Hints(103 状态码)、到后端的 HTTP/2 代理、upstream sticky session、Multipath TCP 支持,还有一个细节改动------默认代理 HTTP 版本改成了 1.1 并开启 keep-alive,以前要手动配的现在默认就有了。但线上别用 mainline,稳定版更靠谱。
worker 进程和连接数调优
Nginx 的进程模型是 master + worker。master 负责管理,worker 负责干活。默认配置里 worker_processes 是 1,也就是只有一个 worker 在处理请求,这在多核机器上纯属浪费。
ini
# 自动按 CPU 核心数启动 worker
worker_processes auto;
设成 auto 是最省心的做法,Nginx 会根据 CPU 核心数自动启动对应数量的 worker。如果你想手动控制,就填具体数字,一般跟 CPU 核心数一致。
worker_connections 是每个 worker 能同时处理的最大连接数。默认 1024,生产环境太保守了。
ini
events {
worker_connections 10240;
# 允许一个 worker 同时接受多个连接
multi_accept on;
}
10240 是个比较稳妥的值,根据机器内存调整。每个连接大概占 256 字节内存,10240 个连接也就 2.5MB,不用担心内存问题。multi_accept on 让 worker 一次性接受所有新连接,而不是一个一个来,高并发场景下能减少延迟。
最大连接数还有一个系统层面的限制:文件描述符。worker_processes * worker_connections 不能超过系统的 ulimit -n。生产环境一般把系统文件描述符设到 65535 或更高:
markdown
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
Nginx 配置里也加一行:
ini
worker_rlimit_nofile 65535;
keepalive 配置
keepalive 是提升性能的低成本手段。客户端跟 Nginx 之间保持连接复用,避免每次请求都重新握手。Nginx 1.30 默认到后端的代理已经用 HTTP/1.1 + keep-alive 了,但前端的 keepalive 还是要配。
ini
http {
# 客户端长连接超时,65 秒覆盖大部分浏览器默认超时
keepalive_timeout 65;
# 单个长连接最大请求数,设够大避免频繁重建连接
keepalive_requests 1000;
}
到后端的长连接也要配,不然每个请求都新建一条到后端的 TCP 连接,开销不小:
ini
http {
upstream backend {
server 127.0.0.1:8080;
# 到后端的长连接池大小
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
# 清除 Connection 头,让长连接生效
proxy_set_header Connection "";
}
}
}
keepalive 32 表示为每个 worker 预留 32 个到后端的长连接。后端应用如果不支持 keep-alive(比如某些 CGI 程序),这配置不生效,但现在的应用服务器基本都支持。
gzip 压缩别忘开
默认 Nginx 的 gzip 是关的,这太常见了。一个 200KB 的 JSON 接口,开了 gzip 能压到 30KB 左右,带宽直接省 85%。线上不开 gzip 等于白扔带宽钱。
bash
http {
gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
application/rss+xml application/atom+xml image/svg+xml;
gzip_vary on;
gzip_proxied any;
}
几个关键参数:gzip_min_length 1k 太小的响应不压缩,压缩开销大于收益。gzip_comp_level 5 是性价比最高的级别,6 以上压缩率提升不明显但 CPU 开销猛增。gzip_types 要把 JSON 和 JS 加上,默认只压缩 text/html。gzip_vary on 加上 Vary: Accept-Encoding 响应头,让 CDN 能正确缓存压缩和非压缩版本。
静态文件缓存策略
静态文件(CSS、JS、图片)是缓存的重点对象。配好了能大幅减少回源请求。
ini
server {
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
# 日志里不记录静态文件请求,减少 IO
access_log off;
}
}
expires 30d 设置浏览器缓存 30 天。带 hash 的文件名可以用 immutable,告诉浏览器这个文件永远不会变,连条件请求都省了。没带 hash 的文件名别用 immutable,改了文件浏览器不更新就尴尬了。
服务端缓存可以配合 open_file_cache,缓存文件描述符和元数据,减少磁盘 IO:
ini
http {
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
反向代理和负载均衡
反向代理是 Nginx 最常用的功能,负载均衡策略直接决定了后端的压力分布。
ini
upstream backend {
# 加权轮询(默认)
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1;
server 10.0.0.3:8080 backup;
# 1.30 新增的 sticky session
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
默认是加权轮询,weight 大的服务器分到更多请求。backup 表示备用服务器,前面的全挂了才用。
如果你的后端是有状态的(比如 session 存在本地),用 sticky session 让同一个客户端的请求始终打到同一台后端。Nginx 1.30 原生支持了 sticky session,以前只有 Nginx Plus 才有。
最小连接数策略适合后端处理能力不均匀的场景:
ini
upstream backend {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
ip_hash 适合需要会话保持但不想用 cookie 的场景,按客户端 IP 哈希分配,但同一 NAT 出口下的所有用户会打到同一台后端,移动端场景慎用。
SSL/TLS 优化
HTTPS 性能损耗主要在握手阶段。优化好了,握手开销能降到几乎无感。
ruby
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 只用 TLS 1.2 和 1.3,干掉不安全的旧版本
ssl_protocols TLSv1.2 TLSv1.3;
# 优先用服务端密码套件,防止客户端选弱算法
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# session 缓存,减少握手次数
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# TLS 1.3 session ticket
ssl_session_tickets on;
# OCSP stapling,客户端不用自己去查证书状态
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
}
TLS 1.3 的握手只需要一次往返,比 1.2 少一轮,延迟能降几十毫秒。ssl_session_cache 让同一个客户端的后续连接复用握手结果,shared:SSL:10m 大约能缓存 4 万个 session。OCSP stapling 把证书状态查询放到服务端,省去客户端额外请求,也能避免某些网络环境下 OCSP 查询失败导致连接变慢。
安全头不能少
安全响应头是低成本高收益的防护手段,配几行就能挡掉一批常见攻击:
ini
server {
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Content-Security-Policy "default-src 'self'";
}
X-Frame-Options 防点击劫持,X-Content-Type-Options 防 MIME 嗅探,HSTS 强制浏览器后续都走 HTTPS。CSP 策略按你的实际需求调,default-src 'self' 是最严格的,可能需要放开一些外部资源域名。
502 和 504 怎么排查
这俩是线上最高频的错误,排查思路要清晰。
502 Bad Gateway 是 Nginx 连不上后端。先看后端进程是不是挂了,ps aux | grep your_app 确认进程在不在。进程在的话看端口,netstat -tlnp | grep 8080 确认后端在监听。都正常就查 Nginx 的 upstream 配置,IP 和端口对不对。还有一种情况是后端处理太慢,Nginx 等不及了,这时候需要调超时:
ini
location / {
proxy_pass http://backend;
# 后端响应超时时间
proxy_read_timeout 60s;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
}
proxy_connect_timeout 是连接后端的超时,一般 5 秒够了------连不上说明后端有问题。proxy_read_timeout 是等待后端响应的超时,如果后端有慢接口,适当调大,但别设成 300s 之类的离谱值。
504 Gateway Timeout 是连上了后端但后端在规定时间内没返回。这个基本就是后端的问题了------接口太慢、数据库查询卡死、死循环。查后端日志,看慢查询,该加索引加索引,该加缓存加缓存。
Nginx 层面能做的是给后端加一层降级保护,避免慢请求拖垮整个连接池:
ini
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
}
一个后端超时了自动切到下一个,最多试两次,总超时 10 秒。这样单个后端节点出问题不会让用户干等。
一份可用的生产配置骨架
把上面的配置整合一下,给一份能直接拿来改的生产骨架:
ini
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 10240;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
error_log /var/log/nginx/error.log warn;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
gzip_vary on;
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Strict-Transport-Security "max-age=31536000";
location ~* .(js|css|png|jpg|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
}
这份配置覆盖了进程调优、连接复用、压缩、缓存、HTTPS、安全头、反向代理这些核心项。拿去改改 IP 和域名就能用。上线前记得跑一下 nginx -t 验证配置语法,改完用 nginx -s reload 平滑加载,不会断已有连接。
Nginx 调优不是一次性的事,流量涨了、后端变了、新版本出了功能了,都得回头看看配置是不是还合理。养成习惯,定期 review 一次,比出事了再救火强太多。