SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

Ubuntu 24.04 用 Certbot 为 Apache 签发证书

在 Ubuntu 24.04 上用 Certbot 为 Apache 申请 Let's Encrypt 证书:snap 还是 apt、ServerName 陷阱、自动续期,以及阻碍签发的确切报错。

您将搭建的内容

在 Ubuntu 24.04 上运行一个通过 HTTPS 提供服务的 Apache 站点,使用免费且受浏览器信任的 Let's Encrypt 证书。证书由 Certbot 签发,并由一个您此后再也无需操心的 systemd 定时器自动续期。真正完成工作的命令只有一行。所有出错的环节都发生在这行命令之前:某个 vhost 没有配置 ServerName、80 端口在服务商防火墙处被关闭、DNS 仍指向旧服务器。因此本指南把大部分篇幅花在这些前置条件上,并指出每种错误会打印出的确切报错字符串。

两点范围说明。如果您的 Web 服务器是 nginx,流程的整体形态相同,但插件和配置文件不同,请改用 本指南的 nginx 版本。如果您要保护的对象仅供内部使用,比如私有地址上的管理面板、别人不会访问的预发布机器,那么您根本不需要证书颁发机构(CA);自签名证书所需的部件更少,而且可以离线工作。

前置条件,以及 Certbot 运行前就会失败的三种情况

  • Apache 已经通过普通 HTTP 为您的站点提供服务。 Certbot 的 Apache 插件编辑的是已存在的站点,它不会创建站点。如果您从一台空白的 VPS 开始,请先搭建 Ubuntu 24.04 上的 LAMP 技术栈 再回来,本指南正是它所缺失的 TLS 章节。
  • 拥有一个公网域名,且其 A 记录指向您的 VPS 地址。 Let's Encrypt 的 HTTP-01 验证意味着它们的验证服务器会从互联网连接到您的机器:不支持没有端口转发的 NAT 家庭实验室、不支持 .local 名称、不支持裸 IP。dig +short example.com 必须返回您的 VPS 地址;如果您在过去一小时内修改过 DNS,请等旧记录的 TTL 过期后再签发。
  • 如果存在 AAAA 记录,它必须正确。 当发布了 AAAA 记录时,Let's Encrypt 会优先使用 IPv6,因此一条过期的 AAAA 记录会导致验证失败,即便您从笔记本电脑(很可能走 IPv4)运行 curl 一切正常。要么发布一条正确的 AAAA 记录,要么干脆不发布。

80 端口 443 端口都需要在 ufw 以及您服务商的网络防火墙中开放,大多数托管面板都有一层操作系统永远看不到的第二重防火墙。HTTP-01 专门通过 80 端口验证,您无法只开放 443 端口来完成这一流程。

sudo ufw allow "Apache Full"
sudo ufw status

这些就绪之后,整个工作只需十五分钟,其中十分钟还是在阅读。

Certbot 用 snap 还是 apt?在 24.04 上,apt 终于可以了

多年前 Certbot 转向 snap 分发有一个充分的理由:发行版软件包僵化了。Ubuntu 20.04 附带的是 Certbot 0.40 且从未更新,项目方也厌倦了去调试五年前的老 bug。在 24.04 上,这个理由已经不成立了:软件源提供的是当代版本 Certbot 2.9.0,并且 unattended-upgrades 会持续为它打补丁。对于这个操作系统,我的建议是:使用 apt。您可以省去 snapd 守护进程,Apache 插件会在同一次事务中一并安装,续期定时器也会以 Debian 常规的方式与 systemd 集成。

sudo apt update
sudo apt install -y certbot python3-certbot-apache
certbot --version

正确结果:certbot 2.9.0python3-certbot-apache 软件包就是那个读取并编辑您 Apache 配置的插件,缺少它时,certbot --apache 会以 The requested apache plugin does not appear to be installed 报错失败。

在两种情况下 snap 仍是正确的选择:您希望在 Certbot 新版本发布当天就用上最新版,或者您需要某个只以 snap 形式分发的 DNS 插件(有几个 certbot-dns-* 服务商插件正是如此)。如果您选择这条路:

sudo apt remove -y certbot python3-certbot-apache
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

无论选择哪一种,都绝不要同时运行两者。两套安装意味着两个续期调度器争抢 /etc/letsencrypt,而您 shell 在 PATH 中找到的那个 certbot 未必就是真正管理您证书的那一个。上面那行 apt remove 不是可有可无的摆设。

Certbot 编辑的 vhost 必须已经存在:ServerName 是全部关键

