解决 Cloudflare 525 错误:Apache 反代 WebSocket (wss) 到 Ratchet
2025-03-16 22:07:28
解决 Cloudflare 525 错误:Apache 反向代理 WebSocket (wss) 到 Ratchet
搞个聊天应用,用了 Ratchet 做 WebSocket 服务器。结果遇到 Cloudflare 报 525 错误,说 SSL 握手失败。头疼!这篇博客就来捋一捋怎么解决这个问题。
一、 问题
简单来说,就是客户端 (JavaScript) 用 wss://
连接,通过 Cloudflare 和 Apache 反向代理,最终要连到 Ratchet WebSocket 服务器。但是 Cloudflare 报 525 错误,连不上。
客户端 JavaScript 代码大概长这样:
ws = new WebSocket("wss://meetwithchat.com/ws");
Apache 的配置,把 /ws
路径的请求反向代理到本地的 127.0.0.1:2053
:
<VirtualHost *:443>
ServerName meetwithchat.com
ServerAlias www.meetwithchat.com
DocumentRoot /var/www/meetwithchat
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/meetwithchat.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/meetwithchat.com/privkey.pem
# 反向代理 WebSocket
ProxyPreserveHost On
ProxyPass /ws ws://127.0.0.1:2053/
ProxyPassReverse /ws ws://127.0.0.1:2053/
ErrorLog ${APACHE_LOG_DIR}/meetwithchat_error.log
CustomLog ${APACHE_LOG_DIR}/meetwithchat_access.log combined
</VirtualHost>
Ratchet 服务器跑在 127.0.0.1:2053
,没有 用 TLS (就是普通的 ws://
):
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\Server\IoServer;
use React\Socket\Server as Reactor;
require __DIR__ . '/vendor/autoload.php';
class ChatServer implements MessageComponentInterface {
// ... (实现细节,比如 onOpen, onMessage 等)
}
// Ratchet 服务器设置 (本地用普通的 ws)
$loop = React\EventLoop\Factory::create();
$socket = new Reactor('127.0.0.1:2053', $loop);
$webSocket = new WsServer(new ChatServer());
$httpServer = new HttpServer($webSocket);
$server = new IoServer($httpServer, $socket, $loop);
error_log("WebSocket server is running on port 2053 (plain ws)...");
$server->run();
?>
二、问题原因分析
Cloudflare 525 错误通常表示 Cloudflare 无法与源服务器建立 SSL 连接。 结合我们的架构, 几个可能的原因:
- Cloudflare 和 Apache 之间的 SSL/TLS 配置问题: Cloudflare 尝试用 HTTPS 连接 Apache (因为客户端用的是
wss://
),但 Apache 可能没有正确处理这个 WebSocket 的 SSL 连接。 - Apache 反向代理配置问题: Apache 的反向代理配置可能不完整,或者有错误,导致它无法正确地将 WebSocket 请求转发到 Ratchet 服务器。
- Ratchet 服务器没有启用 TLS: 虽然 Apache 到 Ratchet 之间可以用普通的
ws://
,但是 Cloudflare 到 Apache 之间必须是wss://
。问题可能出在第一段。 - 防火墙或安全组规则: 防火墙阻止了 Cloudflare 到服务器的连接。
三、 解决方案
针对上面分析的原因,咱们一个个来解决。
1. 确保 Cloudflare 到 Apache 的连接是 HTTPS
- 检查 Cloudflare 的 SSL/TLS 设置: 在 Cloudflare 的 Dashboard 里,找到 "SSL/TLS" 选项卡。
- "Encryption mode" 应该设置为 "Full (strict)" 或 "Full"。如果是 "Flexible",改成 "Full (strict)"。("Flexible" 不安全,它允许 Cloudflare 和你的服务器之间使用不加密的 HTTP)。
- "Full (strict)" 要求源服务器有受信任的 CA 签发的证书,或者 Cloudflare Origin CA 证书。"Full" 则只要求源服务器有证书(可以是自签名的)。一般推荐用"Full (strict)" 。
- 在Edge Certificates 标签页下确认证书正常.
- 在Origin Server 标签页中安装Cloudflare Origin CA 证书 (可选, 建议) Cloudflare 提供免费的 Origin CA 证书, 专门用来加密 Cloudflare 和源服务器之间的连接. 这个证书只会被 Cloudflare 信任.
- 下载 Cloudflare Origin CA 证书(RSA 或 ECC 版本)。
- 将证书和私钥安装到你的 Apache 服务器。
- 在 Apache 配置文件中,更新
SSLCertificateFile
和SSLCertificateKeyFile
指向新的证书和私钥文件。
2. 完善 Apache 反向代理配置
Apache 的配置看起来基本没问题,但还可以做一些优化:
<VirtualHost *:443>
ServerName meetwithchat.com
ServerAlias www.meetwithchat.com
DocumentRoot /var/www/meetwithchat
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/meetwithchat.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/meetwithchat.com/privkey.pem
# 反向代理 WebSocket
ProxyPreserveHost On
# 明确指定 Upgrade 和 Connection 头
RequestHeader set Connection "Upgrade"
RequestHeader set Upgrade "websocket"
ProxyPass /ws ws://127.0.0.1:2053/
ProxyPassReverse /ws ws://127.0.0.1:2053/
# 更安全的 RewriteRule (可选,但推荐)
# RewriteEngine On
# RewriteCond %{HTTP:Connection} Upgrade [NC]
# RewriteCond %{HTTP:Upgrade} websocket [NC]
# RewriteRule /(.*) ws://127.0.0.1:2053/$1 [P,L]
ErrorLog ${APACHE_LOG_DIR}/meetwithchat_error.log
CustomLog ${APACHE_LOG_DIR}/meetwithchat_access.log combined
</VirtualHost>
RequestHeader set Connection "Upgrade"
和RequestHeader set Upgrade "websocket"
: 明确告诉 Apache,这是一个 WebSocket 连接,需要特殊处理。 这两个指令通常是必需的, 不然连接可能被当做普通 HTTP 请求.RewriteEngine On
... (注释掉的部分): 这是一种更灵活的反向代理 WebSocket 的方式,使用RewriteRule
。通常情况下,ProxyPass
足够了,但如果遇到更复杂的情况,可以用RewriteRule
。不过对于这个简单例子, ProxyPass已经可以胜任了.- 确认mod_proxy_wstunnel已开启。
a2enmod proxy_wstunnel
。 然后重启 Apache:systemctl restart apache2
.
3. (进阶) Ratchet 服务器启用 TLS (可选,但更安全)
虽然你的需求是 Apache 来处理 TLS,但在 Ratchet 服务器端也启用 TLS,可以增加一层安全性 (端到端加密)。 这意味着 Apache 和 Ratchet 之间也会用 wss://
。
要让 Ratchet 支持 wss://
,需要用到 ReactPHP 的 SecureServer
:
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\Server\IoServer;
use React\Socket\Server as Reactor;
use React\Socket\SecureServer; // 引入 SecureServer
require __DIR__ . '/vendor/autoload.php';
class ChatServer implements MessageComponentInterface {
// ...
}
$loop = React\EventLoop\Factory::create();
// 使用你的证书文件路径
$secureSocket = new SecureServer(
new Reactor('0.0.0.0:2053', $loop), // 监听所有 IP, 方便测试. 生产环境最好还是 127.0.0.1
$loop,
[
'local_cert' => '/etc/letsencrypt/live/meetwithchat.com/fullchain.pem',
'local_pk' => '/etc/letsencrypt/live/meetwithchat.com/privkey.pem',
'verify_peer' => false, // 在服务器端通常不需要验证客户端证书
'verify_peer_name' => false,
]
);
$webSocket = new WsServer(new ChatServer());
$httpServer = new HttpServer($webSocket);
$server = new IoServer($httpServer, $secureSocket, $loop);
error_log("WebSocket server is running on port 2053 (wss)...");
$server->run();
SecureServer
: 用它来代替原来的Reactor
。local_cert
和local_pk
: 指向你的 Let's Encrypt 证书和私钥文件。- 注意这里用了
0.0.0.0:2053
, 这会让Ratchet监听所有接口的2053端口. 这样方便你直接用openssl s_client
测试(不需要通过Cloudflare 和 Apache), 来确认Ratchet 的 wss 是否正常. 确认正常后, 可以改回127.0.0.1:2053
, 避免直接暴露到公网.
如果 Ratchet 启用了 TLS,记得修改 Apache 的 ProxyPass
和 ProxyPassReverse
,把 ws://
改成 wss://
:
ProxyPass /ws wss://127.0.0.1:2053/
ProxyPassReverse /ws wss://127.0.0.1:2053/
4. 检查防火墙和安全组
确保你的服务器防火墙 (比如 ufw
、iptables
) 或者云服务商的安全组规则允许 Cloudflare 的 IP 地址访问你的服务器的 443 端口 (或者你自定义的其他端口)。 Cloudflare 的 IP 地址列表可以在这里找到: https://www.cloudflare.com/ips/
如果需要更细粒度的控制, 可以根据Cloudflare提供的IP列表, 只允许这些IP访问.
5. 其他
- 检查 Apache 和 Ratchet 的日志: 查看错误日志,可能会有更详细的错误信息,帮助你定位问题。
- 简化测试: 先不要通过 Cloudflare,直接在服务器上用
openssl s_client
测试 Apache 的 SSL/TLS 配置是否正确:
如果这个命令失败,说明 Apache 的 SSL/TLS 配置有问题。 成功的话, 再尝试连接 websocket.openssl s_client -connect localhost:443 -servername meetwithchat.com
四、 总结
按照上面的步骤排查和修改,应该能解决 Cloudflare 525 错误。记住,关键在于确保 Cloudflare 到 Apache,Apache 到 Ratchet 的每一段连接都正确配置了 SSL/TLS。 如果在 Ratchet 也启用了 TLS,就实现了端到端加密,更安全。