内网 IP 访问 Web 应用失败:原因与排查
什么情况下会撞上它
内网服务器跑 Web 应用,前面套反向代理,同时接入组网 VPN、内网穿透或 Mesh 网络。域名访问正常,换成内网 IP 后失败,常见三种现象:
- 无响应或超时:流量没到代理;
- 返回 400/403/404/502:到了代理但被拒或上游没连上;
- 页面能加载但跳转仍指向域名,或跳转后证书报错。
先分清是哪一种,排查方向完全不同。
为什么会这样
域名和 IP 在 HTTP 层不等价。请求里除目标地址外还有 Host 头(HTTPS 下还有 SNI),反向代理靠它决定转发到哪个后端。
- 虚拟主机匹配:Nginx、Caddy、Traefik 按
server_name分发。IP 访问时Host是 IP,匹配不到站点,落到默认站点或 404。 - Host 白名单:Django 的
ALLOWED_HOSTS、Next.js 的allowedDevOrigins等会校验 Host,不在名单直接拒绝,用于防 DNS 重绑定。 - 跳转地址写死:应用按配置的
base_url或public_url生成重定向。配成域名时,用 IP 进来也会被跳到域名。 - 证书与 SNI:用 IP 访问时 SNI 为空或为 IP,代理找不到对应证书,握手失败;自签证书还会触发浏览器拦截。
- 网络层差异:域名可能解析到组网地址,IP 是另一块网卡地址,路由、防火墙、监听地址都可能不同。
怎么做
第一步:确认端口和监听地址。
ss -lntp | grep 20000
只监听 127.0.0.1 时外部 IP 一定连不上,这是最常见原因之一。
第二步:绕开浏览器直接看响应。
curl -vk https://192.0.2.10:20000/ -H 'Host: example.com'
curl -vk https://192.0.2.10:20000/
带 Host 能通、不带不能通,问题在虚拟主机或 Host 校验;两次都连不上,问题在网络层或监听地址;两次都证书错误,问题在 SNI 与证书。
第三步:看代理日志。
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
用 IP 访问时日志有无记录,是区分“没到代理”和“到了但被拒”的直接证据。
第四步:按结论修。
- 监听地址不对:把
listen改成0.0.0.0:20000,并确认防火墙放行。 - Host 校验:在应用允许列表加入该 IP,或让代理统一改写
Host头。 - 跳转写死:把
base_url、public_url、回调地址改成相对路径或按请求动态生成。 - 证书问题:为 IP 单独签发证书(多数 CA 支持 IP SAN),或纯内网改用 HTTP,或手动信任自签证书。
怎么确认成了
用 curl -v 观察三件事:TCP 是否连上、TLS 握手是否完成、HTTP 状态码是多少。三者正常且返回内容与域名访问一致,就算通了。浏览器侧再确认跳转后的最终地址仍是 IP。
边界与代价
IP 直连不是推荐做法:绕过虚拟主机隔离,多站点共用端口时容易串站;证书对 IP 支持更麻烦;应用里写死的绝对地址几乎必然出问题。
更稳妥的是把 IP 访问当调试手段:临时在 hosts 文件把域名指向该 IP,或用 curl --resolve 指定解析,既保留 Host 语义又能验证网络。若长期需要 IP 访问,就在代理层显式加一个以 IP 为 server_name 的站点,并把应用重定向地址改为相对路径,而不是依赖默认站点兜底。