certbot --apache 的工作方式是:找到 ServerNameServerAlias 与您传入的每个 -d 域名相匹配的那个 80 端口虚拟主机(vhost),通过它证明您对域名的控制权,然后为该 vhost 写出一份 SSL 孪生配置。没有匹配的 ServerName 就无法匹配,而 Ubuntu 默认的 000-default.conf 出厂时就把 ServerName 注释掉了。这一行被注释的配置,正是本指南那条关键长命令失败的最常见原因。

因此在动 Certbot 之前,先给站点配置一个像样的基于名称的 vhost。创建 /etc/apache2/sites-available/example.com.conf

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com
    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

启用它,并确认 Apache 既能解析它、又能把该名称路由到它:

sudo a2ensite example.com.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo apache2ctl -S

configtest 必须打印 Syntax OK。如果它同时打印 AH00558: apache2: Could not reliably determine the server's fully qualified domain name,那是关于全局 ServerName 的警告,而非您的 vhost,在这里无害,可以用 echo \"ServerName $(hostname -f)\" | sudo tee /etc/apache2/conf-available/servername.conf && sudo a2enconf servername && sudo systemctl reload apache2 来消除。

-S 的输出才是真正要看的检查。您需要看到类似 port 80 namevhost example.com (/etc/apache2/sites-enabled/example.com.conf:1) 这样一行,且其下方有 alias www.example.com,Apache 报告的是它实际读取的 sites-enabled 符号链接,而不是您在 sites-available 中编辑的那个文件。如果 example.com 没有列在 80 端口下,那么 Certbot 也同样找不到它。

签发证书:certbot --apache

sudo certbot --apache -d example.com -d www.example.com

