1、背景
1.1 nginx配置
线上 nginx 配置 ip 白名单,使用allow + deny all做访问控制,仅允许内网 ip196.16.0.2访问服务。
allow 196.16.0.2; deny all;
1.2 发起请求
同一台 windows 浏览器发起两类请求:
- 访问首页
get /:请求正常返回 304,页面成功加载;nginx 访问日志客户端 ip 记录为196.16.0.2。 - 页面 js 发起后端接口
get /test/get:直接返回 403 拒绝访问;nginx 日志客户端 ip 记录为公网 ip1.80.211.195。
1.3 nginx日志
现象:同一个浏览器,页面请求放行,接口请求被拦截,nginx 日志记录两个完全不同的客户端 ip,业务人员第一反应怀疑浏览器或者 nginx 缓存异常。
196.16.0.2 - - [07/aug/2026:15:01:25 +0800] "get / http/1.1" 304 0 "-" "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/147.0.0.0 safari/537.36" 1.80.211.195 - - [07/aug/2026:15:01:26 +0800] "get /test/get http/1.1" 403 555 "http://196.16.0.37/" "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/147.0.0.0 safari/537.36"
referer 字段http://196.16.0.37/,说明前端页面部署在196.16.0.37这台 nginx 服务器。
2、根因分析
2.1 核心原理:allow/deny 的判断对象
allow、deny属于 nginx 的access访问控制模块,读取的是 tcp 四层连接的源 ip 变量$remote_addr,完全忽略 http 请求头,包括x‑forwarded‑for。 即使 http 头部携带真实客户端内网 ip,allow/deny也不会读取,仅以 tcp 连接对端 ip 作为判断依据。
这是绝大多数开发容易踩坑的知识点:经过代理转发的请求,$remote_addr是代理服务器出口 ip,而不是浏览器真实 ip。真实客户端 ip 被放在 http 请求头x‑forwarded‑for中。
2.2 两条请求链路完全不一样
- 首页请求链路(正常) 浏览器访问内网地址
http://196.16.0.37/,直接在内网访问 nginx 服务。 tcp 连接来源 ip 就是浏览器机器196.16.0.2,命中白名单allow 196.16.0.2,请求放行,页面正常返回。 - ajax 接口请求链路(403 拒绝) 前端 js 代码硬编码写死公网域名调用后端接口,没有使用相对路径。 浏览器发起接口请求不再走内网,流量走到公网,经过上层负载均衡 / 代理网关,再转发到同一套 nginx。 此时 nginx 收到 tcp 连接的源 ip 变成代理出口公网 ip
1.80.211.195;虽然 http 头x‑forwarded‑for携带真实客户端196.16.0.2,但是allow/deny不识别 http 头,匹配不到白名单,执行deny all,返回 403。
关键点:同一个浏览器,不同请求可以走完全独立的网络链路,nginx 看到的客户端 ip 可以完全不同,不是浏览器故障。
2.3 现象总结
页面在内网访问,接口走公网代理;四层 tcp 源 ip 发生改变,http 头里的真实 ip 没有被 nginx 访问控制模块识别,最终出现页面正常,接口 403 的诡异现象。
2.4 故障流程图说明

