现象
一台机器上四个子域名,反代到本地四个端口。后端都没起来,按说应该整整齐齐返回 502。实际是:
admin.example.cn HTTP 502 0.12s
login.example.cn HTTP 502 0.13s
pay.example.cn HTTP 502 0.12s
api.example.cn 超时 (curl 等到 timeout 才放弃)
前三个瞬间失败,第四个卡住不动。四份配置是同一个模板生成的,看起来一模一样。
差别
把四份配置里的 proxy_pass 拉出来对比:
proxy_pass http://127.0.0.1:31400; # admin
proxy_pass http://127.0.0.1:31401; # login
proxy_pass http://127.0.0.1:31402; # pay
proxy_pass http://31403; # api ← 少了 127.0.0.1:
为什么不是 502,而是挂起
关键在于 nginx 怎么解析 proxy_pass 后面那个 URL。
http://127.0.0.1:31403 —— host 是 127.0.0.1,port 是 31403。nginx 直接连本地回环,端口没人监听,内核立刻回 ECONNREFUSED,nginx 把它翻译成 502。失败得很快,因为根本没出网卡。
http://31403 —— nginx 按 URL 语法解析,冒号前面什么都没有,于是 31403 整个被当成主机名。这是一个合法的主机名(域名规则允许全数字标签),所以 nginx 不会报配置错误,nginx -t 也照样通过。它会老老实实拿去做 DNS 解析。
DNS 查一个不存在的名字,走的是超时重试路径——通常是若干秒一轮、重试几次。请求就卡在那里。等 resolver 最终放弃,nginx 才会返回 502 或 504。
所以这个 bug 的表现是慢,不是错。而慢比错难查得多:你会先去怀疑后端卡住了、怀疑网络、怀疑超时参数,最后才想到去数配置里的字符。
顺带还有一处
同一份配置里,紧挨着的下一行也被污染了:
proxy_set_header Host 31403; # 其它三个站是 Host $http_host
后端收到的 Host 头是字符串 31403。如果应用要靠 Host 推导回调地址、拼绝对 URL、或者做 CORS 白名单匹配,这会引发一连串更莫名其妙的问题——而且都发生在「请求确实到达了后端」之后,看起来完全不像同一个原因。
根因
这两处是同一个来源。面板类工具(宝塔、1Panel 之类)建反代站点时,界面上有个「目标 URL」输入框,模板会把它同时套进 proxy_pass 和 proxy_set_header Host。当时那一栏只填了 31403,没填 127.0.0.1:31403。
也就是说:改生成后的配置文件只是治标。只要有人再从面板保存一次那个站点,配置就会被重新生成,bug 原样回来。要根治得去面板把那一栏改对。
怎么避免
proxy_pass后面永远写完整的scheme://host:port。少写 host 不会报错,只会让你在几个月后花一下午查一个「后端偶尔很慢」的问题。多站点部署时,横向对比同类配置比逐份细读有效得多:
grep -h "proxy_pass" /path/to/vhost/*.conf | sed 's/^\s*//' | sort -u一眼就能看出哪一行长得不一样。
用脚本或模板统一生成 vhost,别让人手填能被拼进配置的自由文本。
排障时,区分「快速失败」和「超时」。前者通常是连接被拒(端口没人听、防火墙 REJECT),后者通常是解析卡住、路由黑洞、或者防火墙 DROP。这两类的排查方向完全不同,先分清能省很多时间。