在之前的 LAMP 搭建与邮件服务部署中,我们一直面临一个棘手的问题:Let's Encrypt 的证书续签太麻烦了。尤其是申请通配符(泛域名)证书时,Certbot 默认需要手动添加 DNS TXT 记录验证,每三个月就要折腾一次,一旦忘记,服务就会中断。

        此外,当我们的站点托管在 Cloudflare(以下简称 CF)后,HTTPS 的链路变得更加复杂。为什么开启了 HTTPS 还有“Unknown”流量?CF 的边缘证书和源服务器证书到底用哪个?

        本文将先通俗地厘清 SSL 核心概念,分析 CF 的架构,最后给出一个终极解决方案:使用 acme.sh 配合 CF API 实现全自动证书续签,为后续配置 Dovecot 邮件加密打好基础。

1. 证书、签名、摘要:数字世界的“身份证”

        在配置服务器时,我们经常面对 .pem, .crt, .key 这些文件。要理解它们的作用,我们需要理解 HTTPS 握手背后的防伪逻辑。我们可以将数字证书比作一张“防伪钞票”。

1.1 核心组件:A面与B面

        我们可以把一个完整的证书文件(如 fullchain.pem)想象成一张钞票,它由两部分组成:

  • A面(明文正文):包含你的身份信息。

    • 内容包括:域名(example.com)、有效期、颁发机构(CA)、以及最重要的——你的公钥。
    • 摘要 (Digest):这是对 A面 内容进行哈希运算(如 SHA-256)后得到的“指纹”。只要 A面 改动一个标点,摘要就会完全改变。
  • B面(数字签名):这是 CA 机构盖的“防伪钢印”。

    • CA 机构用CA 自己的私钥,对 A面的“摘要”进行加密,生成的这串密文就是数字签名。

1.2 验证原理:公私钥的“反转”用法

        很多同学知道“公钥加密,私钥解密”,这用于数据传输。但在身份验证阶段,逻辑是反过来的:“私钥签名,公钥验证”。

        当浏览器(验钞机)收到服务器发来的证书时,会进行如下验证流程:

  1. 左手算指纹:浏览器读取证书的 A面,用哈希算法算一遍,得到 摘要 X。
  2. 右手解钢印:浏览器使用操作系统内置的 CA 公钥,去解密证书的 B面(签名),得到 摘要 Y。
  3. 对比防伪:如果 摘要 X = 摘要 Y,说明证书内容未被篡改,且确实是该 CA 签发的。

1.3 为什么黑客无法伪造证书?

        如果黑客拦截了你的请求,试图进行中间人攻击,他必须替换掉证书里的公钥(换成他自己的,以便解密你的数据)。

  • 一旦他修改了 A面的公钥,A面的摘要就变了。
  • 但他没有 CA 的私钥,无法生成一个新的、能被 CA 公钥解开的 B面(签名)来匹配新的摘要。
  • 浏览器验证时会发现摘要不匹配,随即报警拦截。

        唯一的例外:如果黑客在你电脑里偷偷植入了一个“恶意的根证书”并设为信任(例如某些流氓软件或公司监控),那么黑客就可以用这个恶意 CA 的私钥堂而皇之地签发假证书。这也是为什么保护系统“受信任根证书列表”至关重要。

2. Cloudflare 环境下的证书架构与流量加密

        当我们将域名托管至 Cloudflare(CF)并开启 CDN(橙云)后,原本简单的 用户 <--> 服务器 直连模式,变成了 用户 <--> Cloudflare <--> 服务器 的三方模式。

        在这个架构中,Cloudflare 实际上扮演了一个“反向代理”的角色。对于用户来说,CF 是服务器;对于我们的 VPS 来说,CF 则是浏览器(客户端)。

2.1 CF 的三种凭证机制

        在 Cloudflare 的 SSL/TLS 后台,我们经常会看到三类容易混淆的证书概念,它们分别负责链路中不同的环节。

2.1.1 边缘凭证 (Edge Certificate)

  • 位置:部署在 Cloudflare 的全球 CDN 节点上。
  • 作用:负责 用户 $\leftrightarrow$ Cloudflare 这一段的加密。
  • 感知:这是用户在浏览器地址栏点开“小锁头”时看到的证书。通常由 Google Trust Services 或 Let's Encrypt 签发,颁发对象是 sni.cloudflaressl.com 或你的域名。
  • 管理:完全由 CF 自动申请、续签,无需用户干预。

2.1.2 原点凭证 (Origin Certificate)

  • 位置:部署在你的 VPS (Apache/Nginx) 上。
  • 作用:负责 Cloudflare $\leftrightarrow$ 你的 VPS 这一段的加密。
  • 选择策略:

    • 方案 A(CF 自签):在 CF 后台生成一张有效期 15 年的证书安装到 VPS。

      • 缺点:这张证书只被 CF 信任。如果用户不走 CDN 直接访问 VPS(如邮件客户端),会报证书错误。
    • 方案 B(公共受信,推荐):在 VPS 上使用 acme.sh 申请 Let's Encrypt 泛域名证书。

      • 优点:CF 信任它(只要开启 Full Strict 模式),普通浏览器和邮件客户端也信任它。这是兼顾 Web 加速与邮件服务的最佳方案。

2.1.3 客户端凭证 (Client Certificate / mTLS)

  • 位置:安装在访客的设备(浏览器)里。
  • 作用:双向认证(门禁系统)。不仅是你检查服务器的证书,服务器也要检查你有没有“通行证”。
  • 误区:很多博主误以为开启 HTTPS 就需要配置这个。错!

    • 一旦开启并生效,没有安装特定证书的普通访客将直接被拒绝访问(400 Bad Request)。
    • 除非你是银行内网或私有管理系统,否则千万不要在公开博客上配置 mTLS 规则。

