这篇记录我在一个CMS站点接进别人的Nginx反向代理后,踩到的两个真坑:一个把整站打成502,一个让协同编辑静默失效。全部现象与验证都是本机实测。
背景
我手上有一个基于Node的CMS站点(内容管理用,跑在3000端口,本机lo回环监听)。新需求是接进公司统一入口:由统一网关的Nginx集群按主域名分发,HTTPS由网关侧终止,我这边只需要一个HTTP上游。
按经验,把站点接进别人的Nginx应该是最省事的一步,结果上来就挂了。
第一个坑:网关证是合并SAN证,SNI拿不到匹配证书,握手直接失败
现象:用主域名访问返回502,网关错误日志里全是"no suitable certificate"这类握手报错,后端Node进程却一切正常,直连IP的3000端口200。
我一开始怀疑是Nginx配置写错了upstream,反复核对location和proxy_pass,没问题。后来用openssl s_client连了一下,才看清根因:网关那台机器上证书是「合并SAN」的一张证,同时包含主域名和另外两个子域,但Nginx里这个server块的server_name写的是子域,SNI进来的是主域名,于是证书选择阶段就挑不出一张能匹配的证书,TLS握手失败,浏览器侧表现就是502/连接被重置。
修法两步:一是把这个server块的server_name改成覆盖主域名(或者干脆加default_server兜底),二是用openssl s_client -servername 主域名 -connect 网关:443 复验,确认返回的证书SAN里包含主域名才算完。
这里踩出来的经验是:502不一定是后端挂了。网关到后端这一段如果压根没握手成功,浏览器看到的也是502。排查顺序应该是:先在后端机curl自己的端口(确认后端活)→再curl网关IP并显式带Host头(确认分发正确)→最后openssl s_client看SNI与证书(确认TLS层)。把这三层分开,5分钟能定位,否则就会在upstream配置里瞎改。
第二个坑:协同编辑走WebSocket,Nginx默认不升264级
TLS修好后静态页面正常,但协同编辑功能没反应:打开编辑器,页面不报错,字数栏不动,两个人同时改互相看不到。
查浏览器控制台,WebSocket连接一直pending然后失败。原因是Nginx默认不会把Upgrade请求转发给上游——普通HTTP请求能过,带Upgrade的都死在网关。同时上游侧也得接受外部Host,否则应用侧的跨域判断会拒掉。
最终配置的关键三行:proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade",再加proxy_set_header Host $host。补完之后协同编辑立刻正常。
顺带发现的一处旧代码坑
排查过程中我还顺手翻了自己站点的库访问代码,发现一处双标写法:变量声明时名字写的是一个(DB_PATH),打开时却硬编码了"data/app.db"。当时正好有环境变量兜底,所以线上跑着没出事——但这属于埋雷:哪天换库路径,变量改了而硬编码没改,就会出现「以为连的是新库、实际写的是旧库」,而且因为周围有try/catch兜底,可能连报错都没有,静默写错地方。
我的处理方式是把硬编码全部收敛到一个DB_PATH(环境变量优先,其次默认路径),并且让「库打不开」这种情况直接抛错而不是被catch吞掉。凡是catch里什么都不做的代码,都值得回头看一眼,它吞掉的不只是异常,往往还有需要立刻知道的事实。
可复用的结论
- 接别人的Nginx,先确认三件事:SNI证书是否覆盖你的域名、Upgrade头是否透传、Host是否透传。这三条不过,后面所有排查都是浪费。
- 502优先怀疑TLS握手,不是后端进程。
- 路径类配置不要留硬编码兜底,环境变量改了硬编码没改等于静默写错库。
- 空catch是排查事故的隐形人,宁可让它抛出来。
补一句成本口径:这次从502到全部正常,实际动手约40分钟,其中30分钟浪费在没先分层验证、直接在upstream配置里反复试。先分层、再动手,比改配置快得多。