从 Cloudflare 到 Let's Encrypt:一次 HTTPS 加速迁移
写在前面
去年底我把个人服务全部搬到了一台上海腾讯云轻量服务器上,域名通过 Cloudflare(下文简称 CF)托管,开了橙色云(Proxied)模式——就是那个 DNS 记录旁边的小橙开关。全程 HTTPS、CDN 加速、DDoS 防护,看起来很美好。
直到我注意到一个诡异的现象:每次打开我的博客或者 API 服务,首屏加载都要等好几秒。一开始以为是服务器配置不够,后来用 Chrome DevTools 看了下网络面板——TTFB(Time to First Byte)高达 4-6 秒。
而我的服务器就在上海,用户也在国内。这个延迟显然不正常。
于是我做了一个简单的对比测试:绕开 CF 代理,直接用服务器 IP 请求同一个页面。
结果让我瞬间清醒。
一、问题:那个橙色云的真相
CF 免费版的路线
CF 免费用户的流量不走它的 premium 网络(Argo Smart Routing 要额外付费),而是走共享路由。当一个中国用户访问你在 CF 上开启了代理的域名时,实际请求路径是:
用户浏览器 → CF 海外边缘节点(通常是美西或日本) → CF 后端网络 → 上海服务器简单说就是:你的中国用户的数据包要先飞到海外,再折返回来。而且 CF 免费版在中国大陆没有边缘节点,最近的节点在日本或美国西海岸,再加上 CN2 线路在国际出口的拥堵,延迟曲线非常难看。
我在迁移前测到的数据
我选了五个维度做了 10 次重复测试,取中位数(单位:秒):
| 指标 | CF 代理 | 直连 IP | 差距 |
|---|---|---|---|
| TTFB | 4.8s | 0.3s | 16x |
| 首字节到达 | 5.2s | 0.4s | 13x |
| 完整页面加载(简单页面) | 3.1s | 0.5s | 6.2x |
| 完整页面加载(API 响应) | 6.6s | 0.4s | 16.5x |
| TLS 握手 | 1.8s | 0.08s | 22.5x |
最离谱的是 TLS 握手。CF 代理模式下,TLS 握手需要在用户和 CF 边缘节点之间完成一次,然后 CF 再和服务器建立另一条 TLS 连接,相当于两次 TLS 握手,再加上国际线路的 RTT(Round-Trip Time),1.8 秒就没了。
而直连 IP 模式下,TLS 握手只需要 80 毫秒——因为服务器就在上海,用户也在国内。
这还不是全部
除了速度,还有几个隐性成本:
- 证书问题:CF 代理模式必须用 CF 颁发的 Origin CA 证书,无法使用自己的证书
- 端口限制:CF 代理只支持 80/443 等有限几个端口,如果你想跑非标准端口就得用灰色云
- SSL 终止在 CF 侧:意味着 CF 可以解密你的流量——对绝大多数场景这不是问题,但如果你对数据主权有要求,这是一个需要接受的现实
二、方案:灰色云 + Let’s Encrypt
为什么不做完全去掉 CF?
我仍然继续用 CF 做 DNS 托管——它的 DNS 解析服务本身非常快(全球 Anycast 网络),我只需要把代理功能关掉就行。灰色云模式下,CF 只做 DNS 解析,流量直接从用户到服务器,不经过 CF 的网络。
代价是失去了 CF 的免费 DDoS 防护和 CDN 缓存。但对于轻量服务器上的个人服务来说,这个权衡是值得的——速度是第一位的。
第一步:所有 A 记录切到灰色
在 CF Dashboard 上,把每个域的 DNS 记录从橙色云 ⚡️ 改为灰色云 🗯️。我的域名 outsider-studio.cloud 下有五条 A 记录需要操作:
| 记录类型 | 名称 | 指向 |
|---|---|---|
| A | @ | 服务器 IP |
| A | www | 服务器 IP |
| A | api | 服务器 IP |
| A | relay | 服务器 IP |
| A | proxy | 服务器 IP |
这一步操作本身只要 30 秒,但效果立竿见影——原有的 CF Origin CA 证书立即失效了,因为灰色云模式下 CF 不再帮你终止 TLS,你的服务器必须自己提供证书。
第二步:Certbot 签发 Let’s Encrypt 证书
在服务器上安装 certbot 和 nginx 插件:
apt update && apt install certbot python3-certbot-nginx -y然后一次性签发覆盖所有域名的证书:
certbot --nginx -d outsider-studio.cloud \
-d www.outsider-studio.cloud \
-d api.outsider-studio.cloud \
-d relay.outsider-studio.cloud \
-d proxy.outsider-studio.cloud \
--non-interactive --agree-tos -m your@email.com这里有几个关键点:
ACME Challenge 通过 HTTP 完成:certbot 的 nginx 插件会自动在 /var/www/html/.well-known/acme-challenge/ 下创建验证文件,nginx 返回给 Let’s Encrypt 的 ACME 服务器验证你确实拥有域名的控制权。前提是你的 80 端口已经正确配置且可以从公网访问。
一次性签发 vs 逐个子域名签发:我把五个域名放在一个 SAN(Subject Alternative Name)证书里,这样只需要维护一个证书,nginx 配置也简洁。Let’s Encrypt 对 SAN 证书没有额外限制,只要总的域名数不超过 100 个就行。
签发后的输出:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/outsider-studio.cloud/fullchain.pem
Key is saved at: /etc/letsencrypt/live/outsider-studio.cloud/privkey.pem
This certificate expires on 2026-10-26.
These files will be updated when the certificate renews.Certbot 还会自动更新 nginx 配置,把刚才那五个域名的 server block 指向这个证书。不过如果你的 nginx 配置是手工管理的(像我一样),你需要自己配置 ssl_certificate 和 ssl_certificate_key 的路径。
三、Nginx 代理配置细节
我的服务器上跑着几个独立的后端服务(Node.js 应用、AI 推理服务、WebSocket 中继等),通过 nginx 反向代理对外暴露统一的 443 端口。
以下是核心的配置文件片段。
全局 SSL 配置
# /etc/nginx/conf.d/ssl.conf
ssl_certificate /etc/letsencrypt/live/outsider-studio.cloud/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/outsider-studio.cloud/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;API 服务(端口 3000)
# /etc/nginx/conf.d/api.conf
server {
listen 443 ssl http2;
server_name api.outsider-studio.cloud;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
# WebSocket 支持
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name api.outsider-studio.cloud;
return 301 https://$host$request_uri;
}Relay 服务(端口 3090)
# /etc/nginx/conf.d/relay.conf
server {
listen 443 ssl http2;
server_name relay.outsider-studio.cloud;
location / {
proxy_pass http://127.0.0.1:3090;
proxy_http_version 1.1;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name relay.outsider-studio.cloud;
return 301 https://$host$request_uri;
}Proxy 服务(端口 9090 + 9091)
这个服务比较特殊——它由两个进程组成:
# /etc/nginx/conf.d/proxy.conf
upstream proxy_backend {
server 127.0.0.1:9090;
server 127.0.0.1:9091;
}
server {
listen 443 ssl http2;
server_name proxy.outsider-studio.cloud;
location / {
proxy_pass http://proxy_backend;
proxy_http_version 1.1;
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;
}
}
server {
listen 80;
server_name proxy.outsider-studio.cloud;
return 301 https://$host$request_uri;
}主站服务(端口 80/443 默认)
# /etc/nginx/conf.d/default.conf
server {
listen 443 ssl http2;
server_name outsider-studio.cloud www.outsider-studio.cloud;
root /var/www/outsider-studio;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
server {
listen 80;
server_name outsider-studio.cloud www.outsider-studio.cloud;
return 301 https://$host$request_uri;
}注意每个 server block 都配置了从 80 端口到 443 的强制跳转,确保所有流量都是 HTTPS 加密的。
四、自动续期:Certbot Systemd Timer
Let’s Encrypt 的证书有效期是 90 天,所以续期必须自动化。Certbot 安装时会自动创建一个 systemd timer。
检查 timer 的状态:
systemctl status certbot.timer输出类似:
● certbot.timer - Run certbot twice daily
Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2026-07-28 10:00:00 CST; 5h ago
Trigger: Wed 2026-07-29 04:42:00 CST; 13h leftCertbot 默认每天检查两次,只有在证书剩余时间不足 30 天时才会真正发起续期。你还可以手动测试续期流程:
certbot renew --dry-run输出应该是:
Congratulations, all renewals succeeded. The following certificates have been renewed:
/etc/letsencrypt/live/outsider-studio.cloud/fullchain.pem (success)续期成功后,certbot 会自动 reload nginx 使新证书生效,不需要额外操作。
五、迁移前后速度对比
迁移完成后,我用同一套测试流程重新跑了一遍,结果如下:
静态页面(博客首页)
| 指标 | CF 代理 | 直连 Let’s Encrypt | 提升 |
|---|---|---|---|
| DNS 解析 | 12ms | 12ms | 持平 |
| TCP 连接 | 85ms | 18ms | 4.7x |
| TLS 握手 | 1,820ms | 78ms | 23x |
| TTFB | 4,120ms | 286ms | 14.4x |
| 内容下载 | 210ms | 45ms | 4.7x |
| 总计 | 6,247ms | 439ms | 14.2x |
API 请求(JSON 响应)
| 指标 | CF 代理 | 直连 Let’s Encrypt | 提升 |
|---|---|---|---|
| TTFB | 5,380ms | 312ms | 17.2x |
| 总耗时 | 6,560ms | 398ms | 16.5x |
多地域采样
我用了三个不同城市的监测点做了额外验证:
| 监测点 | CF 代理 | 直连 Let’s Encrypt |
|---|---|---|
| 上海 | 4.8s | 0.3s |
| 北京 | 5.1s | 0.4s |
| 广州 | 4.6s | 0.3s |
| 成都 | 5.7s | 0.4s |
结论很明确:对国内用户来说,CF 免费代理慢的不是一点点,而是数量级的差距。
为什么差距这么大?
简单的回答是:物理距离。上海到美西的往返延迟(RTT)在 150-200ms 之间,但实际观测到的 TLS 握手时间为什么是 1.8 秒而不是 200ms?
原因在于 CF 免费版的拥塞控制和路由质量:
- TCP 慢启动:CF 边缘节点到服务器的连接需要重新建立,TCP 慢启动阶段丢包导致窗口增长缓慢
- 国际出口拥堵:尤其是晚高峰时段,上海到美西的国际出口带宽竞争激烈
- CF 共享网络的额外处理:流量过滤、WAF 规则检查、缓存命中失败的请求——这些处理在免费版上使用的资源优先级较低
潜在代价
当然,关掉 CF 代理也有一些需要接受的 trade-off:
失去了免费 CDN 缓存。之前静态资源(图片、JS、CSS)会在 CF 边缘节点缓存,国内用户访问时虽然 TCP/TLS 慢,但内容可以从 CF 节点直接获取。迁移后,所有资源都直接从服务器提供,服务器的带宽和 I/O 变成了瓶颈。
我的服务器带宽是 5Mbps,实测够用(日均 PV 在几百级别)。如果你的站点流量较大,或者有大量大文件分发需求,建议在服务器前面再加一层国内 CDN(如又拍云、七牛云)做静态资源缓存。
失去了 DDoS 防护。CF 免费版提供基础的 DDoS 过滤。关掉代理后,服务器 IP 直接暴露在公网上。对个人站点来说,被 DDoS 的概率很低,但可以配合 iptables fail2ban 做一些基础防护。
六、最后的一些建议
如果你也在考虑做类似的迁移,以下几个坑可以提前避开:
- 先在 CF Dashboard 做灰度切换:不要一次性把主域名的橙云关掉,可以先拿一个子域名做测试,确认一切正常后再切其余记录
- 注意 DNS 缓存 TTL:在切灰色云之前,把 DNS 记录的 TTL 降到 60 秒,等迁移完成后再恢复正常值(3600 秒),可以大幅缩短切换过程中的不稳定窗口
- Certbot 的 -nginx 插件很省事,但不是万能的:如果你的 nginx 配置比较复杂(多个 server block、非标准路径),建议用 certonly 模式手动获取证书,然后自己配置 SSL 路径
- 监控证书过期:虽然 certbot 有自动续期,但建议用一个外部监控服务(如 uptimerobot 或 crontab 脚本)定期检查证书的有效性,防止因为服务异常导致续期失败
- 备份私钥:证书文件本身可以重新签发,但私钥一旦丢失所有已签发的证书都会作废。建议定期备份
/etc/letsencrypt/目录
回到最开始的问题:那个橙色云到底值不值得开?
对全球业务、海外用户为主的站点来说,CF 免费版是非常好用的加速工具。但如果你和你的用户都在国内,CF 免费代理非但不是加速,反而是一种减速。 关掉它,切换到 Let’s Encrypt 直连,你会感受到服务速度从一个完全不同的量级回来了。
以上就是这次迁移的全过程,希望能帮到有同样困扰的朋友。如果你也遇到了类似的问题,欢迎在评论区留言交流。