Skip to content

🔐 Cloudflare 回源 SSL 与源站证书

大部分人开完橙云、看到浏览器上那把小锁,就以为 HTTPS 配完了。其实那把锁只证明了一半

这篇把整条链路拆开讲清楚,然后给两条能照抄的实操路线。

读完你会知道

  • 浏览器上的小锁,为什么跟你服务器有没有证书完全无关
  • Flexible / Full / Full (strict) 到底差在哪,为什么 Full 不够安全
  • 自己的服务器怎么配到 strict —— Origin CALet's Encrypt 两条路,各自完整步骤
  • 为什么 S3、GitHub Pages 这类托管服务一张证书都不用配

先补基础

不清楚证书本身是什么、fullchain.pemcert.pem 差在哪,可以先看 👉 证书是怎么工作的,那篇讲原理,这篇讲怎么摆到链路上。


一、先搞懂:一次请求其实有两段 TLS

这是全文的地基,理解了它,后面所有问题都会自己解开。

开启橙云代理后,访客到你服务器的路径不是一条直通的加密隧道,而是在 Cloudflare 边缘断开成两截

两段各自独立加密,各自用不同的证书

谁验谁用哪张证书谁负责
① 前半段浏览器验 CloudflareUniversal SSLCF 自动签,你碰不到也不用管
② 后半段Cloudflare 验你的源站Origin CALet's Encrypt你自己装

由此得出两个最容易被搞错的结论:

结论 1:小锁跟你的服务器无关

浏览器地址栏那把锁,只说明 ① 前半段是加密的,而那是 CF 用自己的证书做到的。哪怕你服务器上一张证书都没有,锁照样是绿的。

结论 2:SSL/TLS 模式只管后半段

你在 CF 后台选的那个档位,唯一作用是决定 CF 用什么方式去连你的源站。它管不着前半段。

别把两张证书搞混

它们名字里都带 Cloudflare,但用途和位置完全不同:

Universal SSLOrigin CA
装在哪Cloudflare 边缘你的源站
给谁看访客浏览器Cloudflare 回源时
怎么来的自动签发你手动下载、手动安装
你要做什么什么都不用做装它

后面提到"装证书",指的永远是第二张


二、四种 SSL/TLS 模式

一眼看懂

模式CF 怎么连源站校验源站证书评价
Off不加密,还强制访客走 http别用
FlexibleHTTP:80没证书可校验假 HTTPS
FullHTTPS:443不校验半吊子
Full (strict)HTTPS:443完整校验✅ 唯一正确答案

Flexible:小锁是真的,加密是假的

访客看到锁 🔒,以为全程加密。实际上 CF 到你服务器那一段是明文 HTTP 在公网上裸奔,任何人在链路上抓包都能直接读到内容。

它存在的唯一理由:源站根本不支持 443

Flexible 的两个坑

① 它是整个 zone 生效的。 为了一个子域降级,等于把主站的回源安全一起拉低。

② 配 http→https 跳转会死循环。 源站收到的是 http 请求,如果它回一个"301 跳 https":

浏览器 → CF →(http)→ 源站 →(301 到 https)→ 浏览器 → CF →(http)→ 源站 → …

浏览器最终报 ERR_TOO_MANY_REDIRECTS

顺带分清「回源」和「跳转」

上面那个死循环之所以会发生,是因为回源和跳转被同时配了两套。这两个词在 CDN 后台里经常挨着出现,其实是完全不同的两件事:

  • 回源——CDN 悄悄去后台取内容再返回,用户无感,地址栏不变,内容可缓存
  • 跳转——CDN 回一条 Location 让浏览器自己再跑一趟,地址栏会变,两个请求

本文讲的所有 SSL 模式、Origin CA、Host 头,管的都是回源那条线。

机制差异、301/302 怎么选、以及死循环的两种修法,见 👉 CDN 回源与跳转

Full:加密了,但不确认对面是谁

