LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

Nginx 常见问题排查记录

admin
2026年5月21日 8:48 本文热度 42

一、proxy_pass 末尾 / 的差异

两段配置看起来几乎一样,转发结果完全不同:

​# 末尾带 /location /api/ {    proxy_pass http://backend/;}# 请求 /api/user → 转发为 /user(*****)# 末尾不带 /location /api/ {    proxy_pass http://backend;}# 请求 /api/user → 转发为 /api/user(*****)

简单记:带 / 是替换前缀,不带 / 是保留前缀。

如果 proxy_pass 的 URL 本身就带路径,规则又会变:

location /api/ {    proxy_pass http://backend/v1/;}# /api/user → /v1/user (location 前缀被 /v1/ 替换掉)

每次改完 proxy_pass 最好用 curl -v 跑一下确认转发结果,肉眼判断容易出错。

还有个隐蔽的限制:正则 location 里的 proxy_pass 不能带 URI 部分,否则启动直接报错。

# 错误:启动报错 "proxy_pass" cannot have URI part in location given by regular expressionlocation ~ ^/api/(.*)$ {    proxy_pass http://backend/v1/;}# 正确:用捕获组自己拼路径location ~ ^/api/(.*)$ {    proxy_pass http://backend/v1/$1$is_args$args;}

proxy_pass 里带变量时也是同样规则:URI 部分由你自己拼,Nginx 不会再做前缀替换。

二、改完配置不生效

改配置后没看到变化,按下面顺序排查:

# 1. 测试语法nginx -t# 2. 平滑重载nginx -s reload# 3. 确认进程加载情况ps -ef | grep nginx

如果上面都正常但还是不生效,看看是不是这几种情况:

- 改错文件了:以为改的是 `nginx.conf`,其实是被 `include` 引入的子文件- `server_name` 没命中:多个 server 监听同一端口时,请求可能落到 `default_server` 或第一个 server 上- 浏览器缓存:换无痕窗口或 `Cmd/Ctrl + Shift + R`- CDN 缓存:经过 CDN 的话需要刷新 CDN- `reload` 失败但没注意:`nginx -t` 通过不代表 `reload` 一定成功,留意输出和 error.log

    不确定当前实际加载的配置时,用:

    nginx -T                                          # 打印全部合并后的配置nginx -T | grep -n "server_name"                  # 看所有 server_name 命中情况nginx -T | sed -n '/server_name example\.com/,/^}/p'   # 看某个 server 的完整内容

    nginx -T 输出的是 Nginx 实际加载并合并后的最终配置,比一层层翻 include 文件靠谱。

    三、502 Bad Gateway

    意思是 Nginx 能收到请求,但后端没正常响应。

    排查顺序:

    # 1. 后端服务是否启动ps -ef | grep java          # 或对应的进程名ss -tlnp | grep 8080        # 看端口是否监听(老系统用 netstat -tlnp)# 2. 后端连通性telnet 127.0.0.1 8080curl -v http://127.0.0.1:8080/health# 3. 看 Nginx 错误日志,最关键tail -f /var/log/nginx/error.log

    常见原因和处理方式:

    - **后端服务挂了**:重启服务,并加上进程存活监控- **后端只监听 `127.0.0.1`,Nginx 走的不是本机**:改后端监听地址为 `0.0.0.0`,或让 Nginx 通过 `127.0.0.1` 访问- **upstream 用域名,DNS 解析失败或缓存过期**:error.log 里能看到 `no resolver defined to resolve` 或 `host not found`,需要在 http 段配 `resolver 8.8.8.8 valid=30s;`- **防火墙 / SELinux 拦截**`setsebool -P httpd_can_network_connect on`(Nginx 与 Apache 共用同一个布尔)- **后端响应过大,缓冲区不足**:调大 `proxy_buffer_size``proxy_buffers`- **upstream 节点被熔断**:检查 `max_fails` / `fail_timeout` 配置是否过于激进- **keepalive 复用了被关闭的连接**:偶发性 502 经常是这个——后端连接空闲超时比 Nginx 的 `keepalive_timeout` 还短,Nginx 复用了已被对端关闭的连接。要么把后端空闲超时调大,要么把 upstream 的 `keepalive_timeout` 调小

    四、504 Gateway Timeout

    后端响应太慢,Nginx 等不及主动断开。

    先调大超时让请求能完成:

    location / {    proxy_connect_timeout 60s;     # 与后端建连超时    proxy_send_timeout    300s;    # 向后端发送数据超时    proxy_read_timeout    300s;    # 从后端读响应超时    proxy_pass http://backend;}

    调大超时只是兜底,根本问题通常在后端:

    - SQL 慢查询- 调用外部接口卡住- 后端线程池打满- GC 停顿过长

    正确做法是定位后端慢在哪里,超时配置只是给一个上限。

    几类特殊场景的超时要单独调:

    - **大文件下载、导出**`proxy_read_timeout` 调到分钟级- **SSE / 长轮询**`proxy_read_timeout` 要大于服务端两条消息之间的最大间隔,不然中途会被断- **WebSocket**:见第八章,要专门配 `Upgrade` 头和长超时

    五、413 Request Entity Too Large

    上传文件常见。Nginx 默认请求体大小限制 1MB,文件上传场景肯定不够。

    http {    client_max_body_size 100m;     # 全局:最大 100MB}# 也可以只针对上传接口调大location /upload/ {    client_max_body_size 500m;    proxy_pass http://backend;}

    如果有多层代理(CDN → 入口 Nginx → 应用 Nginx),每一层都要配,否则会被中间某一层卡住。后端框架(Spring、Express 等)自身也有限制,需要一起调。

    排查时用:

    curl -v -F "file=@bigfile.zip" http://example.com/upload

    看响应里的 Server 头基本能判断 413 是哪一跳返回的——是入口 Nginx 还是后端框架。

    六、location 匹配命中不符合预期

    写了好几个 location,请求没命中你以为的那个,先看下匹配优先级(从高到低):

    1. `=`   精确匹配2. `^~`  前缀匹配,命中后不再查正则3. `~` / `~*`  正则匹配(区分 / 不区分大小写)4. 无修饰符  普通前缀匹配,最长前缀优先

    举例:

    location = /favicon.ico { ... }       # 精确匹配location ^~ /static/    { ... }       # 命中后不再查正则location ~* \.(jpg|png|gif)$ { ... }  # 正则匹配图片location /api/          { ... }       # 普通前缀匹配location /              { ... }       # 兜底

    调试时可以在 location 里加一个响应头标记当前命中的是哪一条:

    location /api/ {    add_header X-Matched-Location "/api/";    proxy_pass http://backend;}

    然后 curl http://xxx.com -I 看响应头。

    七、后端拿到的客户端 IP 是 Nginx 的

    经过反向代理后,TCP 连接的源 IP 就是 Nginx,真实 IP 需要通过 HTTP 头透传。

    Nginx 端:

    location / {    proxy_set_header X-Real-IP       $remote_addr;    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    proxy_pass http://backend;}

    后端读取 X-Real-IP 或 X-Forwarded-For 头:

    - Java:`request.getHeader("X-Real-IP")`- Node.js:`req.headers['x-real-ip']`

      如果想让后端的 remote_addr 直接拿到真实 IP,可以在 Nginx 里用 realip 模块:

      set_real_ip_from 10.0.0.0/8;       # 信任的代理网段set_real_ip_from 172.16.0.0/12;    # 可以写多条real_ip_header   X-Forwarded-For;real_ip_recursive on;              # 递归剥掉信任段,找到最左侧的真实客户端
      注意 X-Forwarded-For 是可以伪造的,不要直接拿来做鉴权。生产环境必须配 set_real_ip_from 限制只信任内网代理,否则攻击者随便伪造一个头就能绕过 IP 黑白名单。

      八、HTTPS 证书相关问题

      证书这块踩坑频率不低,按现象分几种。

      浏览器提示「证书无效」或「链不完整」

      最常见的是证书链不全。CA 签发时通常会给三个文件:站点证书、中间证书、根证书。Nginx 要的是把站点证书 + 中间证书拼成一个文件,顺序不能反:

      cat your_domain.crt intermediate.crt > fullchain.crt
      ssl_certificate     /etc/nginx/certs/fullchain.crt;     # 注意是 fullchainssl_certificate_key /etc/nginx/certs/your_domain.key;

      验证链是否完整:

      # 用 openssl 看openssl s_client -connect example.com:443 -servername example.com# 用在线工具更直观# https://www.ssllabs.com/ssltest/

      如果只配了站点证书没拼中间证书,桌面浏览器可能能访问(因为系统缓存了常见中间证书),但 App、curl、Java HttpClient 会报错。「PC 能开、App 报错」的反馈基本就是这个原因。

      多域名共享 IP,命中了错的证书

      一个 IP 上挂多个 HTTPS 站点,靠 SNI 区分。如果客户端不发 SNI(很老的客户端、或者直接用 IP 访问),Nginx 会返回第一个 server 的证书,结果就是域名对不上。

      # 显式定一个默认 server,避免落到别的业务站点上server {    listen 443 ssl default_server;    ssl_certificate     /etc/nginx/certs/default.crt;    ssl_certificate_key /etc/nginx/certs/default.key;    return 444;    # 直接断开连接,不响应}

      证书过期

      # 命令行看过期时间echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \    | openssl x509 -noout -dates# 用 Let's Encrypt 的话配自动续期certbot renew --dry-run

      线上证书一定要加监控,过期前两周报警。等用户截图发过来才知道就晚了。

      混合内容(Mixed Content)

      页面是 HTTPS,但里面引用了 HTTP 的 js/css/图片,浏览器会拦截并在控制台报错。检查页面源码里有没有 http:// 开头的资源,统一改成 // 开头或 https://。老项目改不动的可以先加一个 meta 兜底:

      <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

      九、301 / 302 跳转死循环

      浏览器报 ERR_TOO_MANY_REDIRECTS,最常见两种情况。

      HTTP → HTTPS 跳转死循环

      典型场景:Nginx 前面挂了 SLB / CDN,SLB 已经把 HTTPS 卸载成 HTTP 转给 Nginx,Nginx 又判断「协议不是 HTTPS」再跳一次,跳完 SLB 又卸载,循环往复。

      # 错误写法:会死循环server {    listen 80;    if ($scheme != "https") {        return 301 https://$host$request_uri;    }}

      正确做法是看 SLB 透传的 X-Forwarded-Proto,而不是自己看 $scheme

      server {    listen 80;    if ($http_x_forwarded_proto != "https") {        return 301 https://$host$request_uri;    }    # 已经是 HTTPS 了(被 SLB 卸载过的),正常处理    location / {        proxy_pass http://backend;    }}

      更稳的做法是直接在 SLB 上做 HTTP→HTTPS 跳转,Nginx 这层不掺和。

      Nginx 和后端互相跳

      Nginx 跳一次,后端框架(比如 Spring Security 默认开启的 HTTPS 强制跳转)又跳一次,两边互相把对方的请求当成 HTTP,于是来回打转。

      排查时用 curl -IL 跟一次完整跳转链路:

      curl -IL http://example.com/login

      看输出里每一跳的 Location: 目标地址,能直接看出是哪一层在反复跳。

      十、静态资源 403 Forbidden

      文件明明存在却返回 403,按这个清单排:

      # 1. 看 Nginx 运行用户ps -ef | grep "nginx: worker"   # 一般是 nginx 或 www-data# 2. 看文件权限ls -la /data/www/index.html# 3. 看目录权限(一路到根)namei -l /data/www/index.html   # macOS 没这个命令,用 ls -ld 一层层看# 4. 看 SELinux(CentOS 上常见)getenforcesetenforce 0    # 临时关掉确认是不是它

      常见情况和处理方式:

      - **文件权限不够**`chmod 644 file`- **目录没 x 权限**`chmod 755 dir`- **Nginx 用户没读权限**`chown nginx:nginx /data/www -R`- **SELinux 拦截**(推荐持久化方式):  ```bash  semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"  restorecon -Rv /data/www  ```  直接 `chcon -R -t httpd_sys_content_t /data/www` 也能临时生效,但下次系统 `restorecon` 重打标签时会被还原,不如 `semanage` 持久。- **访问的是目录但没有 index 文件**:配置 `index` 或开启 `autoindex on;`

      十一、access.log 撑爆磁盘

      Nginx 自身不会切割日志,需要靠 logrotate 或自定义脚本。

      大多数发行版装包时会自带 /etc/logrotate.d/nginx,先看一眼默认配置:

      cat /etc/logrotate.d/nginx

      有的话按需调整 rotatecompress 等参数;没有的话再自己写一份。

      logrotate 方案

      新建 /etc/logrotate.d/nginx

      /var/log/nginx/*.log {    daily                # 每天切一次    rotate 7             # 保留 7 份    missingok            # 文件不存在不报错    notifempty           # 空文件不切    compress             # 压缩历史日志    delaycompress        # 延迟一天压缩(避免当天的还在写)    sharedscripts        # 多个文件共享 postrotate    postrotate        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`    endscript}

      kill -USR1 是给 Nginx 发「重新打开日志文件」的信号,配合 mv 完成滚动。如果不发这个信号,Nginx 会继续往改名后的旧文件里写,新文件一直是空的。

      调试:

      logrotate -d /etc/logrotate.d/nginx   # 调试模式(不实际执行)logrotate -f /etc/logrotate.d/nginx   # 强制执行一次

      按小时切割

      高流量场景按天切割颗粒度太粗,可以写个脚本按小时切:

      #!/bin/bashLOG_DIR=/var/log/nginxDATE=$(date -d "1 hour ago" +%Y%m%d%H)mv ${LOG_DIR}/access.log ${LOG_DIR}/access_${DATE}.logkill -USR1 $(cat /var/run/nginx.pid)# 清理 7 天前的日志find ${LOG_DIR} -name "access_*.log" -mtime +7 -delete

      加到 crontab:

      0 * * * * /usr/local/bin/cut_nginx_log.sh

      减少不必要的日志

      # 静态资源不记访问日志location ~* \.(jpg|png|css|js)$ {    access_log off;}# 健康检查接口不记日志location = /health {    access_log off;    return 200;}

      十二、Too many open files

      高并发场景常见,error.log 里会一直刷:

      accept4() failed (24: Too many open files)

      Nginx 每个连接占一个文件描述符,默认 1024 远远不够。要按下面三层一起调,少调一层就不生效。

      # 1. 系统级cat /proc/sys/fs/file-maxecho "fs.file-max = 1000000" >> /etc/sysctl.confsysctl -p# 2. 用户级(编辑 /etc/security/limits.conf)nginx soft nofile 65535nginx hard nofile 65535# 3. Nginx 进程级(nginx.conf 顶部)worker_rlimit_nofile 65535;events {    worker_connections 65535;}

      改完确认是否生效:

      cat /proc/$(pgrep -f "nginx: worker" | head -1)/limits | grep "open files"

      如果是 systemd 启动的 Nginx,/etc/security/limits.conf 不生效,要在 service 文件里改:

      # /usr/lib/systemd/system/nginx.service[Service]LimitNOFILE=65535

      然后 systemctl daemon-reload && systemctl restart nginx

      附:常用排查命令

      # 实时看错误日志tail -f /var/log/nginx/error.log# 实时看访问日志,5xx 高亮tail -f /var/log/nginx/access.log | grep --color -E "5[0-9]{2}|$"# 访问 TOP 10 IPawk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10# 慢请求 TOP 10awk '{print $NF, $7}' access.log | sort -rn | head -10# 各状态码数量awk '{print $9}' access.log | sort | uniq -c | sort -rn# 看连接数ss -s                                          # 总览(替代 netstat)watch -n 1 "ss -ant | grep :80 | wc -l"        # 80 端口连接数

      排查 Nginx 问题的思路其实就几条:先看 error.log,再确认请求经过哪些节点,然后对比能用和不能用的环境,最后一次只改一个变量。多数线上问题按这个顺序走基本都能定位。


      该文章在 2026/5/21 8:52:29 编辑过
      关键字查询
      相关文章
      正在查询...
      点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
      点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
      点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
      点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
      Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-2  粤公网安备44030602007207号