3、改变 nginx 看到的 $remote_addr的各类方式
说明:$remote_addr是 nginx 拿到的tcp 连接对端 ip,allow/deny 直接读取这个值。 下面全部是会改变服务端看到 ip 的场景,也就是:浏览器真实 ip 不变,但 nginx 日志里面记录的 ip 变成别的 ip。
代理转发类(最常见,业务系统高频踩坑)
nginx 反向代理
客户端→代理 nginx→后端 nginx / 服务。后端 nginx 看到的 ip 是代理服务器 ip。 需要配置proxy_set_header x‑forwarded‑for $remote_addr传递真实 ip;但allow/deny仍然看 tcp 源 ip,不读该 header。
haproxy / lvs (dr 模式除外) / 负载均衡 slb(云厂商)
云 slb、硬件负载均衡,流量经过负载均衡转发,后端服务 tcp 对端 ip 变成 lb 节点 ip。
本次故障就是该场景:页面直连内网,接口走公网 slb,ip 发生变化。
cdn 加速
域名接入 cdn 后,用户请求全部打到 cdn 节点,源站 nginx 看到的 ip 是 cdn 节点 ip,不是用户真实 ip。
内网正向代理(公司代理服务器)
公司内网配置 http 代理,浏览器所有请求经过代理服务器出去,服务端看到代理服务器 ip。
vpn、隧道类
远程 vpn(ssl‑vpn、ipsec vpn)
- 客户端接入 vpn:访问内网资源时,tcp 源 ip 变为 vpn 分配给本机的虚拟内网 ip;访问公网资源时,出口为 vpn 网关公网 ip。
现象:本机真实网卡 ip 不变,但是服务端看到的 ip 已经切换。
代理隧道工具(socks5 代理)
浏览器配置 socks5 代理,全部流量经由代理节点转发,服务端记录代理节点 ip。
zerotier / tailscale 虚拟组网
组建虚拟局域网,访问虚拟网络地址,tcp 源 ip 为虚拟网卡分配 ip,和物理网卡 ip 不一样。
域名 / dns 带来的 ip 变化(本次故障根源)
同一个浏览器,不同域名,会走完全不同网络路径,服务端拿到不同源 ip
- 域名 a 记录指向内网 ip:浏览器直接内网 tcp 直连,nginx 看到浏览器真实内网 ip。
- 同一个业务,另外一个域名解析到公网 ip(经过 slb/cdn):dns 解析出来公网 ip,浏览器向外网发起 tcp 连接,经过公网代理,nginx 看到代理节点 ip。
典型坑:页面用内网域名http://196.16.0.37访问,js 接口写死公网域名,虽然是同一台浏览器,两套请求 tcp 链路完全不一样,服务端拿到的源 ip 完全不同,就是我们故障案例。
关键点:域名 / dns 本身不会修改 ip,但是 dns 解析结果决定流量走哪条网络链路,最终导致服务端看到的 $remote_addr 改变。
网关 / nat 网络环境
snat 源地址转换(路由器、防火墙做源 nat)
大量企业内网出口防火墙做 snat,内网机器访问服务器,源 ip 被转换成防火墙网关 ip。
- 如果客户端和服务器同网段:不经过 snat,服务端看到真实客户端 ip。
- 如果客户端访问跨网段,经过防火墙 snat:服务端看到网关 ip。
极易出现:同个浏览器,访问同服务器,不同访问路径,日志 ip 不一样。
容器环境 nat(docker、k8s)
容器内部访问外部服务,经过 docker0 网桥 snat,外部服务看到宿主机 ip,不是容器内部 ip。
客户端侧网络切换
1. 电脑多网卡:机器同时有内网网卡、wi‑fi、4g 上网卡。访问不同目标 ip,系统路由选择不同网卡出口,tcp 源 ip 随之改变。
例如:访问内网服务走内网网卡;访问公网域名走 wi‑fi 网卡,服务端看到不同 ip。
2. 双栈网络 ipv4/ipv6:服务器支持 ipv6,客户端优先 ipv6 连接,nginx 日志记录客户端 ipv6 地址,和 ipv4 内网 ip 完全不同。
总结
allow/deny是四层 tcp 访问控制,只要中间任意设备做了代理、nat、vpn 转发,$remote_addr 就会被替换,哪怕 http 头携带真实 ip,allow/deny 完全无视。- 有代理 / nat 环境,不要使用
allow/deny做业务客户端 ip 白名单,需要读取x‑forwarded‑for做应用层 ip 校验,同时做好防请求头伪造。
| 方式 | 是否改变 tcp 源 ip ($remote_addr) | 是否使用 x‑forwarded‑for 传递真实 ip | allow/deny 能否拿到真实客户端 ip |
|---|---|---|---|
| 反向代理 / slb/cdn | ✅改变(变成代理 ip) | ✅可以带上真实 ip | ❌不能,allow/deny 只看 tcp 源 ip,忽略 header |
| vpn、socks 代理 | ✅改变(网关 / 代理节点 ip) | ❌一般不带 x‑forwarded‑for | ❌ |
| dns 域名切换链路 | ✅间接改变(路由链路变化) | 看链路中间设备 | ❌ |
| snat 防火墙 nat | ✅改变(网关 ip) | ❌ | ❌ |
以上就是nginx allow/deny诡异问题:同一浏览器页面正常接口403问题分析与解决的详细内容,更多关于nginx allow/deny页面正常接口403的资料请关注代码网其它相关文章!
发表评论