最终还是没有解决莫名其妙的重定向循环问题。隔个一两天就来一次,重启Apache就好了,不知道具体原因。但是大概率是https和http之间难舍难分的纠葛,所以选择了frps和frpc双SSL认证,用mkcert自签发证书。
让frps正确监听和转发HTTPS真是废了不少功夫,好累。
WordPress & frp 穿透架构循环重定向及 502 错误排查报告
报告日期: 2025年7月11日;记录者:gemini-2.5-pro
一、 初始问题与系统架构
- 初始问题: 外部通过域名 https://fanyiming.life 访问部署在本地 Windows XAMPP 环境下的 WordPress 站点时,出现无限循环重定向。
- 系统架构:
- 客户端: 浏览器
- 云服务器 (39.105.184.3): Nginx (处理 SSL/TLS) -> frps 服务
- 内网穿透: frps <-> frpc
- 本地服务器: frpc -> Windows XAMPP (Apache + PHP + WordPress)
二、 核心排查历程与关键发现
整个排查过程曲折但逻辑清晰,我们一步步剥离问题,最终定位到根源。
- 阶段一:怀疑 WordPress 协议判断错误
- 现象: 无限重定向。
- 分析: 这是典型的 WordPress 在反向代理后无法正确判断客户端协议 (HTTP/HTTPS) 导致的。虽然 Nginx 配置了 X-Forwarded-Proto 等头部,但问题依然存在,暗示更深层次的代理链问题。
- 阶段二:尝试建立端到端 HTTPS 隧道,遭遇 wrong version number
- 目标: 为简化协议,我们决定让 Nginx 到 frps,再到 frpc,全程使用 HTTPS。
- 操作: 配置 frps.ini 使用 vhost_https_port = 8080。
- 关键发现 (1): curl https://127.0.0.1:8080 在 frps 服务器上返回 error:0A00010B:SSL routines::wrong version number。同时 curl http://… 能返回 frp 的页面。这铁证如山地证明了 frps 的 8080 端口当时仍在监听 HTTP 协议,而非 HTTPS。
- 结论: frps 的 vhost_https_port 参数本身不会启用 TLS,必须手动配置证书。
- 阶段三:为 frps 配置证书,遭遇“幽灵”问题
- 操作: 使用 mkcert 生成自签名证书,并在 frps.ini 中配置 vhost_https_cert_file 和 vhost_https_key_file。
- 关键发现 (2): 即使配置了证书,并通过 systemctl restart frps.service 重启服务,wrong version number 错误依然存在。systemd 的日志显示“https service listen on …”,但 curl 的实际表现却与日志完全矛盾。
- 结论: frps v0.51.3 在您的系统环境中存在一个严重的 Bug 或兼容性问题,导致其无法正确应用 TLS 设置到监听端口,尽管它声称已经这么做了。
- 阶段四:升级 frps,遭遇 unrecognized name
- 操作: 将 frps 从 v0.51.3 大幅升级到 v0.63.0。
- 关键发现 (3): 升级后,wrong version number 错误消失!取而代之的是新错误 error:0A000458:SSL routines::tlsv1 unrecognized name。这是一个巨大进步,证明新版 frps 已成功启用了 HTTPS。
- 结论: 新错误是 SNI (Server Name Indication) 问题。frps 现在要求客户端在 TLS 握手时必须指明要访问的域名(fanyiming.life),而 curl 和 Nginx 直接访问 127.0.0.1,没有提供这个信息。
- 阶段五:解决 SNI 问题,大功告成
- 操作:
- 修改 curl 测试命令,使用 –resolve 参数来模拟域名访问。
- 修改 Nginx 代理配置,添加 proxy_ssl_server_name on; 和 proxy_ssl_name fanyiming.life;,使其在代理时向 frps 表明身份。
- 结果: Nginx 成功连接到 frps,502 Bad Gateway 错误消失,网站访问成功。
三、 根本原因分析
- 主要元凶: frps v0.51.3 的一个 Bug/兼容性问题,它无法在特定环境下正确启用 vhost_https_port 的 TLS 功能。
- 次要原因: Nginx 反向代理 SNI 配置缺失。在后端服务(新版 frps)要求 SNI 的情况下,Nginx 未配置相应的 proxy_ssl_server_name,导致连接被拒绝。
- 表面现象: WordPress 循环重定向和 Nginx 502 错误,均是由上述两个核心问题引发的连锁反应。
四、 最终解决方案
升级 frp: 将云服务器上的 frps 程序从 v0.51.3 升级到 v0.63.0。
frps.ini 配置
[common]
bind_port = 7000
vhost_https_port = 8080
token = ...
vhost_https_cert_file = /path/to/selfsigned.pem
vhost_https_key_file = /path/to/selfsigned.key
Nignx代理配置
location / {
proxy_pass https://127.0.0.1:8080;
proxy_ssl_verify off;
proxy_ssl_server_name on; # 关键:开启 SNI
proxy_ssl_name fanyiming.life; # 关键:指定 SNI 名称
# 其他头部设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ...
}
本地 frpc 配置: type 需设置为 https,local_port 指向本地 WordPress 服务的 HTTPS 端口。
五、 总结与建议
- 当服务的日志输出与实际网络行为不符时,应高度怀疑软件本身的版本 Bug 或环境兼容性问题,果断升级是最高效的解决手段。
- wrong version number 是协议不匹配的典型错误;unrecognized name 是 SNI 配置问题的典型错误。记住这两个特征能极大提高排查效率。
- 在复杂的代理链中,确保每一环节的协议(HTTP/HTTPS)和主机名(SNI)都正确传递,是保证系统稳定运行的核心。
再次祝贺问题圆满解决!这次排查非常有价值。
配置思路:
- 浏览器 → frps:HTTPS(公认CA证书)
- frps → frpc:frp的HTTPS隧道(frp自带TLS加密)
- frpc → 本地XAMPP:HTTPS(mkcert证书)
- type = https 表示 frp 隧道本身用 TLS 加密,frps和frpc之间的流量不会被明文传输。
- frps 监听 443 端口,使用公认CA证书,外部浏览器100%信任。
- frps 收到 HTTPS 请求后,解密,再通过 frp 的 https 隧道加密转发到 frpc。
- frpc 收到后,转发到本地 XAMPP 的 443 端口(mkcert证书)。
- 全链路加密:浏览器→frps(HTTPS),frps→frpc(frp HTTPS隧道),frpc→XAMPP(HTTPS)。
- 浏览器100%信任:外部用户无警告。
- 本地开发体验好:本地mkcert证书,浏览器信任。
- 安全性极高:即使公网、家庭宽带都不会有明文流量。
本地安装自签发证书
PS C:\xampp\mkcert (local_SSL)> .\mkcert-v1.4.4-windows-amd64.exe --install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
Note: Firefox support is not available on your platform. ℹ️
PS C:\xampp\mkcert (local_SSL)> .\mkcert-v1.4.4-windows-amd64.exe fanyiming.life localhost 127.0.0.1 ::1
Created a new certificate valid for the following names 📜
- "fanyiming.life"
- "localhost"
- "127.0.0.1"
- "::1"
The certificate is at "./fanyiming.life+3.pem" and the key at "./fanyiming.life+3-key.pem" ✅
It will expire on 11 October 2027 🗓
PS C:\xampp\mkcert (local_SSL)> copy fanyiming.life+3.pem C:\xampp\apache\conf\ssl.crt\fanyiming.life.crt
PS C:\xampp\mkcert (local_SSL)> copy fanyiming.life+3-key.pem C:\xampp\apache\conf\ssl.key\fanyiming.life.key
PS C:\xampp\mkcert (local_SSL)>
然后要记得在Apache配置虚拟主机。
C:\xampp\apache\conf\extra\httpd-ssl.conf
# 添加您的域名SSL虚拟主机
<VirtualHost fanyiming.life:443>
DocumentRoot "C:/xampp/htdocs"
ServerName fanyiming.life
ServerAlias www.fanyiming.life
# SSL配置
SSLEngine on
SSLCertificateFile "conf/ssl.crt/fanyiming.life.crt"
SSLCertificateKeyFile "conf/ssl.key/fanyiming.life.key"
# PHP支持
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
# 错误和访问日志
ErrorLog "C:/xampp/apache/logs/fanyiming.life_error.log"
CustomLog "C:/xampp/apache/logs/fanyiming.life_access.log" common
# 设置安全头
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
</VirtualHost>
打开 C:\xampp\apache\conf\httpd.conf,找到并取消注释以下两行:
httpd.conf
LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf
云服务器上frps也需要一个mkcert证书(因为现在frps和frpc之间用https加密通讯)
shell
root@Eamon:/usr/local/frp/frp_0.63.0_linux_amd64# ll
total 19288
drwxr-xr-x 2 mysql smmta 4096 Jul 11 14:27 ./
drwxr-xr-x 4 root root 4096 Jul 11 14:10 ../
-rwxr-xr-x 1 mysql smmta 19714200 Jun 25 12:10 frps*
-rw-r--r-- 1 root root 232 Jul 11 14:10 frps.ini
-rw-r--r-- 1 mysql smmta 11358 Jun 25 12:14 LICENSE
-rw------- 1 root root 1704 Jul 11 14:10 selfsigned.key
-rw-r--r-- 1 root root 1444 Jul 11 14:10 selfsigned.pem
root@Eamon:/usr/local/frp/frp_0.63.0_linux_amd64#
frps服务端配置文件:
frps.ini
root@Eamon:/usr/local/frp/frp_0.63.0_linux_amd64# cat frps.ini
[common]
bind_port = 7000
vhost_https_port = 8080 # 注意必须是vhost_https_port而不是vhost_http_port
token = 4xxxxxxxxxxxxxxxm
vhost_https_cert_file = /usr/local/frp/frp_0.51.3_linux_amd64/selfsigned.pem # 证书必须加
vhost_https_key_file = /usr/local/frp/frp_0.51.3_linux_amd64/selfsigned.key
frpc.ini
[common]
server_addr = [ip地址]
server_port = 7000 (是云服务器上的7000端口,用于与frps通讯)
token = 4xxxxxxxxxxxxxxxxxxxxxm
# 设置 SOCKS5 代理(有时我想在博客上集成gemini需要梯子)
# http_proxy = socks5://127.0.0.1:1080
log_file = ./frpc.log
log_level = info
# --- 每个代理是一个独立的节,节名就是代理名 ---
[web]
type = https
local_port = 443 # 这个443是本地windows上面Apache监听的端口,跟云服务器无关
custom_domains = fanyiming.life,www.fanyiming.life
xxxx_proxy.conf
#PROXY-START/
location ^~ /
{
add_header X-Nginx-Scheme $scheme always;
proxy_pass https://127.0.0.1:8080; # 转发到frps服务器正在监听的8080端口(注意因为从443加密转发来的,必须用https开头不能是http)
# --- 新增下面这两行,因为frps只给fanyiming.life这个域名配置了证书,不然无法通过SSL认证 ---
proxy_ssl_server_name on;
proxy_ssl_name fanyiming.life;
proxy_ssl_verify off; # 关键!关闭证书校验(因为我们是自签发证书,这里不关闭也通不过认证)
proxy_set_header Host $host; # 传递客户端(浏览器)请求的名字
proxy_set_header X-Forwarded-Host $host; # 传递客户端(浏览器)请求的标准主机名
proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实ip
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 传递客户端ip和代理的ip
# --- 关键修改:添加/确保正确的协议和端口头 ---
proxy_set_header X-Forwarded-Proto $scheme; # 传递协议
proxy_set_header X-Forwarded-Port $server_port; # 传递监听端口
# proxy_set_header REMOTE-HOST $remote_addr; # 这个头通常不是必需的
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # 修改为 "upgrade" 以支持 WebSocket
proxy_http_version 1.1;
proxy_redirect off; # wordpress让关闭重定向
}
#PROXY-END/
~