Nginx配置不生效?我翻了10个案例总结的
说起来都是泪。上线前本地测得好好的,nginx -t 也通过了,结果切到生产环境——404、502、重定向循环、SSL报错,一个接一个。翻了10个生产环境的翻车案例之后,我总结出一套"三板斧"排查思路,今天毫无保留地分享出来。
一、先说几个经典翻车场景
看看你有没有中招的:
nginx.conf,各种验证没问题,早上起来发现所有用户访问的还是旧逻辑。排查了一圈,发现 nginx 进程跑的还是旧配置,根本没Reload。
http {} 块里写了一句 include /etc/nginx/sites-enabled/*.conf;,结果那个目录根本不存在,或者文件没给执行权限,Nginx 启动时直接无视了这行,整个 server 块都没加载进去。
location / {} 写了 return 301 https://$host$request_uri;,然后又在 location /api {} 里写了反向代理,结果访问 /api 也被外层捕获先做了重定向,API 直接炸了。
upstream timed out,但没人知道是超时时间太短。
nginx -t 也过了,但浏览器打开红叉子一堆,说是证书链不完整。原来只配了 ssl_certificate,没配 ssl_certificate_key 和中间证书。
以上场景有没有你似曾相识的?接下来,我按顺序给你讲清楚排查和修复方法。
二、5种最常见的配置错误
1. rewrite 规则写错——正则捕获取消了还不自知
这是最容易出幺蛾子的一条。很多人写 rewrite 时以为用 last 和 break 效果一样,其实差远了。
# 错误写法:break 不会停止后续 location 匹配,但会终止当前 rewrite 阶段 location /old-path { rewrite ^/old-path/(.*)$ /new-path/$1 break; # break 只中断当前阶段的处理,不会发起内部重定向 # 所以 /new-path 如果也有对应的 location,就直接匹配上了 } # 正确写法:用 last 让它重新走一遍 location 匹配流程 location /old-path { rewrite ^/old-path/(.*)$ /new-path/$1 last; }
break 和 last 的核心区别:
| 指令 | 作用 | 适用场景 |
|---|---|---|
break | 中断当前 rewrite 阶段的处理,继续在当前 location 内执行后续指令 | 内部重写 URL(不改变用户看到的 URL) |
last | 中断当前 rewrite 阶段,用新 URL 重新发起一次 location 匹配 | URL 真正跳转,需要在新 location 里继续处理 |
redirect | 返回 302 临时重定向 | 临时跳转,SEO 不传递权重 |
permanent | 返回 301 永久重定向 | 永久跳转,SEO 传递权重 |
2. 重定向循环——Too many redirects
这大概是线上最恐怖的事故之一——用户访问一个页面,浏览器直接报"重定向次数过多"。常见原因就那么几个:
# 翻车案例:HTTPS 强制跳转写成死循环 server { listen 80; server_name example.com; # 如果这个 server 块没有对应的 443 监听,或者 443 块也配了 80 重定向, # 就会形成 80 → 443 → 80 → 443 的死循环 return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; # 如果 ssl 块也写了这个 return,那 80→443 后又被送回 80 if ($scheme = http) { return 301 https://$host$request_uri; } ... }
正确的做法是 if 判断只在 需要的那一端 写,不要两边都写:
# 正确:80 强制跳 443,443 不再判断 server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; # 443 块干干净净,不做任何 http→https 判断 ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ... }
server 级别用 if 判断 $scheme 在某些 Nginx 版本里行为不一致。推荐在 listen 指令里直接用 default_server 和 ssl 参数来处理 HTTPS 逻辑。
3. 反向代理超时——后端一慢整站卡
默认超时时间很保守:连接超时 60s,发送超时 60s,读取超时 60s。生产环境里,后端 Java/Python 服务冷启动、GC 暂停、数据库慢查,都可能导致超时。
upstream backend { server 127.0.0.1:3000; server 127.0.0.1:3001; # keepalive 复用连接,减少连接建立开销 keepalive 32; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend; # 超时配置——按实际场景调整 proxy_connect_timeout 10s; # 建立连接的超时 proxy_send_timeout 30s; # 向后端发送请求的超时 proxy_read_timeout 60s; # 等待后端响应的超时 # buffer 配置——减轻后端压力 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 16k; # 把真实 IP 传给后端 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; # HTTP 1.1 + keepalive(配合 upstream 的 keepalive) proxy_http_version 1.1; proxy_set_header Connection ""; } }
4. SSL 证书配置——配了≠配对
SSL 配置翻车最常见的三种情况:证书链不完整、私钥和证书不匹配、HTTPS 协议和加密套件配置太老。
# 翻车写法:只配了证书,没配证书链 ssl_certificate /etc/nginx/ssl/server.crt; # 只有服务器证书 ssl_certificate_key /etc/nginx/ssl/server.key; # 缺少中间证书,浏览器需要自己拼接,如果拼接失败就报红叉 # 正确写法:证书链文件包含中间 CA ssl_certificate /etc/nginx/ssl/fullchain.pem; # 证书 + 中间 CA 打包在一起 ssl_certificate_key /etc/nginx/ssl/server.key; # 现代 SSL 配置——禁用 TLS 1.0/1.1,推荐 TLS 1.3 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; # OCSP Stapling——加速 SSL 握手 ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/ssl/trustchain.pem; # 根 CA 证书 # HSTS——强制 HTTPS(谨慎使用,一旦开启很难关闭) add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
openssl s_client -connect example.com:443 -servername example.com 看证书链是否完整。推荐用 SSL Labs 跑一遍评分,A+ 才算过关。
5. upstream 负载均衡——权重没生效、会话粘性丢
配了 upstream 但流量根本不按权重走,或者用户每次刷新页面就换了一台服务器(Session 没粘住)。
upstream backend { # 加权轮询——权重高的 server 承接更多流量 server 127.0.0.1:8080 weight=5; server 127.0.0.1:8081 weight=3; server 127.0.0.1:8082 weight=2 backup; # 仅在主节点全挂时才启用 # 失败重试次数(对 transient 错误有帮助) max_fails 3; fail_timeout 30s; } # 如果需要 Session 粘性(同一用户一直打到同一台后端): upstream backend { server 127.0.0.1:8080; server 127.0.0.1:8081; # ip_hash 按来源 IP 做哈希,同一 IP 永远打到同一台后端 ip_hash; }
6. CORS 头配置——OPTIONS 请求被吞了
前端跨域请求,后端接口正常,但浏览器就是报 CORS 错误。问题几乎总是出在两点:Access-Control-Allow-Origin 配错,或者 OPTIONS 预检请求没处理。
server { listen 80; server_name api.example.com; location / { # 处理 CORS 预检请求(OPTIONS) if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } # 实际请求加上 CORS 响应头 add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
always 参数保证了错误响应(比如 502)里也能带 CORS 头。不然后端挂了,返回 502 时没有 CORS 头,前端一样抓瞎。
三、排查步骤——三板斧走天下
第一板斧:nginx -t 先跑
任何配置修改后,第一件事永远是跑语法检查:
$ sudo nginx -t nginx: [emerg] could not build optimal types_hash, you should increase either types_hash_max_size: 512 or types_hash_bucket_size: 64; you can specify the sizes in nginx.conf nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
这里有个细节:即使 syntax is ok,emerg 级别的警告 也可能影响实际运行。比如上面这个 types_hash 的警告,在高并发场景下会引发性能问题。
第二板斧:看日志
# 错误日志级别从 crit 调到 info(临时),能看到更多信息 error_log /var/log/nginx/error.log info; # 查看实时日志 $ sudo tail -f /var/log/nginx/error.log # 配合 grep 过滤 $ sudo tail -f /var/log/nginx/error.log | grep -i "upstream\|rewrite\|client" # 访问日志也值得关注 $ sudo tail -f /var/log/nginx/access.log
debug 级别日志量巨大,只在需要精确定位问题时临时开启。生产环境长期开 debug 可能一秒生成几百 MB 日志,硬盘分分钟爆掉。
第三板斧:热加载而不是重启
# 测试通过后,热加载配置(不断开现有连接) $ sudo nginx -s reload # 其他信号 $ sudo nginx -s stop # 优雅停止 $ sudo nginx -s quit # 优雅退出(等所有请求处理完) # 如果 Nginx 没响应信号,可以直接 kill $ sudo kill -HUP $(cat /var/run/nginx.pid) # 相当于 reload $ sudo kill -USR1 $(cat /var/run/nginx.pid) # 重新打开日志文件 # 查看当前生效的 worker 进程数(确认 reload 生效) $ sudo nginx -s reload && sleep 1 && ps aux | grep nginx
nginx -v 显示的版本号和 ps aux | grep nginx 里 worker 的启动时间要对应上。也可以在配置里加个特殊的响应头,然后 curl -I 验证。
进阶:看实际处理的配置片段
有时候 include 太多层,不知道哪个文件的哪一行在生效:
# 测试配置并输出最终合并后的配置(包含所有 include) $ sudo nginx -T # 配合 grep 找到具体 location 的处理逻辑 $ sudo nginx -T | grep -A 30 "location /api"
四、典型配置示例——拿来即用
完整反向代理 + SSL + CORS 配置
# /etc/nginx/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; multi_accept on; use epoll; } http { include /etc/nginx/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" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; # 性能相关 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # Gzip 压缩 gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; # 引入站点配置 include /etc/nginx/conf.d/*.conf; }
# /etc/nginx/conf.d/api.example.com.conf upstream api_backend { server 127.0.0.1:8080 weight=5; server 127.0.0.1:8081 weight=3; keepalive 32; } # HTTP → HTTPS 重定向 server { listen 80; listen [::]:80; server_name api.example.com; return 301 https://$host$request_uri; } # HTTPS 主站点 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name api.example.com; # SSL 配置 ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; 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; ssl_stapling_verify on; location / { # CORS 预检 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; proxy_pass http://api_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 10s; proxy_read_timeout 60s; } # 健康检查 location /health { access_log off; return 200 'OK'; } }🛠️ 想偷懒?这些工具我帮你写好了
正向代理、反向代理、负载均衡……Nginx 的活儿太多了,一个个配太累?
🚀 前往 CloverTools
我在 CloverTools 里放了一堆现成的工具,输入参数直接生成配置,拿走即用。五、写在最后
配置不生效这事,十次有九次是人的问题——路径写错、超时没配、reload 忘了做。说白了 Nginx 本身很稳定,出问题往往是细节。记住我的排查顺序:
nginx -t验证语法- 看
error.log报错信息nginx -s reload热加载- 加特殊 header 用
curl -I验证配置是否命中- 必要时
nginx -T看完整配置树把这套流程玩熟了,以后再遇到配置不生效,5 分钟内定位问题不是梦。祝你的 Nginx 再也不作妖。
☘️ CloverTools · 运维效率工具站 · https://clovertools.cn
常见问题
A: 这类工具一般有明确的输入框和输出框,按提示输入内容,点击对应按钮即可得到结果。建议先用简单示例测试功能是否正常,再处理实际数据。
A: 根据具体工具类型决定。格式转换工具适合处理第三方数据,编码工具适合加密传输,压缩工具适合文件上传前处理。多积累工具使用经验,遇到问题时能快速判断用哪个工具解决。
A: 不同工具有不同侧重,重点是理解原理。可以同时安装多个类似工具,实际使用中对比效果,选择最顺手的一个。随着使用经验增加,你也能判断工具的好坏。