走 443 了,但 CF 不检查源站证书是谁签的、有没有过期、域名对不对。源站挂一张自签名的、过期十年的证书,CF 照收不误。

打个比方:信封是密封的(加密没问题),但你从不核对收信人身份(不校验证书),谁来签收都行。

所以它比 Flexible 强——路上抓包的人看不懂内容;但挡不住有能力插到链路中间、拿一张假证书冒充你源站的攻击者

想深入了解这个攻击是怎么成立的,可以另找资料看"中间人攻击(MITM)",这里不展开。结论是:能装证书就别停在 Full。

Full (strict):把身份也验上

strict 下 CF 会检查三件事:

  1. 证书是不是受信任 CA 签发的(公共 CA,或 Cloudflare 自己的 Origin CA)
  2. 有没有过期
  3. 证书上的域名跟回源连接的主机名对不对得上

任何一条不过,CF 直接掐断连接,返回 526——宁可报错,也不把数据交给身份不明的对象

所以 Full 是个鸡肋档

它存在的唯一理由,是给源站只有自签名证书的人一个过渡台阶。但免费证书拿起来只要几分钟,这个台阶没必要踩。

能装证书就直接上 Full (strict)。


三、实战一:自己的服务器配到 Full (strict)

先说清楚一个常见误会:

只要开 strict,源站就必须装证书

没有例外。要选的不是"装不装",而是"装哪张"

先选一条路

两条路都能开 strict,选一条走就行,不用都做

路线 A:Origin CA路线 B:Let's Encrypt
谁签的Cloudflare 的私有 CA公共受信任 CA
谁信任它只有 Cloudflare所有浏览器
有效期15 年90 天
续期不用管自动续(需配一次)
拿证书要多久点一下,3 分钟装 certbot,10 分钟

怎么选,就看一个问题:会不会有人绕过 Cloudflare 直接访问你的服务器?

  • 不会(流量永远走橙云)→ 路线 A,签一次管 15 年,最省心
  • (要灰云、要 IP 直连调试、哪天想撤掉 CF)→ 路线 B

路线 A 的唯一限制

Origin CA 证书只有 Cloudflare 认。浏览器直连你的服务器会弹红色警告——这是正常的、预期内的,不是配错了。


Step 1 域名托管到 Cloudflare

CF 后台 Add a site,输入域名。CF 给你两个 NS 地址,去域名注册商后台把 NS 改成这两个

等 zone 状态变成 Active 再往下走(几分钟到几小时)。这一步没完成,后面签不了证书。


Step 2 拿到证书(二选一)

🅰️ 路线 A:Origin CA

CF 后台 → 选中域名 → SSL/TLS → Origin Server → Create Certificate

选项填什么
密钥类型RSA (2048),兼容性最好
主机名默认的 example.com, *.example.com 就行
有效期15 years

点 Create,页面出现两块内容:Origin CertificatePrivate Key

Private Key 只显示这一次

关掉页面就再也看不到了。两块都先复制到记事本。

为什么它不需要验证域名?

因为 Origin CA 是 Cloudflare 的私有 CA,唯一的信任方就是 CF 自己。而你的域名已经托管在 CF 了——所有权在你把 NS 指过来那一刻就已经证明完了,不用再验一遍。

所以:

它不检查那些主机名解析到哪、有没有解析、服务器能不能访问。 此刻 DNS 还没指向你的服务器,完全没关系。

🅱️ 路线 B:Let's Encrypt(DNS-01)

这里要解决一个"先有鸡还是先有蛋"的问题:域名还没解析到服务器,能签证书吗?

能。关键是选对验证方式。

证书是签给「域名」的,不是签给「服务器」的。 Let's Encrypt 只要确认一件事:你是不是这个域名的主人。它不关心你有没有服务器、服务器在哪。

HTTP-01DNS-01
怎么证明LE 来访问 http://域名/.well-known/...在 DNS 加一条 TXT 记录
域名要先解析到服务器吗不要
服务器要能被公网访问吗(80 端口)不要
能签泛域名 *.不能

