1662 字
8 分钟
FRP 代理服务后通过 Proxy Protocol 获取真实用户 IP

前文 AMD64 Linux 部署 FRP v0.65 内网穿透 已经介绍了安装的背景

但是使用过程中发现一个现象:
无论查看应用日志还是简单打印请求来源,所有访问看起来都是由 127.0.0.1 发起的

经过查询了解到,这并不是配置错误,而是 FRP 在默认工作模式下的正常行为

在当前的访问链路中,外部请求经过 FRPS 转发后,最终是由 FRPC 在本地建立连接并访问后端服务

对于被访问的服务而言,请求的直接来源就是 FRPC 本身,因此看到的源地址自然是 127.0.0.1

根据 官方文档 说明,如果希望在使用 FRP 的场景下获取真实的客户端 IP,需要额外启用相关机制
例如通过 HTTP 场景下的 X-Forwarded-For,或使用更通用的 Proxy Protocol

针对没有备案的域名,但是又需要获取真实用户 IP 的情况,一番折腾了是少不了的了,于是乎一篇文章又双叒叕的应运而生

一、环境说明#

IP 地址及端口作用与需求
FRPS 公网服务器服务器ip
假设为1.1.1.1
接收公网请求并转发给 FRPC
宿主机 FRPC10.211.55.2连接 FRPS 并转发公网请求
启用 Proxy Protocol v2
宿主机 Spring Boot 服务10.211.55.2:50000提供业务 API
读取X-Real-IP 获取真实 IP
虚拟机 Nginx
服务需要,也可以使用 Docker
10.211.55.100运行 Nginx解析 Proxy Protocol
注入X-Real-IP

二、修改配置文件 frpc.toml#

针对单端口内网穿透最简配置文件

serverAddr = "1.1.1.1"
serverPort = 7000
[[proxies]]
name = "my_local_server"
type = "tcp"
# 虚拟机上 Nginx 的 IP
localIP = "10.211.55.100"
# 虚拟机上 Nginx 的 HTTP 监听端口
localPort = 8080
# 公网暴露端口
remotePort = 20002
# 启用 Proxy Protocol v2
transport.proxyProtocolVersion = "v2"

手动启动 FRPC ,确保 FRPC 所在主机能访问 10.211.55.100:8080

Terminal window
./frpc -c frpc.toml

日志中出现 [my_local_server] start proxy success 即表示访问成功

三、Nginx配置#

创建一个独立的配置文件,例如/etc/nginx/conf.d/web-proxy.conf,并填写以下配置

server {
# 启用 Proxy Protocol v1/v2 接收(由 FRPC 发送)
listen 8080 proxy_protocol;
# 严格限制 set_real_ip_from :仅来自 FRPC 所在主机的 Proxy Protocol 数据
# 替换为 FRPC 实际 IP(如 10.211.55.2)
set_real_ip_from HOST_IP;
# 从 Proxy Protocol 中提取真实客户端 IP
real_ip_header proxy_protocol;
location / {
# 透传协议信息
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# 传递真实客户端 IP(由 Proxy Protocol 解析后赋值给 $remote_addr)
proxy_set_header X-Real-IP $remote_addr;
# 指定后端 Host(通常设为目标服务 IP 或保留原始 Host)
# 或改为 $http_host 以保留原始 Host
proxy_set_header Host 10.211.55.2;
# 超时设置(设置为0会导致响应504,建议设合理值)
proxy_connect_timeout 60s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
# 转发到宿主机上的 Spring Boot 服务
proxy_pass http://10.211.55.2:50000;
}
}

重载 Nginx配置

Terminal window
nginx -s reload

虚拟机内执行 ss -tuln | grep :8080 应显示监听状态

四、宿主机 Spring Boot 服务获取真实 IP 的代码示例#

private static String getClientIpAddress(HttpServletRequest request) {
// 1. 优先检查是否是 Cloudflare 代理
String cfIp = request.getHeader("CF-Connecting-IP");
if (cfIp != null && !cfIp.isEmpty() && !"unknown".equalsIgnoreCase(cfIp)) {
return cfIp;
}
// 2. 回退到标准代理头
String[] headers = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
"WL-Proxy-Client-IP"
};
// 3. 遍历标准代理头获取
for (String header : headers) {
String ip = request.getHeader(header);
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
if ("X-Forwarded-For".equals(header)) {
String[] ips = ip.split(",");
for (String ipStr : ips) {
ipStr = ipStr.trim();
if (!ipStr.isEmpty() && !"unknown".equalsIgnoreCase(ipStr)) {
return ipStr;
}
}
} else {
return ip;
}
}
}
// 4. 最终回退:直接连接的 IP (一般为 127.0.0.1 或 ::1)
return request.getRemoteAddr();
}

