前言
很多人在用 frp 做内网穿透时,都会踩到两个经典坑:
- 网站接入 Cloudflare 之后,访问直接报
HTTP ERROR 526 - 即使网站能打开,宝塔、Nginx、WordPress、日志里看到的也不是用户真实 IP,而是
127.0.0.1、FRP 节点 IP,或者一堆代理层地址
这类问题表面上像是 frpc.toml 写法不对,实际上真正的根源,往往是三件事缠在一起:
Cloudflare 的源站证书校验、frp 的 HTTPS 代理方式、以及 Nginx 对 Proxy Protocol 的解析配置。
如果你刚好也在用 Cloudflare、frp 的 http/https 代理、宝塔面板自带 Nginx,并且希望在站点日志、应用层里拿到真实用户 IP,那这篇文章就是一份比较完整的排查和配置思路。
一、先说结论:问题到底出在哪
1. Cloudflare 526 不一定是 frp 挂了
很多人第一反应是 frp 配错了。实际上,526 更常见的原因是:Cloudflare 连到了源站 HTTPS,但源站证书不被 Full (strict) 模式接受。
2. frp 能传递真实 IP,但 Nginx 必须会“接”
transport.proxyProtocolVersion = "v2" 不是装饰项。它的意义是:frpc 会在连接本地服务时,先发送一段 Proxy Protocol 头,把真实访客 IP 带过去。
3. Nginx 不解析 Proxy Protocol,HTTPS 就可能直接异常
如果要接收 PROXY protocol,Nginx 的 listen 指令必须加 proxy_protocol,并通过 real_ip_header proxy_protocol; 告诉 Nginx 从 Proxy Protocol 中读取真实 IP。否则,Nginx 会把那段 PROXY 头当成普通流量,轻则真实 IP 拿不到,重则 HTTPS 握手异常。
也就是说:frp 负责传,Nginx 负责收,Cloudflare 负责校验证书。 这三层只要有一层没对齐,网站就会出问题。
二、这个问题最容易出现的典型场景
假设链路如下:
用户 → Cloudflare → frps 服务器 → frpc → 宝塔 Nginx → 站点程序
如果你用了 type = "https" 代理,这类 HTTPS 代理本质上是把请求转给本地 HTTPS 服务,本地服务自己处理 TLS,不是由 frps 在远端帮你终止 TLS。也就是说,Cloudflare 最终看到的,仍然是你本地 Nginx 的 443 端口返回的证书。
因此这里会立刻引出两个要求:
- 本地 443 必须真的能正常提供 HTTPS
- 这张证书必须能通过 Cloudflare
Full (strict)的校验
如果证书不合规,Cloudflare 就会报 526;如果你又同时开了 transport.proxyProtocolVersion = "v2",但宝塔 Nginx 没配 proxy_protocol,那 frp 带过去的真实 IP 也会失效,甚至把站点搞得更不稳定。
三、正确思路:先把链路拆开理解
很多人一上来就疯狂改 frpc.toml,其实更有效的方法是先分层排查。
第一步:确认 Cloudflare 的 526 是证书问题,不是 frp 语法问题
你要先确认:
- 源站 443 上是否真的有证书
- 证书是否过期
- 证书是否覆盖当前访问域名
- 证书链是否完整
- Cloudflare 是否启用了
Full (strict)
第二步:确认 frp 是否真的在给本地服务发送 Proxy Protocol
只有在代理项里显式加了:
transport.proxyProtocolVersion = "v2"
frpc 才会在连接本地服务后发送原始 IP 信息。
第三步:确认宝塔 Nginx 是否已经准备好接收 Proxy Protocol
要接收 Proxy Protocol,listen 必须带 proxy_protocol,而真实 IP 的来源要设为 proxy_protocol。
四、frps 端应该怎么配
如果你要让 frp 代理 HTTP 和 HTTPS,frps 端必须启用对应的 vhost 端口。
一个常见的 frps.toml 参考如下:
bindPort = 1234
vhostHTTPPort = 80
vhostHTTPSPort = 443
auth.method = "token"
auth.token = "your-token"
这里最关键的是:
bindPort是 frpc 连接 frps 的控制端口vhostHTTPPort = 80用于 HTTP 代理vhostHTTPSPort = 443用于 HTTPS 代理
如果你没开 vhostHTTPSPort = 443,HTTPS 类型代理就无法正常接流量。
五、frpc.toml 正确写法:关键就在这里
下面这份配置,更适合 Cloudflare + 宝塔 + Nginx + frp 的思路。
frpc.toml 示例
serverAddr = "your-frps-server"
serverPort = 1234
auth.method = "token"
auth.token = "your-token"
[[proxies]]
name = "site_http"
type = "http"
localIP = "127.0.0.1"
localPort = 80
customDomains = ["example.com", "*.example.com"]
transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "site_https"
type = "https"
localIP = "127.0.0.1"
localPort = 443
customDomains = ["example.com", "*.example.com"]
transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
这里有两个特别容易被忽略的点。
1. transport.proxyProtocolVersion = "v2" 要按代理分别写
它不是全局默认继承项,而是代理级配置。这意味着:
- 你希望 HTTP 代理带真实 IP,就在
type = "http"那段里写 - 你希望 HTTPS 代理也带真实 IP,就在
type = "https"那段里也写
你想让两个代理都发送 Proxy Protocol,就要写两遍。
2. 不要一边开 Proxy Protocol,一边让本地 Nginx 继续按普通监听处理
这是最多人翻车的地方。frpc 发了 v2 头,但宝塔 Nginx 的 80/443 没有开启 proxy_protocol 解析,结果就是:
- 真实 IP 还是拿不到
- HTTPS 可能异常
- 有时还会引发 Cloudflare 层面的握手问题
六、宝塔 Nginx 怎么配:这是拿到真实 IP 的关键
如果 frpc 就跑在本机,那么宝塔站点的 server 配置里,可以参考这样写:
server
{
listen 80 proxy_protocol;
listen 443 ssl http2 proxy_protocol;
server_name example.com *.example.com;
ssl_certificate /www/server/panel/vhost/cert/example.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/example.com/privkey.pem;
real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;
root /www/wwwroot/your-site-directory;
index index.php index.html index.htm default.php default.htm default.html;
# 其余宝塔默认伪静态、PHP、Rewrite 配置继续保留
}
这里再强调两个细节。
1. set_real_ip_from 要写 frpc 连接本地 Nginx 时使用的来源地址
如果 frpc 在本机,把流量转发给本地 Nginx,通常就是:
set_real_ip_from 127.0.0.1;
只有来自你信任来源的 PROXY protocol 信息,才应该被用于替换客户端地址。
2. 开了 listen 443 ssl http2 proxy_protocol; 后,Nginx 这个端口就会期待收到 PROXY 头
也就是说,这个 443 不再适合被普通客户端直接裸连测试。因为 Nginx 现在默认认为接进来的连接前面应该带一段 Proxy Protocol 头。
七、为什么以前拿不到真实 IP
这个问题本质上就是“发”和“收”没有对上。
frp 这边已经把真实 IP 用 Proxy Protocol 发出去了,但你的宝塔站点没有:
listen 80 proxy_protocol;
listen 443 ssl http2 proxy_protocol;
real_ip_header proxy_protocol;
于是 Nginx 根本不知道该从哪里取真实 IP。也就是说,不是 frp 不行,而是 Nginx 没接住。
八、为什么以前会报 Cloudflare 526
这个也很好理解。你用了 type = "https" 代理,Cloudflare 最终连到的,是你本地 443 提供的 HTTPS 服务;而 Cloudflare 在 Full (strict) 模式下,会严格验证源站证书。证书不合规,就会报 526。
所以 526 的根本解决思路不是盲目改 frp,而是先把源站证书这层打通:
- 证书必须覆盖对应域名
- 证书必须可被 Cloudflare 验证
- 证书链必须完整
- 不要使用不受信任的自签证书直接上线
尤其注意一个常见误区:
*.example.com 不能覆盖 example.com 根域。
如果你同时要跑根域和泛子域,通常证书得同时包含:
example.com*.example.com
如果还要接入其他不同主域名,也需要确保对应证书或 SAN 覆盖完整,否则在严格模式下同样可能出错。
九、完整排障顺序:建议按这个来
- 先确保 frps 开了 vhost 端口
- 确认 frpc 的 HTTP 和 HTTPS 代理都加了
transport.proxyProtocolVersion = "v2" - 在宝塔站点的 Nginx 配置里开启
proxy_protocol - 确认本地 443 的证书正常
- 最后再看应用层对真实 IP 的读取逻辑
十、真正有效的解决思路
真正能把这个问题彻底解决的,不是继续怀疑 frpc.toml 语法,而是把这三个认知对齐:
- Cloudflare 526 的本质是源站证书校验失败,不是“frp 不能用”
- frp 的
transport.proxyProtocolVersion = "v2"确实能把真实 IP 带到本地服务,但这是“发出端”的动作 - 宝塔 Nginx 必须在站点监听上显式启用
proxy_protocol并用real_ip_header proxy_protocol;解析它,否则真实 IP 还是接不到
把这三件事同时做好之后,问题通常就能彻底打通:
- Cloudflare 不再报 526
- frpc.toml 正常工作
- 宝塔 Nginx 与站点程序都能正确拿到用户真实 IP
十一、可直接参考的最终配置
frps.toml
bindPort = 1234
vhostHTTPPort = 80
vhostHTTPSPort = 443
auth.method = "token"
auth.token = "your-token"
frpc.toml
serverAddr = "your-frps-server"
serverPort = 1234
auth.method = "token"
auth.token = "your-token"
[[proxies]]
name = "site_http"
type = "http"
localIP = "127.0.0.1"
localPort = 80
customDomains = ["example.com", "*.example.com"]
transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "site_https"
type = "https"
localIP = "127.0.0.1"
localPort = 443
customDomains = ["example.com", "*.example.com"]
transport.proxyProtocolVersion = "v2"
宝塔站点 Nginx
server
{
listen 80 proxy_protocol;
listen 443 ssl http2 proxy_protocol;
server_name example.com *.example.com;
ssl_certificate /www/server/panel/vhost/cert/example.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/example.com/privkey.pem;
real_ip_header proxy_protocol;
set_real_ip_from 127.0.0.1;
root /www/wwwroot/your-site-directory;
index index.php index.html index.htm default.php default.htm default.html;
}
十二、最后提醒:这几个坑千万别再踩
- 不要只在 frp 开
transport.proxyProtocolVersion = "v2",却不在 Nginx 上开proxy_protocol - 不要把 Cloudflare 526 当成 frp 语法错误
- 不要忽略源站证书覆盖范围
- 不要以为 HTTP 和 HTTPS 两个代理中,只写一处
transport.proxyProtocolVersion = "v2"就能全局生效
结语
这个问题之所以折腾人,不是因为它多难,而是因为它刚好卡在三个系统的交界处:Cloudflare、frp、Nginx。任何一层理解偏一点,最后都会表现成“网站打不开”“526”“真实 IP 失效”“frpc.toml 像是没生效”。
但一旦把链路想明白,其实配置并不复杂。
一句话总结:frp 负责把真实 IP 送到本地,Nginx 负责正确解析它,Cloudflare 负责验证你的源站证书。三层对齐,问题就通了。