Nginx resolver 域名解析缓存:反代上游换 IP 后仍走旧地址的排查

    发布时间:2026-09-27 12:30 更新时间:2026-09-27 12:30 阅读量:0

    把 Nginx 当反向代理用时,很多站长图省事,直接写 proxy_pass http://api.example.com; 让 Nginx 自己去解析上游域名。平时没问题,可一旦上游换了 IP——比如对象存储换了接入点、后端服务迁到了新机器、CDN 回源节点调整——奇怪的现象就来了:你在本机 ping 域名已经是新 IP,浏览器直连也正常,但经过 Nginx 的请求还是打到旧 IP 上,报 502 或者干脆连到一台已经下线的机器。这类问题多半不是 DNS 没生效,而是 Nginx 对解析结果的处理方式和你想象的不一样。

    为什么 Nginx 会“记死”旧 IP

    关键点在于:当 proxy_pass 后面跟的是一个不带变量的域名时,Nginx 会在启动或重载配置的那一刻解析一次,把得到的 IP 记录下来,之后一直用这个结果,直到下次 reload。也就是说,上游 DNS 的 TTL 到期、A 记录已经指向新地址,Nginx 进程并不知情,它手里的还是启动时那份解析结果。这跟浏览器、curl 每次请求都可能重新查 DNS 的行为差别很大,所以会出现“外面都好了,只有 Nginx 没跟上”的情况。

    要打破这种固定,有两条路。一条是变更上游 IP 后主动执行 nginx -s reload,让 Nginx 重新解析;另一条是在配置里引入变量,并配合 resolver 指令,让 Nginx 按 TTL 周期性重新解析。前者简单但依赖人工,后者适合上游地址经常变动的场景。

    还有一种容易被忽略的情况:即使解析已经更新,如果开了 proxy_cache,缓存里存的仍是旧上游返回的内容,看起来就像“还连着旧机器”。这时需要区分是连接层没更新,还是缓存层没刷新。

    排查步骤:先确认请求到底发给了谁

    第一步,看错误日志里报的是什么。旧 IP 已经下线时,通常会看到 connect() failed 或 upstream timed out 之类的记录,日志里往往直接带着 IP,能一眼看出用的是哪个地址:

    tail -n 50 /www/wwwlogs/example.com.error.log
    grep -i "upstream" /www/wwwlogs/example.com.error.log | tail -n 20

    第二步,在服务器上手动解析域名,和日志里的 IP 对比。注意要确认 Nginx 用的是系统解析还是自己配的 DNS:

    getent hosts api.example.com
    dig +short api.example.com
    cat /etc/resolv.conf

    如果日志里的 IP 和 dig 出来的不一致,基本可以判定是 Nginx 侧解析结果陈旧。第三步,检查配置里 proxy_pass 的写法,看是写死的域名还是变量形式;同时确认有没有 proxy_cache 相关指令,以及缓存目录里是否还有旧内容。第四步,可以临时用 curl 带上 Host 头直连上游验证,排除是上游本身的问题:

    curl -I -H "Host: api.example.com" http://新IP地址/

    如果直连新 IP 正常,而经 Nginx 仍报错,问题就落在 Nginx 的解析或缓存上。此时先执行一次 reload 观察是否恢复,能恢复说明就是启动期解析被固定,接下来再考虑是否改成变量化配置。

    用变量 + resolver 让解析跟随 TTL

    要让 Nginx 动态解析,需要两件事配合:一是配置 resolver 指定 DNS 服务器,二是在 proxy_pass 里使用变量,通常借用 set 或 map 把域名放进变量。下面是一个可参考的写法,DNS 地址请换成你服务器实际可用的解析服务,具体行为以官方文档为准:

    resolver 223.5.5.5 119.29.29.29 valid=30s ipv6=off;
    resolver_timeout 5s;
    
    upstream backend_api {
        zone backend_api 64k;
        server api.example.com:443 resolve;
    }
    
    server {
        listen 443 ssl;
        server_name www.example.com;
    
        location /api/ {
            proxy_pass https://backend_api;
            proxy_set_header Host api.example.com;
            proxy_ssl_server_name on;
        }
    }

    这里用的是 upstream 块里的 resolve 参数配合 zone,属于较新版本 Nginx 支持的做法,好处是保留 upstream 的健康检查和负载均衡能力,同时让成员地址随 DNS 变化。另一种常见写法是直接在 location 里用变量拼 URL:

    location /api/ {
        set $backend_host api.example.com;
        proxy_pass https://$backend_host;
        proxy_set_header Host $backend_host;
        proxy_ssl_server_name on;
    }

    需要提醒的是,proxy_pass 中使用变量后,Nginx 不再做启动期解析,而是每次需要时向 resolver 查询并按其 TTL 缓存,因此 resolver 必须配置,否则会报“no resolver defined”之类错误。valid 参数可以覆盖 TTL,写小了会增加 DNS 查询压力,写大了更新会有延迟,按业务容忍度取舍。另外,带变量时 URI 的拼接规则会变化,proxy_pass 末尾是否带斜杠要结合 location 一起测试,避免路径被吃掉或多出一段。

    如果确认是缓存层的问题,除了检查 proxy_cache_path 和 proxy_cache_key,变更上游后可以清理对应缓存目录,或在需要即时生效的接口上关闭缓存。别把连接层的解析缓存和内容缓存混为一谈,两者要分开排查。

    小结与下一步建议

    上游域名换 IP 后 Nginx 仍走旧地址,核心原因就两类:启动期解析被固定,或者内容缓存没刷新。排查时先看错误日志里的 IP,再和 dig 结果对照,能快速定位是哪一类。日常运维里,如果上游地址相对稳定,写死域名加变更后 reload 就够用;如果上游会随调度变化,建议改成 resolver + 变量或 upstream resolve 的写法,并把 valid 值设成一个和业务匹配的秒数。改完配置记得用 nginx -t 校验语法再 reload,观察一段时间日志确认请求确实打到新地址上。

    继续阅读

    📑 📅
    MySQL被OOM Kill排查:swap、buffer pool与监控取舍 2026-09-27
    HTTPS混合内容批量修复:控制台与curl定位http资源 2026-09-27
    宝塔面板部署 WordPress 后固定链接 404:伪静态规则加载顺序与 try_files 排查 2026-09-27
    网站301与302混用导致权重分散:跳转类型选择与生效验证 2026-09-27
    WordPress后台被暴力撞库告警:登录限速与日志加固 2026-09-26
    域名下PC站与m站SEO冲突:canonical、自适应与UA跳转取舍 2026-09-26
    宝塔面板日志被CDN节点IP填满:log_format与real_ip联动调整 2026-09-27
    WordPress 数据库表前缀修改实战:从 wp-config 到 SQL 批量改名 2026-09-27
    Nginx泛域名证书自动签发:acme.sh DNS验证与泛解析站点批量部署 2026-09-27
    服务器内存充足却频繁502:PHP-FPM进程回收与pm.max_requests取舍 2026-09-27