在自建网站和家庭服务器中,一个非常常见的架构是:

Internet
   ↓
Router / Firewall
   ↓
Reverse Proxy
   ↓
Application Server
   ↓
WordPress / Jellyfin / Nextcloud / 其他 Web 应用

这种架构很实用。HTTPS 可以集中在反向代理处终止,不同域名可以被分发到不同服务,后端应用也不必直接暴露在公网。

但它同时带来了一个容易被忽视的问题:

后端应用看到的客户端 IP,往往不是真正访问者的 IP,而是反向代理服务器的 IP。

这不仅影响日志可读性,还会直接影响登录审计、暴力破解防护、封禁策略和安全取证。

理解这个问题,需要先弄清楚 IP 地址到底是在网络的哪一层产生和传递的。


一、为什么后端应用会看到代理服务器的 IP

假设一个客户端访问网站:

Client
  ↓
Reverse Proxy
  ↓
Application

客户端首先与反向代理建立 TCP 连接。

例如:

客户端        → 反向代理
198.51.100.25   203.0.113.10

此时反向代理当然能够知道真正的客户端地址:

198.51.100.25

但是接下来,反向代理会自己重新建立一个连接到后端应用:

反向代理     → 后端服务器
10.0.0.10      10.0.0.20

对于后端服务器来说,这是一条新的 TCP 连接。

所以它在网络层看到的源地址实际上是:

10.0.0.10

而不是最初的:

198.51.100.25

这不是 Bug,而是反向代理工作的正常结果。

可以理解为:

原始客户端和后端应用之间,并不存在一条完整的端到端 TCP 连接。

中间的反向代理把连接“截断”成了两段。


二、真实 IP 是怎样传给后端应用的

既然 TCP 层的客户端地址已经变成代理服务器,那么真实地址只能通过 HTTP 层额外传递。

最常见的几个 HTTP Header 是:

X-Real-IP: 198.51.100.25

以及:

X-Forwarded-For: 198.51.100.25

因此完整流程变成:

Client
198.51.100.25
      ↓

Reverse Proxy
知道 TCP 来源 = 198.51.100.25

然后发送:

X-Real-IP: 198.51.100.25
X-Forwarded-For: 198.51.100.25

      ↓

Application
TCP peer = 10.0.0.10
HTTP forwarded IP = 198.51.100.25

这时候后端应用就有机会恢复真正的客户端地址。

不过,这里马上出现了一个安全问题。


三、X-Forwarded-For 本身并不可信

HTTP Header 是客户端可以自行发送的。

攻击者完全可以提交:

X-Forwarded-For: 8.8.8.8

甚至:

X-Forwarded-For: 127.0.0.1

或者:

X-Forwarded-For: 10.0.0.1

如果后端应用无条件相信这个 Header,那么攻击者就可以随意伪造来源 IP。

这会导致:

  • 登录日志记录错误地址;
  • 暴力破解防护把错误 IP 加入限制;
  • 攻击者逃避基于 IP 的速率限制;
  • 审计日志失去取证价值;
  • 某些错误设计的“仅允许内网 IP”逻辑甚至可能被绕过。

因此关键原则是:

不应该信任“客户端声称自己是谁”,而应该只信任已知反向代理重新写入的客户端地址。


四、$proxy_add_x_forwarded_for$remote_addr 的区别

以 Nginx 为例,常见配置之一是:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

它的作用不是简单地“设置真实 IP”。

它实际上会保留已有的 X-Forwarded-For,然后把当前连接地址追加到后面。

例如攻击者发送:

X-Forwarded-For: 8.8.8.8

而 Nginx 实际看到客户端来自:

198.51.100.25

最终发送给后端的内容可能类似:

X-Forwarded-For: 8.8.8.8, 198.51.100.25

这在存在多层可信代理时非常有用。

例如:

CDN
 ↓
Load Balancer
 ↓
Reverse Proxy
 ↓
Application

此时确实需要维护一个代理链。

但是如果架构实际上只有一层 HTTP 反向代理:

Internet
 ↓
Nginx
 ↓
Application

那么更简单、更严格的做法通常是:

proxy_set_header X-Real-IP       $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

这相当于告诉后端:

不管客户端原来提交了什么 X-Forwarded-For,都由边缘代理重新定义真实客户端地址。

这样可以消除客户端自行伪造 XFF 的影响。


五、“可信代理”才是整个机制的核心

仅仅让反向代理发送:

X-Forwarded-For

还不够。

后端应用还必须知道:

哪些服务器有资格提供这个 Header?

这就是 Trusted Proxy,也就是“可信代理”的概念。

假设:

Reverse Proxy = 10.0.0.10
Application   = 10.0.0.20

应用应该配置成:

只信任 10.0.0.10 提供的 forwarded IP

而不能简单写成:

信任整个互联网

或者无条件信任所有请求中的:

X-Forwarded-For

正确逻辑应当是:

请求 TCP 来源是不是可信代理?
        │
        ├─ 否 → 使用 TCP 来源 IP
        │
        └─ 是 → 才允许解析 X-Forwarded-For