域名还没指向服务器时,HTTP-01 签不了,DNS-01 完全没问题。certbot 会调 CF 的 API 加一条 TXT 记录,LE 查到就认可了。

你的服务器只是个"跑命令的地方"

整个验证过程跟这台机器毫无关系,换成你的笔记本跑都一样(签完把文件拷过去即可)。

唯一的硬前提

必须拥有一个域名,且托管在 CF。

"服务器没配域名"没问题;"根本没有域名"不行——LE 不给纯 IP 签可用的证书,没域名你也用不了 CF。

① 建一个 CF API Token

CF 后台 → 右上角头像 → My Profile → API Tokens → Create Token → 选 Edit zone DNS 模板:

  • Permissions:Zone - DNS - Edit
  • Zone Resources:Include - Specific zone - 选你的域名

创建后复制那串 Token(同样只显示一次)。

② 装 certbot 并签发

bash
sudo apt update
sudo apt install -y certbot python3-certbot-dns-cloudflare

存 Token:

bash
sudo mkdir -p /root/.secrets
sudo nano /root/.secrets/cf.ini

里面只写一行:

/root/.secrets/cf.ini
ini
dns_cloudflare_api_token = 粘贴你的Token
bash
sudo chmod 600 /root/.secrets/cf.ini

签发(换成你的域名):

bash
sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cf.ini \
  --dns-cloudflare-propagation-seconds 30 \
  -d example.com -d '*.example.com'

跑起来你会看到它自动加 TXT → 等待生效 → 验证通过 → 自动删掉 TXT,全程无需域名指向这台机器。

成功后证书在:

/etc/letsencrypt/live/example.com/fullchain.pem   ← 证书
/etc/letsencrypt/live/example.com/privkey.pem     ← 私钥

③ 配好自动续期,然后忘掉它

bash
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
echo -e '#!/bin/sh\nsystemctl reload nginx' | \
  sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

sudo certbot renew --dry-run

--dry-run 通过,90 天续期这事就自动化了。

90 天不是"免费版限制"

Let's Encrypt 没有付费版,所有人都是 90 天。短有效期是刻意设计——逼你自动化,并把私钥泄露的暴露窗口压到最短。

而且这是行业方向:CA/B Forum 已投票通过,到 2029 年所有公共证书有效期会砍到 45 天,付费的也一样。换别家(ZeroSSL、Buypass)也是 90 天左右。

certbot 装完自带 systemd timer,每天跑两次、剩余不到 30 天才真正续。配好之后你不用再管。


Step 3 把证书装到服务器

路线 A 需要手动创建文件:

bash
sudo mkdir -p /etc/nginx/ssl
sudo nano /etc/nginx/ssl/origin.pem    # 粘贴 Origin Certificate
sudo nano /etc/nginx/ssl/origin.key    # 粘贴 Private Key

sudo chmod 600 /etc/nginx/ssl/origin.key
sudo chown root:root /etc/nginx/ssl/origin.*

粘贴时要包含 -----BEGIN... / -----END... 两行,结尾留一个换行。

路线 B 的文件 certbot 已经放好了,这步跳过。


Step 4 Nginx 加 443

编辑站点配置(一般在 /etc/nginx/conf.d//etc/nginx/sites-available/):

/etc/nginx/conf.d/example.conf
nginx
# 80 先保持原样,别急着加跳转
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;   # 你的 hello world
    }
}

# 新增这一段
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    # 路线 A:
    ssl_certificate     /etc/nginx/ssl/origin.pem;
    ssl_certificate_key /etc/nginx/ssl/origin.key;

    # 路线 B 改成这两行:
    # ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

http2 on; 是 Nginx 1.25.1+ 的写法;老版本删掉它,改成 listen 443 ssl http2;

bash
sudo nginx -t && sudo systemctl reload nginx