首次运行会询问三件事:一个电子邮件地址(用于您的 ACME 账户和 CA 的紧急通知;Let's Encrypt 不再发送到期提醒,因此监控续期是您自己的责任)、是否同意 Let's Encrypt 的条款、以及是否愿意把您的邮箱分享给 EFF。现在不再有关于跳转的询问了:自 Certbot 2.0 起,Apache 安装程序默认把 HTTP 跳转到 HTTPS,这正是您想要的。如果您确实需要让普通 HTTP 继续提供内容,请加上 --no-redirect

成功时的输出如下,您应当认真阅读它,而不是一扫而过:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2026-10-14.

Deploying certificate
Successfully deployed certificate for example.com to /etc/apache2/sites-available/example.com-le-ssl.conf
Successfully deployed certificate for www.example.com to /etc/apache2/sites-available/example.com-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.com

在这条消息背后,Certbot 做了四件事:如果 Apache 的 ssl 模块尚未启用则将其启用;写出 example.com-le-ssl.conf,也就是您的 vhost 在 *:443 上的一份副本,其中带有 SSLEngine on 和证书路径;启用该副本;并向原来的 80 端口 vhost 中添加了一段 RewriteRule 配置块,把所有请求以 301 跳转到 HTTPS。您原来的 vhost 文件是被编辑,而非被替换,SSL 孪生文件就在它旁边,您可以逐行查看它添加的每一行内容。

证书实际存放的位置,以及为什么您永远不要复制它

所有内容都落在 /etc/letsencrypt/live/example.com/ 下:fullchain.pem(证书加中间证书链,也就是服务器应当指向的文件)、privkey.pem(私钥,仅 root 可读),以及供需要分开使用各部分的软件所用的 cert.pemchain.pem。这些都是指向 /etc/letsencrypt/archive/ 的符号链接,而这层间接正是续期机制所在:续期时会把新文件写入 archive/ 并重新指向符号链接。让任何其他软件指向 live/ 路径,它就能免费获得续期后的新证书;而把这些文件复制到别处,您就为自己埋下了一次 90 天后的服务中断。

另一个值得了解的文件是 /etc/letsencrypt/renewal/example.com.conf,它记录了这份证书是如何签发的:authenticator = apacheinstaller = apache、以及各个域名,这样续期就能无人值守地重复整个过程,包括之后重新加载 Apache。

续期已经安排好了:去验证它,而不要自己搭建

Let's Encrypt 证书按设计有效期为 90 天,而 apt 软件包已经安装好了相应机制:一个 systemd 定时器,每天在随机时间运行 Certbot 两次,为任何距到期不足 30 天的证书续期。不要在此之上再加一个 cron 任务;第二个调度器除了带来日志噪音和触及速率限制的风险外,什么都不会增加。

systemctl list-timers certbot.timer
sudo certbot renew --dry-run

第一条命令显示该定时器处于活动状态,NEXT 时间在未来 24 小时内的某个时刻,调度是每天两次并带有随机延迟,因此确切时间是刻意设计为不可预测的(在 snap 安装中,定时器改为 snap.certbot.renew.timer)。这次演练会针对 Let's Encrypt 的 staging(测试)环境执行一次完整的续期彩排:真实的验证挑战、不签发证书、不消耗速率限制额度。正确结果以如下内容结尾:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

如果这次演练失败,那么约 60 天后真正的续期也会以同样的方式失败,请趁现在解决它,此时当前证书还有整个有效期在前面。常见的罪魁祸首是签发之后新增的一条防火墙规则,它又把 80 端口关上了。

用 curl 验证,以及小锁图标应当显示什么

curl -sI http://example.com | head -n 3
curl -I https://example.com
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -dates

第一条应返回 HTTP/1.1 301 Moved Permanently,并带有 Location: https://example.com/ 响应头,这就是 Certbot 安装的跳转。第二条应返回 HTTP/1.1 200 OK,且 curl 不对 TLS 有任何抱怨。第三条会打印签发者,即一行 O = Let's Encrypt,带有像 R12E7 这样简短的 CN,以及大约 90 天后的 notAfter。在浏览器中您会看到小锁图标,点击它会显示同样的签发者。如果 curl 正常而浏览器却告警,那您几乎可以肯定看到的是缓存页面或用错了主机名,而不是证书问题。

多个站点:一张 SAN 证书还是每站一张证书

两种方式都可行,续期方式也一样。对于同一台机器上互不相关的站点,为每个站点各运行一次签发命令,每个站点都会在 live/ 下拥有自己的目录和自己的续期配置,某个域名出问题也永远不会阻塞其他域名的续期。这是我的默认做法。

对于拥有多个名称的同一个站点,把它们放到一张 SAN(Subject Alternative Name,主题备用名称)证书上,单张证书最多可以承载 100 个名称。上面用 example.comwww.example.com 时您已经这么做过了。若日后要往已有证书中添加一个名称,请重新签发,并指定证书名称和完整的新名称列表:

sudo certbot --apache --cert-name example.com -d example.com -d www.example.com -d blog.example.com

Certbot 会注意到域名集合发生了变化,请您确认这次扩展,然后就地替换证书,live/ 路径不变,因此其他任何东西都无需改动。注意这个列表是替换而非追加:如果您在那条命令里漏掉 www,新证书就会悄悄把它丢掉。

通配符需要 DNS-01,而通常您并不需要通配符

HTTP-01 无法签发 *.example.com,因为在 Web 服务器上放一个文件只能证明对一个主机名的控制权,而不是对整个命名空间的控制权。通配符需要 DNS-01 验证:Certbot 会在 _acme-challenge.example.com 处设置一条 TXT 记录,这在实际操作中意味着一个带有您 DNS 服务商 API 凭据的 certbot-dns-* 插件,或者在每次续期时用 --manual 手动编辑 TXT 记录(很痛苦,不要围绕它来做规划)。从 TXT 记录的原理到能无人值守续期的插件,完整的操作步骤见 用 Certbot 通过 DNS-01 签发通配符证书。老实的建议:如果您只有四个已知的子域名,一张把这四个都列出来的 SAN 证书比通配符更简单,也无需在服务器上存放 DNS API 密钥。

各种失败模式,以及您会看到的报错字符串

Certbot 因为 Apache 配置有误而拒绝启动。

The apache plugin is not working; there may be problems with your existing configuration.
The error was: MisconfigurationError('Error while running apache2ctl configtest.\n\nAction \'configtest\' failed.\nThe Apache error log may have more information.\n\nAH00526: Syntax error on line 12 of /etc/apache2/sites-enabled/example.com.conf')

该插件在改动任何东西之前会先运行 configtest,如果 Apache 本身不满意就中止,那些 \n 是字面字符,因为 Certbot 打印的是异常的 repr 形式。请您自己运行 sudo apache2ctl configtest:它会指出文件和行号,通常是手动编辑时的拼写错误、一个指向已不存在路径的 SSLCertificateFile、或者引用了某个模块却没有启用它。修复到它打印 Syntax OK 为止,然后重新运行 Certbot。

没有 vhost 与该域名匹配。

Unable to find a virtual host listening on port 80 which is currently the only challenge port.

这就是前面提到的缺少 ServerName 的失败,在签发时被捕获。Certbot 在每个已启用的 80 端口 vhost 中搜索与您 -d 相匹配的 ServerName/ServerAlias,却什么都没找到。sudo apache2ctl -S 会显示 Apache 实际的路由情况;把 ServerName 那一行加到正确的 vhost 上,重新加载,再重试。一个近亲问题是验证到达了错误的 vhost,验证挑战的响应返回 Invalid response ... 404,因为另一个站点接住了这个请求。诊断相同,工具也相同:apache2ctl -S

验证超时。

Certbot failed to authenticate some domains (authenticator: apache).
...
Detail: ...: Timeout during connect (likely firewall problem)

Let's Encrypt 无法向您 DNS 所公布的地址的 80 端口打开一个 TCP 连接。按可能性从高到低排列:您服务商的网络防火墙(独立于 ufw,在托管面板中配置)、只允许 443 或只允许 SSH 的 ufw 规则集、DNS 仍指向之前的服务器、或者过期 AAAA 的问题,它们的服务器尝试了 IPv6 而您的机器只在 IPv4 上响应。请从 VPS 外部测试:在您的笔记本电脑上运行 curl -I http://example.com 就能重现它们的验证器所看到的情况。

您反复重试,把自己重试进了速率限制。

Error creating new order :: too many failed authorizations recently: see https://letsencrypt.org/docs/rate-limits/

Let's Encrypt 允许每个主机名、每个账户、每小时有 5 次失败的验证,自其 2025 年的速率限制改版以来,这是一个不断回填的令牌桶,大约每 12 分钟恢复一次重试额度,而对着一个坏掉的防火墙猛烈重试会很快把它耗尽。等待有用,但真正的解决办法是改变行为:任何一次失败之后,都用 staging 环境去调试,直到成功为止。

sudo certbot certonly --apache --dry-run -d example.com -d www.example.com

注意这里的 certonly--dry-run 只被 certonlyrenew 子命令接受,而直接的 certbot --apache --dry-run 形式则完全拒绝运行,并告诉您 --dry-run currently only works with the 'certonly' or 'renew' subcommands。这次演练针对 staging 验证,它有自己宽松的额度且不签发真实证书,所以您可以在那里失败一整个下午。只有在 staging 通过之后才重新运行真正的命令。其他的限制,比如每个注册域名每周 50 张证书、每周同一名称集合 5 张重复证书,只有当某个脚本在循环中反复签发时您才会撞上。

HTTPS 一旦启用,请记住证书保护的是传输,而不是服务器本身:22 端口整天都还在承受密码猜测。把这一步与 Ubuntu 24.04 上的 Fail2ban 搭配起来,是接下来自然而然的三十分钟工作。

FAQ

在 Ubuntu 24.04 上为 Apache 安装 Certbot,该用 snap 还是 apt?

用 apt。Ubuntu 24.04 附带 Certbot 2.9.0,对本指南中的所有内容来说都足够新,能通过 unattended-upgrades 获得安全补丁,而且不需要 snapd。只有当您需要立即用上最新版本、或者需要某个仅以 snap 形式分发的 DNS 插件时,才选择 snap;如果您要切换,请先运行 apt remove certbot python3-certbot-apache,这样两个续期调度器就永远不会共存。

为什么 Certbot 会提示 \"Unable to find a virtual host listening on port 80\"?

因为没有任何已启用的 80 端口 vhost 拥有与您通过 -d 传入的域名相匹配的 ServerNameServerAlias,Ubuntu 默认的 vhost 出厂时就把 ServerName 注释掉了。运行 sudo apache2ctl -S,找到(或创建)本应拥有该名称的 vhost,加上 ServerName example.com,重新加载 Apache,再重新运行 Certbot。

如何解决 \"Timeout during connect (likely firewall problem)\"?

Let's Encrypt 无法访问您 DNS 所公布地址的 80 端口。请检查您服务商面板层面的网络防火墙以及 ufw,确认 dig +short example.com 返回的是这台 VPS,并删除或修正任何过期的 AAAA 记录,因为存在 AAAA 记录时验证会优先使用 IPv6。用 curl -I http://example.com 从服务器外部确认修复已生效,然后在真正签发之前用 sudo certbot certonly --apache --dry-run -d example.com 进行彩排。

在 Ubuntu 24.04 上 Certbot 会自动续期证书吗?

会。apt 软件包会安装 certbot.timer,这是一个每天运行两次的 systemd 定时器,为任何距到期不足 30 天的证书续期,并在之后重新加载 Apache;snap 则用 snap.certbot.renew.timer 来完成同样的工作。用 systemctl list-timers certbot.timer 验证,用 sudo certbot renew --dry-run 彩排,不要在此之上再添加您自己的 cron 任务。

如何用 Certbot 和 Apache 获得通配符证书?

通配符需要 DNS-01 验证:Certbot 必须在 _acme-challenge.example.com 处放置一条 TXT 记录,这意味着需要一个带有您 DNS 服务商 API 凭据的 certbot-dns-* 插件(--manual 这个替代方案则需要在每次续期时手动编辑 TXT 记录)。如果您只有少数几个已知的子域名,一张把它们明确列出的 SAN 证书更简单,也能让 DNS API 密钥不必留在服务器上。