五、FRPS - FRPC - Nginx - Spring Boot 代理的全流程#

  1. 用户访问 http://1.1.1.1:20002

  2. FRPS 接收客户端连接,并通过 frp 隧道将该连接的数据转发给 FRPC

  3. FRPC 与本地服务建立连接后会发送 Proxy Protocol 头

  4. 虚拟机 Nginx:

    • 通过 listen ... proxy_protocol 解析出真实 IP
    • $remote_addr 设为该真实 IP
    • 通过 proxy_set_header X-Real-IP $remote_addr 传递给后端
  5. 请求被反向代理至宿主机 10.211.55.2:50000

  6. Spring Boot 通过解析标准代理头获取用户真实公网 IP

验证方法#

1. 检查 Nginx 日志#
Terminal window
tail -f /var/log/nginx/web-proxy_access.log

$remote_addr 应为用户公网 IP,而非 10.211.55.2

2. 调用测试接口#

访问 http://服务器ip:20002/test-ip,返回应为你的公网 IP

3. 通过 Spring Boot 日志打印 X-Real-IP / X-Forwarded-For / getRemoteAddr() 进行验证#

六、常见问题排查#

问题原因解决方案
Nginx 日志中 $remote_addr10.211.55.2Proxy Protocol 未生效1. 确认 FRPC 配置 proxyProtocolVersion = "v2"
2. 确认 Nginx listenproxy_protocol
连接被拒绝或超时端口冲突或防火墙拦截换用空闲端口(如 20002),检查 iptables/安全组
Spring Boot 拿不到 X-Real-IPNginx 未设置该 Header检查 proxy_set_header X-Real-IP $remote_addr;
跨域错误前端 Origin 未被允许使用 allowedOriginPatterns 配置通配规则

七、无法使用ws长连接原因以及解决方法#

在使用的过程中突然发现 WS 长连接的请求都是不成功的,但是普通 HTTP 请求都是没有问题的
修改 FRPC 直接转发不使用 Nginx ,发现 WS 的请求是没有问题的,所以问题根源还是在 Nginx 配置上

翻了些资料才搞明白,默认情况下 Nginx 反向代理会用 HTTP/1.0 协议转发请求,但 WS 长连接的建立根本离不开 HTTP/1.1 的“协议升级机制”。
HTTP/1.0 压根不支持这种升级操作,后端收不到升级信号,自然没法建立 WS 连接,所以 WS 的请求是一直没办法成功的

另外,WS 握手还有两个缺一不可的核心请求头:Upgrade: websocketConnection: Upgrade
作用是告诉后端“我要从普通 HTTP 切换到 WS 长连接了”,但如果 Nginx 配置里没明确说要透传这两个头,它就会直接把这俩关键信息丢掉。
后端没收到信号,就按普通 HTTP 请求处理,握手自然失败,WS 连接也就建不起来。

知道了原因,解决起来就简单了,核心就是补全 Nginx 配置里缺失的部分:
首先,在配置文件的 server 块上方加动态适配规则,让 Nginx 能自动区分普通 HTTP 和 WS 请求:

map $http_upgrade $connection_upgrade {
default keep-alive;
websocket upgrade;
}

然后在 server 块的 location 块里,补充这几行关键配置:

# 强制使用 HTTP/1.1,支持 WS 升级机制
proxy_http_version 1.1;
# 透传 WS 升级头,不丢失握手信号
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 拉长超时时间,避免 60 秒无数据自动断开
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;

改完这些配置重启 Nginx 再试,WS 长连接就能稳定建立了

FRP 代理服务后通过 Proxy Protocol 获取真实用户 IP
https://www.self4m.com/posts/frp-real-client-ip-proxy-protocol/
✍️作者
Self4m
📅发布于
2025-12-15
©️许可协议
CC BY-NC-SA 4.0

商业用途必须事先获得作者授权;
非商业用途可以使用,但必须注明出处;
若有改编需采用相同许可协议发布。