nginx -t 报 OK 才继续。


Step 5 本地验证(DNS 还没配就能测)

这一招能让你在切 DNS 之前就确认一切正常,切过去零意外。把 1.2.3.4 换成服务器公网 IP:

bash
curl -v --resolve example.com:443:1.2.3.4 https://example.com/ -k

--resolve 的作用是在本地伪造一条解析,让 curl 把这个域名当成解析到该 IP。这样发出的 SNI 和 Host 都是正确域名,跟 CF 回源时的行为一模一样

能返回 hello world 就说明 443 通了。

关于那个 -k

  • 路线 A 必须加 -k:Origin CA 不被你本机信任,报"证书不受信任"是正常现象,不是配错
  • 路线 B 可以不加 -k:LE 是公共 CA,你本机就认。不加也能通过 = 证书装得完全正确,验证起来更踏实

单独检查证书装对没有:

bash
openssl s_client -connect 1.2.3.4:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Issuer

  • 路线 A → Cloudflare Origin SSL Certificate Authority
  • 路线 B → Let's Encrypt

Step 6 配 DNS

CF 后台 → DNS → Add record

字段
TypeA
Name@(根域)或 www
IPv4 address你的服务器公网 IP
Proxy statusProxied(橙云 🟠)

路线 A 必须开橙云

灰云(DNS only)的话访客会直接连你服务器,看到那张只有 CF 认的 Origin CA 证书 → 浏览器红色警告

路线 B 用的是公共证书,灰云也不会报错。


Step 7 切换到 Full (strict)

CF 后台 → SSL/TLS → Overview → 选 Full (strict)

等 30 秒左右,浏览器打开 https://example.com,应该正常显示 hello world 并带小锁。


Step 8 最后才加 80→443 跳转

顺序很重要

之前一直是 Flexible,如果提前加跳转就会触发前面说的重定向死循环。必须等 strict 生效之后再加。

把 Step 4 里 80 那段换成:

nginx
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
bash
sudo nginx -t && sudo systemctl reload nginx

出问题时对照

现象原因去查什么
521443 连不上安全组、ufw、Nginx 有没有 listen 443
525握手失败nginx -t、证书路径写没写对
526证书校验没过pem/key 贴反了、Step 2 主机名没覆盖实际访问的域名
重定向循环Step 8 提前做了先删掉跳转,确认 strict 通了再加回来
浏览器证书警告路线 A + 灰云DNS 记录改成橙云

各错误码的含义见文末 附录 A


可选加固:让源站也验一验 CF

strict 保证了"CF 连到的确实是你",但还有个反方向的洞:别人拿到你的源站 IP,可以绕开 CF 直接打你的服务器,你的 WAF、限速、防 DDoS 全被跳过。

补法有两招,一起用效果最好:

  1. Authenticated Origin Pulls —— CF 后台 SSL/TLS → Origin Server 里开启。开了之后 CF 回源会带一张客户端证书,Nginx 用 ssl_client_certificate + ssl_verify_client on 校验它
  2. 防火墙只放行 Cloudflare 官方 IP 段

两边互相验证,链路才算真正闭合。


四、实战二:为什么 S3 / GitHub Pages 一张证书都不用配

上面折腾了九步,换成托管服务却什么都不用做。区别在哪?

一句话

证书是装在源站上的东西。托管服务的源站是服务商的机器,人家早就装好了。

你连服务器都摸不到,自然没有"装证书"这个动作。

CF 回源到 s3.ap-southeast-1.amazonaws.com,AWS 直接甩出一张 Amazon 签的合法证书,strict 一校验就过。全程零操作。

判断方法:看 443 是谁在听

比背服务清单管用,遇到没见过的服务也能直接套:

443 端口谁在监听证书怎么办
你自己(自建 Nginx / Apache)得你自己装(Origin CA 或 Let's Encrypt)
服务商(S3 / Pages / Vercel / R2)一个字都不用管

但 S3 有个例外要注意

要拆成两个独立的问题看:

服务① 回源用的证书
(服务自己的域名)
② 绑你的自定义域名后的证书
S3自带 ✅不提供
GitHub Pages自带 ✅自动签 Let's Encrypt ✅
Vercel / Netlify / CF Pages自带 ✅自动签 ✅
Cloudflare R2自带 ✅走 CF 自动签 ✅

S3 是唯一的异类。 它的 REST 端点压根没有"绑定自定义域名"这个功能——你就算 CNAME 过去,S3 也只会拿 *.s3.<region>.amazonaws.com 那张证书应答。浏览器直连它就会看到证书域名对不上 → 红色警告。

所以 S3 直连方案里,橙云不是可选项

  • img.example.com 的证书由 CF 的 Universal SSL 提供(前半段)
  • 后半段 CF→S3 用 AWS 自带证书
  • 中间在 CF 边缘断开、换证书

改成灰云,请求直奔 S3,没人替你出证书 → 立刻报错。

不用 CF 的话,AWS 的标准解法是套一层 CloudFront + ACM 免费证书——本质是同一件事:找一个能为你域名出证书的代理层挡在 S3 前面


五、番外:S3 直连时踩的 NoSuchBucket

这个案例很能说明"分层"这件事,单独记一下。

DNS 加一条 img 的 CNAME 指向 mybucket.s3.ap-southeast-1.amazonaws.com,开橙云,请求下去返回:

xml
HTTP/1.1 404 Not Found
<Error>
  <Code>NoSuchBucket</Code>
  <BucketName>img.example.com</BucketName>
</Error>

注意 <BucketName> 里是域名,不是桶名。机制暴露得很彻底:

S3 是按请求的 Host 头去找同名桶的。 CF 回源时把 Host: img.example.com 原样传过去,S3 就去找一个叫 img.example.com 的桶,而实际桶叫 mybucket,对不上。

两个死路:

  • 改桶策略没用 —— 桶策略管权限不管路由NoSuchBucket 发生在权限判断之前
  • 让 CF 改写回源 Host —— Origin Rule 能做,但 Free 套餐会被拒:not entitled to use the HostHeader override(Pro 及以上才有)

结论

纯 CNAME 直连 S3 时,桶名必须等于域名。 想桶名随便起,前面必须有个能改 Host 的代理层——Worker、CloudFront,或付费的 Origin Rule。

于是建一个名字就叫 img.example.com 的桶,aws s3 sync 搬过去,问题解决。

精髓:TLS 看 SNI,HTTP 看 Host

最终架构:

访客 → https://img.example.com/<文件名>
     → Cloudflare(橙云,SSL Full strict)
     → 回源 s3.ap-southeast-1.amazonaws.com(保留 Host: img.example.com)
     → S3 桶 img.example.com(公开读)

为什么这样能保持 strict?

  • CNAME 指向 s3.ap-southeast-1.amazonaws.com,CF 回源建 TLS 用的是这个主机名,AWS 给它一张合法证书 → strict 通过 ✅
  • TLS 握手完成之后,HTTP 层的 Host: img.example.com 才发出去,S3 拿它找同名桶 → 路由正确 ✅

本文最核心的一条

TLS 校验看的是连接的主机名(SNI),HTTP 路由看的是 Host 头。这两件事在不同层,可以不一致。

既拿到了合法证书,又让 S3 找对了桶,还不用像网上一些教程那样把 SSL 降级成 Flexible。

顺带:通配符证书只匹配一级标签

AWS 的证书大致覆盖:

s3.ap-southeast-1.amazonaws.com
*.s3.ap-southeast-1.amazonaws.com

如果桶名本身带点(比如 img.example.com),用虚拟主机风格 URL 访问:

https://img.example.com.s3.ap-southeast-1.amazonaws.com/xxx.webp
                    ↑ 主机名有多级,通配符匹配不了

证书域名不匹配,TLS 直接失败。这是"桶名带点"方案最有名的副作用。

上面那套架构恰好避开了它:CF 连的是干净的一级主机名,桶名只出现在 HTTP 的 Host 头里,跟证书校验井水不犯河水。


附录 A:报错码速查——判断卡在第几层

配置出问题时,先看错误码,能省掉大量瞎猜:

错误码含义通常原因
521Web Server Is Down源站拒连——443 没开、防火墙/安全组挡了、服务挂了
525SSL Handshake Failed443 通了但握手失败——Nginx 没配 ssl、证书路径写错
526Invalid SSL Certificatestrict 下证书校验没过——过期、自签名、域名不匹配
应用层 4xx/5xx源站正常应答了TLS 和连通性都没问题,是业务层的事

一条很实用的判断

只要返回的是应用层错误码(比如 S3 的 404),就说明 TLS 那层早就通了,别再去动 SSL 设置。


附录 B:验证 CF 缓存真的清掉了

跟证书无关,但排查时很容易被骗,一起记下。

别被自己预热的缓存骗了

purge 之后反复请求,会把缓存重新预热成 HIT,看起来像没清掉。验证回源必须用 cache-busting 强制 MISS。

bash
# 精确清单个文件(一次最多 30 个 URL)
curl -sS -X POST \
  -H "X-Auth-Email: [email protected]" \
  -H "X-Auth-Key: $CF_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -d '{"files":["https://img.example.com/xxx.webp"]}'

# 验证:加随机参数强制回源
curl -sI "https://img.example.com/xxx.webp?cb=$RANDOM" | grep -i cf-cache-status
# → 应该是 MISS + 200

预热成 HIT → purge → 再请求变 MISS,就说明单文件精确失效生效了。


📌 一页速查

原理

  1. 橙云下有两段 TLS,SSL/TLS 模式只管后半段;浏览器的小锁跟源站证书无关
  2. Universal SSL(CF 边缘,自动)和 Origin CA(你的源站,手动装)是两张不同的证书,别混。
  3. Flexible = 后半段明文,整 zone 生效,配 http→https 跳转会死循环。
  4. Full 不校验证书:能防路上抓包,但挡不住拿假证书冒充源站的人。能装证书就直接上 Full (strict)
  5. 回源 ≠ 跳转:回源用户无感、内容可缓存;跳转地址栏会变、要跑两趟。跳转规则别在 CDN 和源站两边都配,那正是死循环的来源。详见 CDN 回源与跳转

实操

  1. 开 strict,源站必须装证书,没有例外;要选的是"装哪张"。
  2. 只走橙云 → Origin CA(15 年零维护,点一下就给,不验域名);要绕过 CF → Let's Encrypt(公共信任,90 天自动续)。
  3. 域名还没解析到服务器也能签证书:Origin CA 根本不验;LE 用 DNS-01 也不需要服务器可达。
  4. 泛域名 *. 只能用 DNS-01 签,HTTP-01 签不了。
  5. 切 DNS 前先用 curl --resolve 本地验证,切过去零意外。
  6. 80→443 跳转最后才加,提前加会死循环。
  7. 521 / 525 / 526 分别对应拒连、握手失败、证书校验失败;返回应用层错误码就说明 TLS 那层是好的

托管服务

  1. 看 443 是谁在听:你自己听 → 自己装证书;服务商听 → 一个字都不用管。
  2. S3 是异类:不给自定义域名出证书,必须靠橙云或 CloudFront+ACM 挡在前面,灰云直接报错。
  3. TLS 看 SNI,HTTP 路由看 Host 头,两者可以不一致——S3 直连方案的全部精髓。
  4. 纯 CNAME 直连 S3,桶名必须等于域名;桶策略管权限不管路由,救不了 NoSuchBucket
  5. 通配符证书只匹配一级标签,桶名带点走虚拟主机风格 URL 必挂。

技术笔记