环境:frp 最新版,Nginx‑1.26.3,宝塔面板多站点环境;目标:公网域名穿透获取访客真实客户端 IP。 两种模式:frp vhost 七层插件模式(http2http /https2https)、全量 TCP 透传模式 (type=tcp)。
一、Vhost 七层插件模式(http2http /https2https)
工作原理
frps 对外监听 80、443,完成 HTTP/HTTPS 协议解析、域名路由;frpc 通过插件做七层转发。 frpc 内部作为 HTTP 客户端访问本机 Nginx 127.0.0.1:80/443,在 HTTP 请求头自动插入 X‑Forwarded‑For,携带公网用户真实 IP 。 Nginx 读取该 HTTP 头,通过 real_ip 模块,把真实 IP 赋值给 $remote_addr,PHP $_SERVER['REMOTE_ADDR'] 获取真实访客 IP。
本模式 frps 需要开启 vhost 端口,frpc 不需要填写
transport.proxyProtocolVersion。
frps.toml(服务端)
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "你的token"
# 开启vhost,七层模式必须
vhostHTTPPort = 80
vhostHTTPSPort = 443
frpc.toml(客户端)
Dart
# HTTP 80 vhost转发
[[proxies]]
name = "web‑http"
type = "http"
customDomains = ["xxx.com"]#你的网站域名
plugin = "http2http"
plugin.localAddr = "127.0.0.1:80"
# HTTPS 443 vhost转发
[[proxies]]
name = "web‑https"
type = "https"
customDomains = ["xxx.com"]#你的网站域名
plugin = "https2https"
plugin.localAddr = "127.0.0.1:443"
plugin.localScheme = "https"
Nginx 配置(宝塔最佳实践)
不需要修改任何
listen行,不要写proxy_protocol标记 。 将 real_ip 配置写在 nginx 主配置nginx.conf的http{}全局块,全部站点自动继承,无需逐个修改网站虚拟主机文件。
http {
#frpc vhost 获取真实IP‑start
set_real_ip_from 127.0.0.1;
real_ip_header X‑Forwarded‑For;
real_ip_recursive on;
#frpc vhost 获取真实IP‑end
# 原有nginx配置保持不变
......
include /www/server/panel/vhost/nginx/*.conf;
}
校验重载:
nginx -t
nginx -s reload
模式特点
- ✅内网可以直接访问服务器内网 IP 的 80/443 端口调试网站,连接不会重置;
- ✅宝塔多站点友好,全局配置一次,所有网站生效;
- ✅支持 HTTP3‑QUIC;
- ✅PHP
$_SERVER['REMOTE_ADDR']拿到真实公网 IP; - ✅证书可以放在内网 Nginx;也可以放在 frps;
- ⚠️IP 信息存储在 HTTP Header
X‑Forwarded‑For; - ✅安全:
set_real_ip_from 127.0.0.1,仅信任 frpc 本机传入的请求头,公网无法直连 Nginx,无法伪造 IP 头。
二、TCP 全透传模式 type=tcp(配合 Proxy‑Protocol v2)
工作原理
frps 直接转发原始 TCP 字节流,frps、frpc 不解析 HTTP/TLS,证书、SNI 全部保留在内网 Nginx 。 frpc 连接本地 Nginx 建立 TCP 连接完成后,TCP 连接首包发送 Proxy‑Protocol v2 二进制报文 ,报文二进制内部携带客户端真实公网 IP,不是 HTTP 头。 Nginx 通过listen xxx proxy_protocol;解析二进制 PP 报文,再通过 real_ip 模块将解析出来的 IP 覆盖$remote_addr。
⚠️重要:
transport.proxyProtocolVersion = "v2"只写在 frpc.toml,frps.toml 不能写该字段,会报 json: unknown field "proxyProtocolVersion"。
frps.toml(服务端)
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "你的token"
# TCP透传模式,必须注释/删除vhost端口
#vhostHTTPPort = 80
#vhostHTTPSPort = 443
frpc.toml(客户端)
# TCP透传80
[[proxies]]
name = "tcp‑80"
type = "tcp"
remotePort = 80
localIP = "127.0.0.1"
localPort = 80
transport.proxyProtocolVersion = "v2"
# TCP透传443
[[proxies]]
name = "tcp‑443"
type = "tcp"
remotePort = 443
localIP = "127.0.0.1"
localPort = 443
transport.proxyProtocolVersion = "v2"
Nginx 配置限制与写法
proxy_protocol是 listen 套接字标记,同一个 ip:port 下所有 server {} 的 listen 参数必须完全一致,全部开启或全部关闭;开源 Nginx 没有全局开关。- QUIC/UDP 443 不支持 proxy_protocol,
listen 443 quic;禁止追加该参数。
示例单站点配置:
Dart
server {
listen 80 proxy_protocol;
listen 443 ssl proxy_protocol;
listen 443 quic; # UDP QUIC保持原样
server_name xxx.com;#你的网站域名
#frpc tcp透传获取真实ip‑start
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
real_ip_recursive on;
#frpc tcp透传获取真实ip‑end
}
TCP 模式致命问题:内网直接访问服务器 80/443 端口连接重置
现象:开启
listen 80 proxy_protocol;之后,公网 frp 访问正常;公司内网直接访问服务器内网 IP:80,浏览器提示连接已重置。
原因
listen 80 proxy_protocol;标记告诉 Nginx:该 socket 上所有 TCP 连接第一个数据包必须是 Proxy‑Protocol v2 二进制报文。
- 公网流量走 frp:frpc 会发送 PP‑v2 二进制首包,Nginx 正常解析处理。
- 内网同事直接访问服务器内网 IP:浏览器发出普通 HTTP 数据包,没有 PP‑v2 二进制头。Nginx 收到非预期数据,直接关闭 TCP 连接,表现为连接重置。
开启
proxy_protocol的监听端口,不再接受普通浏览器客户端直连,仅允许上游代理设备(frpc/haproxy)接入。
解决方案
- 方案一(推荐):放弃 80/443 使用 tcp 透传,切回 vhost 七层插件模式,内网访问不受任何影响。
- 方案二:修改端口,不直接占用 80/443 frps 不占用 80、443,web 业务改用 vhost 模式;纯 TCP 业务(RDP、数据库等)使用其他 remotePort,这类业务本来就不会被内网浏览器直接访问,不会触发连接重置问题。
示例:RDP 转发
[[proxies]]
name = "rdp"
type = "tcp"
remotePort = 13389
localPort = 3389
transport.proxyProtocolVersion = "v2"
- 方案三(架构复杂,不适合宝塔):本机增加一层 HAProxy 做流量分流,内网流量直接放行,frpc 流量传入带 proxy‑protocol,再转发给 Nginx。宝塔原生环境不建议实施。
补充:宝塔多站点场景下,使用 tcp 80/443 透传会遇到额外难题:开源 nginx 强制全部虚拟主机 listen 参数完全一致,任意网站漏写
proxy_protocol,会出现a duplicate listen 0.0.0.0:80配置启动报错,维护成本极高。
三、两种模式完整对比表
表格
| 对比项 | Vhost 七层插件模式 http2http/https2https | TCP 全透传 type=tcp + Proxy‑Protocol v2 |
|---|---|---|
| IP 携带位置 | HTTP Header X‑Forwarded‑For |
TCP 连接首包二进制 Proxy‑Protocol v2 报文 |
| nginx real_ip_header | real_ip_header X‑Forwarded‑For; |
real_ip_header proxy_protocol; |
| frps 配置 | 开启vhostHTTPPort、vhostHTTPSPort |
注释关闭 vhost 系列端口 |
| frpc 配置 | plugin 插件,无transport.proxyProtocolVersion |
type=tcp,配置transport.proxyProtocolVersion = "v2",仅 frpc 配置 |
| Nginx listen 行 | 不需要修改,不加 proxy_protocol | 所有 TCP 80/443 ssl 的 listen 必须追加proxy_protocol; |
| 内网直接访问服务器 IP:80/443 | ✅正常访问,无连接重置 | ❌直接连接重置,普通客户端不能直连该端口 |
| 宝塔多站点适配 | ✅极好,http 全局写一次 real_ip 即可 | ❌很差,全部站点 listen 参数必须统一,极易启动报错 |
| HTTP3‑QUIC 支持 | ✅正常支持 | QUIC/UDP 不走 tcp 隧道,公网无法使用 |
| 证书存放位置 | frps 侧 / 内网 Nginx 均可 | 证书必须全部放在内网 Nginx,frp 不处理 TLS 解密 |
| 适用场景 | 网站 Web 业务,同时需要内网调试访问 | 纯 TCP 业务 (RDP、数据库);后端端口只给代理访问,禁止普通客户端直连 |
四、最终选型建议
- Web 网页 80/443 业务优先选择 vhost 七层插件模式 。兼顾公网真实 IP 获取、内网调试访问,适配宝塔多站点,维护简单稳定。虽然 IP 来自 HTTP 头,但
set_real_ip_from 127.0.0.1限制信任来源,安全等级足够。 - RDP、数据库等纯 TCP 业务,使用 type=tcp + Proxy‑Protocol v2,使用非 80/443 的 remotePort,避开 web 端口冲突,不会产生内网直连重置问题。
- 不建议在宝塔环境把 80/443 网页服务直接使用 type=tcp 全透传模式,会带来内网访问失效、Nginx 配置维护负担。
核心总结:Proxy‑Protocol v2 能力很强,但附带硬性约束:开启该标记的端口只能接收代理的流量,普通浏览器不能直连,鱼和熊掌不可兼得。