这也是成熟 Web 应用普遍采用的设计。


六、不同应用怎样处理真实客户端 IP

虽然原理相同,不同软件的实现方式并不一样。

WordPress

WordPress 本身通常运行在 Apache 或 Nginx 后面。

一种常见方案是让 Web Server 先恢复真实 IP。

例如 Apache 可以使用:

mod_remoteip

告诉 Apache:

这个反向代理是可信的;
它提供的 X-Forwarded-For 可以用于恢复客户端地址。

恢复以后:

REMOTE_ADDR

就可以表现为真实客户端地址。

这样 WordPress 以及依赖 REMOTE_ADDR 的插件,都可以得到正确的 IP。

因此 WordPress 环境里通常是:

Reverse Proxy
    ↓
X-Forwarded-For
    ↓
Apache mod_remoteip
    ↓
REMOTE_ADDR
    ↓
WordPress

Jellyfin

Jellyfin 自己具备反向代理相关设置。

它需要知道:

哪些代理服务器是可信的

然后才会从转发 Header 中恢复真实客户端 IP。

如果没有正确配置可信代理,Jellyfin 很可能一直认为:

客户端 = 反向代理服务器

于是所有登录失败记录都会变成同一个内网 IP。

这会严重削弱登录审计功能。

正确配置以后,Jellyfin 的活动记录才能显示:

AuthenticationFailed
Client IP: 真实公网地址

而不是:

Client IP: Reverse Proxy

Nextcloud

Nextcloud 的设计更加明确。

它通过类似:

'trusted_proxies'

这样的配置来指定可信代理。

同时可以从:

HTTP_X_FORWARDED_FOR

中恢复客户端地址。

核心逻辑仍然是:

REMOTE_ADDR
    ↓
是不是 trusted proxy?
    ↓
是
    ↓
才读取 X-Forwarded-For

这非常重要,因为 Nextcloud 自带暴力破解防护机制。

如果真实 IP 配置错误,那么多个用户可能全部被识别成同一个反向代理地址。

结果可能是:

用户 A 登录失败
用户 B 登录失败
用户 C 登录失败

系统却认为:
同一个 IP 连续失败多次

这样就可能错误触发限流。

反过来,如果任意客户端都可以伪造 XFF,攻击者又可能规避基于 IP 的保护。


七、为什么局域网用户又会出现另一个问题:Hairpin NAT

公网访问修好了,不代表局域网访问也一定正确。

这里经常还会出现另一个问题:

NAT Hairpin

也叫:

  • NAT Loopback
  • NAT Reflection

假设一个服务的公网域名:

cloud.example.net

公网 DNS 返回:

203.0.113.10

而局域网用户也访问:

https://cloud.example.net/

如果局域网 DNS 同样返回公网地址,那么流量可能变成:

LAN Client
10.0.0.50
    ↓
Router
    ↓
Public IP 203.0.113.10
    ↓
NAT Reflection
    ↓
Reverse Proxy

为了让回程路由正常,一些路由器会进行 SNAT。

于是反向代理最终看到的客户端不再是:

10.0.0.50

而变成:

10.0.0.1

也就是路由器地址。

此时,即使反向代理和应用的真实 IP 设置全部正确,也无能为力。

因为:

客户端 IP 在到达反向代理之前,就已经被路由器改掉了。


八、这种情况下为什么 Split DNS 更合理

解决方式通常不是继续修改应用,而是使用 Split DNS。

公网用户查询:

cloud.example.net

得到:

203.0.113.10

而局域网用户查询同一个域名:

cloud.example.net

得到:

10.0.0.10

也就是反向代理的内网地址。

于是 LAN 流量变成:

LAN Client
10.0.0.50
    ↓
Reverse Proxy
10.0.0.10

不再经过:

公网 IP
NAT reflection
SNAT

这样反向代理就能直接看到:

10.0.0.50

应用最终也可以记录真实 LAN 地址。

这就是:

Public DNS:
cloud.example.net → Public IP

LAN DNS:
cloud.example.net → Reverse Proxy LAN IP

域名没有变化,HTTPS 证书也没有变化。

客户端仍然正常使用:

https://cloud.example.net/

只是内部和外部解析结果不同。


九、为什么不能把所有服务都机械地按同一种方式修改

这是实际运维中非常重要的一点。

即使两个应用都位于同一个反向代理之后,它们原本的问题也可能完全不同。

例如某个应用可能是:

外部真实 IP:已经正常
LAN IP:错误
XFF:可以被伪造

另一个应用则可能是:

外部 IP:错误
LAN IP:错误
应用完全没有启用 trusted proxy

所以不能看到:

应用记录了代理 IP

就直接套同一份配置。

正确流程应该是:

1. 确认真实网络路径
2. 看边缘代理实际看到的 $remote_addr
3. 看 X-Forwarded-For 是怎样生成的
4. 看后端 TCP peer
5. 看应用最终记录的 remote IP
6. 再判断是哪一层出错