2.2 加密模式的选择:Full (Strict)

        理解了“CF 是浏览器”这个概念后,SSL 模式的选择就显而易见了。为了安全,我们应该选择 Full (Strict) / 完全(严格) 模式。

  • 流程解析:

    1. CF 的服务器向你的 VPS 发起连接请求。
    2. 你的 VPS 出示 Let's Encrypt 证书。
    3. CF(作为客户端)校验证书的合法性(是否过期、是否由受信任 CA 签发)。
    4. 验证通过,CF 与 VPS 建立加密通道传输数据。

        在这个过程中,CF 不需要“拥有”你的证书私钥,它只需要像普通浏览器一样“验证”你的证书即可。这既保证了安全性,又避免了私钥泄露的风险。

2.3 为什么开启了 TLS 还有 Unknown 流量?

        在 CF 后台分析中,你可能会看到 SSL/TLS 版本中有 Unknown 的请求,这通常有以下原因:

  • 爬虫/扫描器:很多恶意扫描器只进行 TCP 握手,不完成 SSL 握手,或者使用非标准的协议头。
  • 直接 IP 访问:如果攻击者绕过域名,直接扫描你的 VPS IP 的 443 端口,CF 无法记录协议细节(因为流量没走 CF)。
  • 旧协议:极老旧的设备尝试使用已废弃的 SSLv3 或 TLS 1.0,如果在握手阶段被拒绝,可能被记录为未知。

3. 终极方案:acme.sh 全自动续签与架构分离

        面对“既要网站享受 Cloudflare (CF) 的 CDN 加速与防护,又要邮件服务(Dovecot/Postfix)能被本地客户端直连”的需求,我们需要一套组合拳。

        核心矛盾:CF 的免费版 CDN(点亮小黄云)本质上是一个 Web 代理,它只负责代理 HTTP/HTTPS 流量(80/443 端口)。它不认识也不转发邮件协议(SMTP/IMAP 对应的 25, 465, 587, 993 等端口)。

        解决策略:域名分流 + 统一证书

  1. 网站域名(Web 流量)

    • 对象:www.example.com 或 @
    • 设置:开启代理(橙云 ☁️)。
    • 效果:流量经过 CF 清洗和加速。CF 负责与用户加密,并持有边缘凭证。
  2. 邮件域名(Mail 流量)

    • 对象:mail.example.com
    • 设置:仅 DNS 解析(灰云 ☁️)。
    • 效果:流量不经过 CF,由用户直连 VPS。此时,用户客户端(如 Outlook)会直接检查 VPS 的证书。
  3. 证书选择(关键点)

    • 由于邮件域名是直连,客户端不会信任 CF 签发的“原点证书”(那个只被 CF 信任)。
    • 因此,VPS 必须持有一张全球受信任的公网证书(Let's Encrypt)。
    • 完美闭环:这张 Let's Encrypt 证书既能让邮件客户端信任(直连不报错),也能让 CF 信任(满足 Full Strict 模式)。

        操作步骤:为了解决 Let's Encrypt 泛域名证书每 3 个月手动续签的痛点,我们将使用 acme.sh 配合 Cloudflare API 实现全自动化的“申请-验证-部署-重启”。

3.1 获取 Cloudflare API Token

  1. 登录 CF 后台 -> 右上角头像 -> My Profile -> API Tokens。
  2. Create Token -> Use template -> Edit zone DNS。
  3. 选择你的域名,生成 Token,复制保存。

3.2 安装 acme.sh

        acme.sh 是一个纯 Shell 脚本,比 Certbot 更轻量,且没有 Python 依赖问题。

# 1. 安装 (将 [email protected] 换成你的邮箱)
curl https://get.acme.sh | sh -s [email protected]

# 2. 让命令生效
source ~/.bashrc

3.3 申请泛域名证书

        这一步会自动在 CF 添加 DNS TXT 记录进行验证,验证完后自动删除,全程无需人工干预。

# 1. 导入 CF Token 环境变量
export CF_Token="你刚才复制的Token"
export CF_Account_ID="你的AccountID(在域名概览页右下角-账户识别码)"

# 2. 申请证书 (包含主域名和通配符域名)
# --dns dns_cf 代表使用 Cloudflare API
# --server letsencrypt 代表明确使用 Let's Encrypt (默认可能是 ZeroSSL)
acme.sh --issue --dns dns_cf --server letsencrypt -d example.com -d *.example.com

3.4 安装证书到 Apache

        注意:不要直接使用 .acme.sh 目录下的文件,应使用 --install-cert 命令复制到指定位置,这样 acme.sh 才会记住路径并在续签后自动更新文件并重启服务。

# 1. 创建存放目录
mkdir -p /usr/local/apache/conf/ssl

# 2. 安装证书并指定重载命令
acme.sh --install-cert -d example.com \
--key-file       /usr/local/apache/conf/ssl/server.key  \
--fullchain-file /usr/local/apache/conf/ssl/server.crt \
--reloadcmd     "/usr/local/apache/bin/apachectl -k graceful"

3.5 验证与自动续签

        acme.sh 会自动添加一个 crontab 定时任务,每天检查证书有效性,到期前自动续签并重启 Apache。你可以通过 crontab -l 查看。

        至此,你已经拥有了一个支持泛域名的、自动续签的、被邮件客户端信任的 SSL 证书。下一步,我们就可以愉快地去配置 Postfix 和 Dovecot 的 SSL 加密了!