Nginx 典型配置错误复盘
通查思路
# 1. 验证语法(通过不等于没问题)
nginx -t
# 2. 查看 error_log
tail -f /var/log/nginx/error.log
# 3. 检查进程和连接
ps aux | grep nginx
ss -tlnp | grep nginx
netstat -an | awk '/:80\s/ {s[$NF]++} END {for(k in s) print k, s[k]}'
# 4. reload 而非 restart(不停机)
nginx -s reload
踩坑点一:location 匹配优先级混乱
现象: /api/users 返回 404 或走到了 / 的处理逻辑。
根因: location 按优先级匹配,不是按配置顺序。
| 优先级 | 语法 | 说明 |
|---|---|---|
| 1️⃣ | = /path |
精确匹配,最高优先级 |
| 2️⃣ | ^~ /path |
前缀匹配,找到后不再做正则 |
| 3️⃣ | ~ / ~* |
正则匹配,按配置顺序命中即停 |
| 4️⃣ | /path |
普通前缀,最长匹配原则 |
关键原则:
- 正则 location(
~/~*)会覆盖普通前缀匹配 - 不想被正则覆盖的前缀须加
^~ location /是最后兜底,写在最前也不影响优先级
server {
location = / { # 精确匹配首页
root /usr/share/nginx/html;
index index.html;
}
location ^~ /static/ { # 前缀 + 禁止正则覆盖
root /data/www;
expires 30d;
}
location /api/ { # API 代理
proxy_pass http://127.0.0.1:8080;
}
location ~* \\.(js|css|png)$ { # 正则匹配静态资源
root /data/www;
expires 7d;
}
location / { # 兜底
root /usr/share/nginx/html;
}
}
验证: curl -I http://localhost/api/users 应为 200。
踩坑点二:proxy_pass 末尾带不带 /
| 写法 | 原始请求 | 代理后路径 |
|---|---|---|
proxy_pass http://backend; |
/api/users |
/api/users(保留全路径) |
proxy_pass http://backend/; |
/api/users |
/users(匹配前缀被替换) |
关键区别: 带 / 时 Nginx 会将 location 匹配的前缀部分移除。
与此类似,alias 的末尾斜杠同样关键——alias 路径末尾带 / 与不带效果不同,且 alias 不支持 / 作为根路径。优先用 root 替代 alias 以降低踩坑概率。
| 指令 | 建议 |
|---|---|
root |
大多数场景用 root,路径 = root + URI |
alias |
仅当需要将 URI 映射到不同目录时使用,且注意末尾斜杠与 root 行为不同 |
location /download/ {
alias /data/files/; # /download/1.zip → /data/files/1.zip
}
# 对比 root 写法
location /download/ {
root /data; # /download/1.zip → /data/download/1.zip
}
# 后端路径一致 → 不带 /
location /api/ { proxy_pass http://127.0.0.1:8080; }
# 需要路径替换 → 带 /
location /api/ { proxy_pass http://127.0.0.1:8080/; }
# 复杂映射 → rewrite + proxy_pass
location /app/v1/ {
rewrite ^/app/v1/(.*) /$1 break;
proxy_pass http://127.0.0.1:8080;
}
踩坑点三:try_files 死循环
现象: 浏览器报 "Too many redirects" 或 500。
根因: try_files 的 fallback URI 又回到了原来的 location。
# ❌ 错误:try_files 最后 fallback 到自身
location / { try_files $uri $uri/ /index.html; }
location = /index.html { rewrite ^ / permanent; } # 死循环!
# ✅ 正确
location / {
try_files $uri $uri/ @backend;
}
location @backend { proxy_pass http://127.0.0.1:8080; }
# ✅ PHP FastCGI 场景
location / {
try_files $uri $uri/ /index.php?$query_string;
}
踩坑点四:upstream keepalive 配置不足
现象: 大量 TIME_WAIT,后端报 "too many connections",但 CPU/内存都不高。
根因: 每个请求都新建 TCP 连接到 upstream,未使用长连接。
upstream backend {
server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
keepalive 32; # 空闲长连接数(worker_connections 的 10%~20%)
keepalive_requests 1000;
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须开启
proxy_set_header Connection "";
}
}
踩坑点五:client_max_body_size 未设置
现象: 上传文件返回 413 Request Entity Too Large。
根因: 默认 1MB,Nginx 在读取请求体阶段直接拒绝,不向后端转发。
http {
client_max_body_size 10m; # 全局下限
server {
location /upload/ {
client_max_body_size 100m; # 按业务放宽
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_pass http://upload-backend;
}
}
}
踩坑点六:gzip 压缩配置不当
现象: 带宽高但页面加载慢,响应头无 Content-Encoding: gzip。
根因: 默认只压缩 text/html。
http {
gzip on;
gzip_comp_level 5; # 1(最快)~9(最慢),建议 4~5
gzip_vary on; # 代理缓存识别
gzip_min_length 1024; # <1KB 不压缩
gzip_types text/plain text/css text/javascript
application/json application/javascript
application/xml image/svg+xml;
# 图片/视频/音频不要压缩(本身就是压缩格式)
}
踩坑点七:SSL/TLS 配置不完整
现象: 浏览器报"不是私密连接"。
根因: 证书链不完整、使用 TLS 1.0/1.1、不安全加密套件。
server {
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...';
ssl_prefer_server_ciphers on;
ssl_stapling on; # OCSP Stapling
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
验证: openssl s_client -connect example.com:443 检查 Certificate chain。
踩坑点八:worker_processes 与文件描述符
现象: worker_connections 设了 65535,但还是 "too many connections"。
根因: 系统级 ulimit 没同步调整。
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
# 系统配置
ulimit -n 65535
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
与 linux-kernel-tuning-production 中的 fs.file-max 配合使用。
踩坑点九:日志配置不当导致磁盘爆满
现象: 磁盘 100%,access.log 几十 GB,logrotate 没生效。
# 生产配置
access_log /var/log/nginx/access.log main buffer=16k flush=2m;
error_log /var/log/nginx/error.log warn; # 勿用 debug
# 静态资源可关闭日志
location /static/ { access_log off; }
location /health { access_log off; return 200 "OK"; }
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
create 0640 nginx nginx
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
⚠️ 不要
rm正在被写入的日志文件,用truncate -s 0。
踩坑点十:隐藏版本号未生效
http {
server_tokens off; # 隐藏 error 页面版本号
}
注意: server_tokens off 不隐藏 Server 响应头,只去掉了版本号。需要完全隐藏需第三方模块(如 OpenResty)。
踩坑点:配置与系统限制遗漏
坑 A:include 文件顺序没管好
现象: 相同的配置写在多个 include 文件中,调试时不知道哪个生效。
# /etc/nginx/nginx.conf
http {
include /etc/nginx/conf.d/*.conf; # 按字母顺序加载
include /etc/nginx/sites-enabled/*; # 另一套路径
}
关键: Nginx 按字母顺序加载 conf.d/*.conf。a-xxx.conf 先于 b-xxx.conf 加载。如果两个文件对同一参数有冲突定义,后加载的覆盖先加载的。实践中应确保:
- 每个 server block 只有一个文件,不跨文件碎片化配置
server_names_hash_bucket_size等全局参数放在nginx.conf主体中,不在conf.d里重复- 使用
include将长配置按功能拆分,但保证同一 server 的配置不分散
坑 B:pid 文件权限未检查
# 现象:reload 时报 Cannot open pid file /var/run/nginx.pid: Permission denied
# 根因:/var/run/ 目录重启被清空,或容器中 pid 目录权限不对
mkdir -p /var/run/nginx && chown nginx:nginx /var/run/nginx
踩坑点:性能调优三大误区
误区一:sendfile / tcp_nopush / tcp_nodelay 三选一
这三个配置不互斥,而是配合使用:
http {
sendfile on; # 零拷贝,从磁盘到网卡不走用户态
tcp_nopush on; # 等到数据包足够大再发,配合 sendfile 减少小包
tcp_nodelay on; # 禁用 Nagle 算法,小包立即发送(对交互式 API 重要)
}
策略: 对大文件下载(静态资源)用 sendfile on + tcp_nopush on 提升吞吐;对 API 代理用 tcp_nodelay on 降低延迟。三者可以同时开启——tcp_nopush 确保缓冲区满再发,tcp_nodelay 对个别小包突破此规则立即发送,互不矛盾。
误区二:proxy_buffer 完全没配
现象: 后端返回大量数据时,Nginx 等待 buffer 写满才转发,客户端响应慢。或 buffer 太小导致 Nginx 频繁写磁盘临时文件。
http {
proxy_buffering on;
proxy_buffer_size 4k; # 响应头缓冲区
proxy_buffers 8 4k; # 8 个 4KB 响应体缓冲区
proxy_busy_buffers_size 8k; # 转发到客户端前的 busy buffer
# 后端响应慢时加大缓冲区
proxy_buffers 16 8k;
proxy_busy_buffers_size 16k;
}
关键权衡:
- buffer 太小 → 磁盘临时文件写入频繁,IO 压力大
- buffer 太大 → 占用过多内存,海量连接下内存耗尽
- 推荐起点:
proxy_buffers 8 4k,按接口响应体大小调整
误区三:open_file_cache 一行都没配
现象: 静态文件高并发下 Nginx 反复打开/关闭文件描述符,浪费 IO。
http {
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 60s; # 每 60 秒检查缓存有效性
open_file_cache_min_uses 2; # 文件被访问 2 次才缓存
open_file_cache_errors on; # 缓存文件不存在的信息,减少 stat 调用
}
效果: 缓存文件句柄、大小、最后修改时间等元数据。静态资源为主的站点能显著减少 open() 和 stat() 系统调用。
踩坑点:if 指令的坑
现象: if 内配置的 proxy_pass 或 rewrite 行为不符合预期。
根因: Nginx 的 if 是 rewrite 模块的指令,不是通用条件分支。在 location 块内使用 if 可能导致不可预料的上下文切换。
# ❌ 错误:if 中配置 proxy_pass 行为异常
location /api/ {
if ($http_user_agent ~* "curl") {
proxy_pass http://debug-backend; # 可能不生效
}
proxy_pass http://prod-backend;
}
# ✅ 正确:用 map 处理条件路由(推荐)
map $http_user_agent $backend {
~*curl debug-backend;
default prod-backend;
}
server {
location /api/ {
proxy_pass http://$backend;
}
}
# ✅ 正确:if 仅做 rewrite + return
if ($scheme = http) {
return 301 https://$host$request_uri;
}
if (-f $document_root/off.html) {
return 503;
}
原则: if 只适合做 rewrite 和 return。不要用 if 做 proxy_pass 或 set(某些场景可以但极易出错)。用 map、try_files、limit_except 等替代。
踩坑点十一:真实 IP 配置不当(set_real_ip_from)
现象: 日志里看到的 IP 全是内网代理地址(10.x / 172.x / 192.168.x),限流和封禁都失效。
根因: Nginx 前面有 CDN / 负载均衡时,$remote_addr 是代理 IP 而非用户真实 IP。
# ✅ 正确:只允许可信代理网段
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
陷阱: 不要写 set_real_ip_from 0.0.0.0/0。攻击者可伪造 X-Forwarded-For 让限流、封禁、日志分析全部失真。
上线前检查清单参见 nginx-pre-launch-checklist。
踩坑点十二:核心接口未做限流
现象: 登录/验证码/上传接口被脚本刷,后端过载、短信费用飙高、磁盘打满。
根因: 所有接口共用一套限流策略,或完全没有限流。
# ✅ 按场景拆分
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://backend;
}
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
常见错误: 对静态资源也做严格限流 → 页面加载异常。限流应优先放在登录、验证码、上传、搜索、下单接口。
踩坑点十三:静态资源缓存策略不当
现象: 不发版时每次请求都打到后端(未配缓存),或者发版后用户拿到旧资源(缓存太猛)。
根因: 缓存策略与前端构建方式不匹配。
# ✅ 文件名带 hash → 长缓存
location ~* \.(css|js|png|jpg|jpeg|gif|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# ✅ 文件名不带 hash → 缓存谨慎
location ~* \.(css|js)$ {
expires 7d;
add_header Cache-Control "public";
}
关键原则: HTML 入口文件不建议强缓存。文件名带 hash 可以安心长缓存。用户上传文件需结合权限和防盗链处理。
502/504/Connection Reset 故障排查流程
502/504/Connection Reset
├── 1. upstream 是否存活(curl 本地后端端口)
├── 2. 网络连通性(telnet/ping/iptables)
├── 3. 连接数和超时配置(keepalive/proxy_timeout)
├── 4. upstream 进程状态(OOM/容器重启/kubectl)
├── 5. error_log 错误信息
│ ├── "connect() failed" → 502
│ ├── "Connection refused" → 502
│ ├── "upstream timed out" → 504
│ ├── "no live upstreams" → 502
│ ├── "Connection reset by peer" → Connection Reset
│ └── "recv() failed" → Connection Reset
└── 6. upstream 配置是否正确
详见 nginx-502-504-connection-reset-guide 深度排查指南(四段链路法、curl 时间分析、按接口拆分超时、Connection Reset 四步排查)。
生产变更 Checklist
# 变更前
cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d%H%M%S)
nginx -t
# 变更中
cp /path/to/new/conf /etc/nginx/nginx.conf
nginx -t && nginx -s reload
tail -f /var/log/nginx/error.log
# 变更后
for i in 1 2 3 4 5; do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost/
sleep 60
done
如果 Nginx 需要高可用保障,参见 keepalived-nginx-ha(Keepalived + Nginx 主备切换 + VIP 漂移方案)。
新服务上线前 7 项检查清单
| # | 检查项 | 不检查的后果 | 详细指南 |
|---|---|---|---|
| 1 | 真实 IP(set_real_ip_from) | 限流/封禁/日志分析失真 | 踩坑点十一 |
| 2 | 核心接口限流(limit_req_zone) | 接口被刷,后端过载 | 踩坑点十二 |
| 3 | 超时参数按接口拆分 | 慢请求堆积,502/504 难排查 | 踩坑点二/502 流程 |
| 4 | 上传大小(client_max_body_size) | 大文件打满磁盘 | 踩坑点五 |
| 5 | 静态资源缓存策略 | 重复请求或发版后缓存旧文件 | 踩坑点十三 |
| 6 | 日志字段(upstream 响应时间) | 出事后不知道请求死在哪 | 踩坑点九 |
| 7 | 配置测试和回滚 | 配置一改全站故障 | 本 checklist |
完整的上线前检查清单与场景优先级参见 nginx-pre-launch-checklist。 完整的安全配置体系(限流/WAF/防DDoS/连接层防护)参见 nginx-security-config-guide。
SSE 与 WebSocket 实时推送的 Nginx 生产配置与踩坑注意事项参见 nginx-realtime-push-guide(代理缓冲/协议升级/会话保持/连接治理)。
关联页面
| 页面 | 关联点 |
|---|---|
| nginx-log-analysis-monitoring-guide | Nginx 日志分析与监控体系构建指南 — 自定义日志格式、性能分析技巧、GoAccess 可视化、 |
| keepalived-ha-hidden-pitfalls | Keepalived+Nginx 高可用架构 3 个隐藏坑位与生产级防护方案(脑裂/健康检查僵尸进程 |
| nginx-production-performance-optimization | 生产级 Nginx 性能优化 — OS 内核/Worker 进程/HTTP I/O/Upstream |
| nginx-load-balancing-strategy-guide | Nginx 负载均衡策略选择实战指南 — 加权轮询与 IP Hash 深度对比、混合策略最佳实践 |