这样才能避免把一个原本正常的环节改坏。


十、一个完整的真实 IP 链路应该是什么样

对于只有一个反向代理的典型家庭服务器,可以把目标简化为:

Client
   │
   │ TCP source = real client IP
   ▼
Reverse Proxy
   │
   │ X-Real-IP: real client IP
   │ X-Forwarded-For: real client IP
   ▼
Application Server
   │
   │ Trust only Reverse Proxy
   ▼
Application
   │
   └─ Log real client IP

其中每一层承担不同职责。

反向代理负责:

确定真实网络来源
覆盖不可信 Header

后端应用负责:

只信任指定代理
解析可信代理提供的 Header

DNS 则负责:

避免 LAN 流量不必要地绕过公网 NAT

三者缺一不可。


十一、怎样验证配置到底有没有成功

仅仅“配置看起来对”是不够的。

真正可靠的验证应该至少包含三类测试。

1. 外部网络测试

例如通过蜂窝网络访问服务。

应该验证:

实际公网出口 IP
=
Reverse Proxy 记录 IP
=
Application 记录 IP

2. LAN 测试

通过正常域名访问,而不是:

直接访问内网 IP

也不要使用:

--resolve

临时绕过 DNS。

因为真正要验证的是正常用户路径。

理想结果:

LAN client = 10.0.0.50

Reverse Proxy log:
10.0.0.50

Application log:
10.0.0.50

3. XFF 伪造测试

客户端主动发送:

X-Forwarded-For: 8.8.8.8

如果实际客户端地址是:

198.51.100.25

最终应用应该仍然记录:

198.51.100.25

而不是:

8.8.8.8

这是检查“真实 IP 配置”和“真实 IP 安全配置”之间区别的关键步骤。


十二、为什么真实 IP 对安全如此重要

真实客户端 IP 并不仅仅是为了让日志“看起来更漂亮”。

它直接影响多个安全机制。

登录审计

错误:

Failed login from 10.0.0.10
Failed login from 10.0.0.10
Failed login from 10.0.0.10

只能知道有人经过代理访问。

正确:

Failed login from 198.51.100.25
Failed login from 203.0.113.45
Failed login from 198.51.100.25

才具有真正的调查价值。

暴力破解防护

很多系统根据:

IP + 失败次数 + 时间窗口

判断是否进行限制。

真实 IP 错误会导致整个机制失真。

Fail2Ban

Fail2Ban 最终需要知道:

到底应该封哪个 IP

如果日志里只有反向代理地址:

10.0.0.10

那么自动封禁就没有实际意义。

安全事件分析

攻击发生后,真正有价值的是:

攻击时间
来源 IP
请求路径
账号
User-Agent
失败/成功结果

真实 IP 是其中最基础的一项。


十三、一个容易忽视的原则:网络层 IP 与应用层 IP不是一回事

在反向代理架构中,经常会看到这样的情况:

Backend Web Server:
TCP peer = 10.0.0.10

但是:

Application:
remoteAddr = 198.51.100.25

这完全正常。

因为:

10.0.0.10

是真正连接后端服务器的机器,也就是反向代理。

而:

198.51.100.25

是反向代理通过可信 HTTP Header 还原出来的原始客户端。

因此排查问题时一定要明确区分:

TCP source IP
Proxy-observed IP
Forwarded IP
Application remote IP

如果把这些概念混在一起,很容易得出错误结论。


十四、最终可以归纳为四条原则

真实客户端 IP 配置看起来复杂,其实核心只有四条。

第一,边缘代理必须知道真正的客户端是谁。

也就是正确使用:

$remote_addr

第二,不允许客户端自行决定自己的真实 IP。

所以边缘代理应该清理或覆盖不可信的:

X-Forwarded-For

第三,应用只能信任明确指定的代理。

也就是:

trusted proxy

而不是“信任所有 Header”。

第四,局域网不要为了访问自己的服务再绕一次公网 NAT。

适合时使用:

Split DNS

直接让 LAN 用户访问内部反向代理。


结语

反向代理之后出现的“IP 全部变成代理服务器地址”,本质上不是某个应用特有的问题,而是代理网络模型本身带来的结果。

真正可靠的设计并不是简单地“开启真实 IP”,而是建立一条完整的信任链:

真实网络来源
   ↓
可信边缘代理
   ↓
不可伪造的 Forwarded Header
   ↓
应用可信代理配置
   ↓
真实客户端 IP

如果再结合 Split DNS,就可以同时解决公网和局域网访问中的地址准确性问题。

对于 WordPress、Jellyfin、Nextcloud 以及大量其他自建 Web 服务来说,这套原理都是通用的。

理解这一点之后,再面对“日志里为什么全是内网 IP”“为什么暴力破解保护识别错用户”“为什么 LAN 登录显示路由器地址”这类问题,就不再需要逐个应用碰运气式排查,而可以从整个网络链路逐层定位。

Leave a Reply

Your email address will not be published. Required fields are marked *