主题
挂 Cloudflare CDN
一句话
把图片和静态资源交给 Cloudflare 的全球节点发,你的服务器只在第一次被回源。 免费,代码一行不用改。
什么时候需要挂
不是一上线就要挂。 先看几个数字再决定:
bash
# 看服务器闲不闲(三个数字都小于 CPU 核数就是闲着)
uptimebash
# 看这段时间总共发出去多少流量
awk '{s+=$10} END{printf "%.1f MB\n", s/1048576}' /opt/mall/logs/nginx-access.log别看错日志文件
HTTPS 的访问日志在 /opt/mall/logs/nginx-access.log(站点块里显式配的)。 默认的 /var/log/nginx/access.log 只剩 80 端口那点跳转流量 —— 查错文件会得出「几乎没人访问」这种看着有证据的错误结论。
出现下面任何一条,就该挂了:
- 图片打开明显变慢,尤其是海外访客
- 服务器带宽跑满,或者服务商开始按流量另外收费
uptime的负载持续接近 CPU 核数- 你打算投广告,预计流量会涨一个数量级
挂 CDN ≠ 换对象存储
这两件事经常被混为一谈:
- CDN:把你现有的图片缓存到全球节点。免费,改 DNS 就行 ← 先做这个
- 对象存储(R2 / S3):把图片搬到别人的服务器上存。要迁移文件、改配置
先挂 CDN。 挂完之后源站的图片流量会趋近于零,多数人到此为止就够了。
⚠️ 开始之前:证书那件事必须先处理
这是整篇文档最重要的一段,跳过它三个月后会出大事。
会发生什么
现在的 HTTPS 证书是 Let's Encrypt 自动续期的,续期时 Let's Encrypt 要来访问 http://你的域名/.well-known/acme-challenge/xxx 验证域名归属(叫 HTTP-01)。
挂上 Cloudflare(橙云)之后,这个请求会先到 Cloudflare。能不能正确转到你的服务器, 取决于 Cloudflare 后台一个叫「Always Use HTTPS」的开关 —— 而那是很多人挂 CDN 时 顺手就打开的。
为什么后果特别严重
三件事叠在一起
- 续期失败是静默的 —— 不报错、不发邮件。 Let's Encrypt 从 2025 年 6 月起不再发到期提醒邮件,没有任何人会通知你。
- 证书有效期 90 天 —— 出问题到发作隔着三个月,那时候早忘了动过什么。
- 我们的站点发 HSTS —— 证书一过期,浏览器直接拒绝连接, 不是"提示不安全但能点继续",是彻底打不开。而且用户那边的 HSTS 缓存 要等很久才过期。
解法:改用 DNS-01 验证
DNS-01 是通过在 DNS 里加一条 TXT 记录来验证域名归属,完全不经过 HTTP, 所以和橙云开不开、Always Use HTTPS 开不开都没有关系。
必须在点橙云之前做完这一步。
第 1 步:换成 DNS-01 证书
① 在 Cloudflare 建一个 API 令牌
- 登录 Cloudflare → 右上角头像 → My Profile
- 左侧 API Tokens → Create Token
- 找到 Edit zone DNS 模板 → Use template
- 权限保持
Zone / DNS / Edit - Zone Resources 选
Include / Specific zone / 你的域名 - Continue to summary → Create Token
- 把令牌复制下来 —— 页面关掉就再也看不到了
别用 Global API Key
那个 key 能操作你 Cloudflare 账号下的所有东西(包括删除域名)。 上面这个令牌只能改这一个域名的 DNS 记录,泄露了损失也有限。
② 在服务器上装插件并换证书
bash
ssh root@你的服务器IPbash
apt update && apt install -y python3-certbot-dns-cloudflare把令牌写进文件(<你的令牌> 换成刚才复制的那串):
bash
printf 'dns_cloudflare_api_token = <你的令牌>\n' > /root/.cloudflare.ini && chmod 600 /root/.cloudflare.inichmod 600 不能省
这个文件里是能改你 DNS 的凭据。不限权限的话,服务器上任何一个进程都读得到。
重新签发证书(jdz.io 换成你的域名):
bash
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.cloudflare.ini --dns-cloudflare-propagation-seconds 30 -d jdz.io -d www.jdz.io --cert-name jdz.io --force-renewal几个参数为什么这么写
--cert-name jdz.io—— 必须和原来的证书同名,否则会生成jdz.io-0001这样的新目录,而 nginx 还指着旧路径-d要把 www 也带上,和原来保持一致(证书不覆盖 www 的话, 访问 www 会直接握手失败)--dns-cloudflare-propagation-seconds 30—— 等 DNS 记录生效。默认 10 秒有时不够
③ 验证续期真的能跑(这一步别跳过)
bash
certbot renew --dry-run看到 Congratulations, all simulated renewals succeeded 才算成功。
这是唯一能提前发现续期坏掉的办法
它模拟一次完整的续期流程。不跑这一步的话,你要等三个月后站点打不开才知道。
顺便确认自动续期的定时器还在:
bash
systemctl is-enabled certbot.timer && systemctl is-active certbot.timer两行都应该输出 enabled / active。
第 2 步:确认 Cloudflare 的 SSL 模式
Cloudflare 后台 → 选中你的域名 → SSL/TLS → Overview
必须选 Full (strict)。
| 模式 | 会发生什么 |
|---|---|
| Off | 不加密,绝对不能用 |
| Flexible | Cloudflare 用 http 回源,而我们的 80 端口是 301 跳 https → 无限重定向循环,站点打不开 |
| Full | 回源用 https 但不验证证书 —— 能用,但中间人可以冒充你的源站 |
| Full (strict) ⭐ | 回源用 https 且验证证书。我们有真证书,就该用它 |
第 3 步:点橙云
Cloudflare 后台 → DNS → Records
找到这两条,把灰色云朵点成橙色:
| 类型 | 名称 | 代理状态 |
|---|---|---|
| A | 你的域名(或 @) | 🟠 Proxied |
| A | www | 🟠 Proxied |
只点 A 记录
MX(邮件)、TXT(SPF / DKIM / DMARC 那些)保持灰色,它们本来就不该走代理。 点橙了会让邮件收发出问题。
改完立刻生效,不用等。
第 4 步:验证(五项,逐个跑)
① 域名解析到 Cloudflare 了吗
bash
curl -sI https://你的域名/ | grep -i "^server:"出现 server: cloudflare 就对了。
② 图片走缓存了吗 —— 最关键的一项
先在浏览器打开任意商品页,右键一张图「复制图片地址」,然后:
bash
curl -sI 那个图片地址 | grep -i "cf-cache-status"连着跑两次:
| 第一次 | 第二次 | 结论 |
|---|---|---|
MISS | HIT | ✅ 正常。第一次回源,之后由边缘节点发 |
MISS | MISS | ❌ 没缓存住,往下看「常见问题」 |
DYNAMIC | DYNAMIC | ❌ Cloudflare 认为这不是静态资源 |
③ 站点功能还正常吗
打开你的商城,走一遍:首页 → 商品详情 → 加购 → 结算页。 再登录一次后台,传一张图(上传走的是同一条 /files/ 路径)。
④ 限流还认得出真实访客吗
这一项已经提前配好了
本系统的 nginx 配置里已经写好了 Cloudflare 的真实 IP 还原 (set_real_ip_from + real_ip_header CF-Connecting-IP),你不需要做任何事。
没配的话后果是:全站访客被算成 Cloudflare 的几个边缘 IP, 限流变成所有人共用一个额度,访客统计也直接失真。
验证方法:登录一次后台,然后在服务器上看:
bash
tail -3 /opt/mall/logs/nginx-access.log | awk '{print $1}'显示的应该是真实访客 IP,不是 172.x / 104.x 这类 Cloudflare 的地址。
⑤ 部署脚本还能正常跑吗
bash
python deploy.py check会看到一条关于 DNS 的警告,属正常
挂橙云后域名解析到 Cloudflare 的 IP,不再等于你服务器的 IP。 脚本会提示但不会中止 —— 当初就是这么设计的,因为那可能是你有意为之。
常见问题
图片一直是 MISS,缓存不住
先确认响应头里有长缓存:
bash
curl -sI 图片地址 | grep -i cache-control应该是 public, max-age=31536000, immutable。本系统默认就是这个, 如果不是,说明 nginx 配置被改过或者没重新渲染:
bash
python deploy.py nginx改了 nginx 一定要用 deploy.py nginx
deploy.py update 不会重写 nginx 配置,只有全量 deploy 和这条命令会。 改完模板跑 update 等于什么都没做,而且没有任何提示。
站点打不开,或者一直在跳转
十有八九是 SSL 模式选成了 Flexible。改成 Full (strict)。
后台改了内容,前台不更新
CDN 把旧内容缓存住了。Cloudflare 后台 → Caching → Configuration → Purge Everything。
调试期间可以开 Development Mode(Caching 页面右侧),它会临时绕过缓存 3 小时。
上传大文件失败
Cloudflare 免费版的请求体上限是 100 MB
而本系统的交付文件上限默认也是 100 MB —— 正好卡在边界上。
超过 90 MB 的文件建议:
- 用外部链接(虚拟商品的交付文件支持粘网盘地址),或者
- 联系我们把上限调低
邮件收不到了
检查 DNS 里的 MX 和 TXT 记录有没有被误点成橙色。它们必须是灰色(DNS only)。
怎么回滚
随时可以退回去,30 秒
Cloudflare 后台 → DNS → Records → 把橙色云朵点回灰色。
立刻生效,流量恢复直连你的服务器。
换成 DNS-01 的证书不用回滚 —— 它在挂不挂 CDN 的情况下都能正常续期, 留着只有好处。
之后还想再省的话
挂完 CDN 之后,源站的图片流量应该已经趋近于零。多数人到此为止就够了。
如果将来图片总量涨到几十 GB、或者你想让上传也更快,下一步是把图片从自己的服务器 搬到对象存储。推荐 Cloudflare R2:
- 出网流量零费用(图片业务的成本大头永远是流量,不是存储)
- S3 兼容,本系统换过去只改
.env里的S3_*四个值,代码一行不动 - 和 CDN 同一家,不用跨服务商调试
别急着做
搬到对象存储要迁移文件、改配置、验证每一张老图还能打开。 没到那个量级就是给自己找活干。