☰
Ubuntu手动配置certbot泛域名证书全流程:DNS验证与Nginx部署
2026/10/1 19:29:09 网站建设 项目流程

1. 为什么我建议你在 Ubuntu 上手动走一遍 certbot 泛域名证书流程

直接说结论:虽然大多数教程都推荐“一键签发、自动续期”,但真实生产环境里,真正卡人的往往不是 Let‘s Encrypt 本身,而是 DNS 解析、权限路径、证书链拼接这些“看起来很简单”的细节。尤其是泛域名证书,它和单域名证书的签发逻辑完全不一样,手动操作一次,你会把整个 HTTPS 链路彻底吃透,后面排查任何 SSL 问题都比别人快一步。

我这次的需求很明确:一台 Ubuntu 服务器上有多个子域名服务,包括 API 网关、管理后台、静态资源、内部监控等,如果每个域名单独签一张证书,管理成本高、续期窗口错开、Nginx 配置也臃肿。用一张*.example.com的泛域名证书,能覆盖api.example.com、admin.example.com、static.example.com,以后新增子服务只要 DNS 解析指向同一台机器,证书直接复用,省掉的麻烦不是一点半点。

适合谁来参考这篇文章?三类人最合适:一是刚接触 Let‘s Encrypt 但被泛域名卡住的运维新手,二是在 Nginx 或 Apache 上手动部署过证书、想搞清楚 certbot 底层逻辑的人,三是准备把多子域名服务收敛到单一证书的团队。文章里不会有任何“复制粘贴就跑”的侥幸心态,每一步我都会告诉你为什么要这么做、不做会踩什么坑。

Let‘s Encrypt 的免费证书现在是互联网 HTTPS 化的绝对主力,但它有一个天然限制:通配符域名(泛域名)不支持 HTTP-01 验证,只能走 DNS-01 验证。这个限制决定了你必须在 DNS 服务商那边临时添加一条 TXT 记录,证书签发完成后再删掉。整个过程如果手动操作,大概需要 10 到 15 分钟,其中大部分时间花在等待 DNS 生效上。

2. 泛域名证书的核心逻辑:为什么 HTTP-01 验证在*.example.com上行不通

2.1 三种验证方式,泛域名只能选一种

Let‘s Encrypt 的 ACME 协议支持三种域名验证方式,理解它们的区别是手动配置的基础。

HTTP-01 验证是默认方式,CA 会访问http://你的域名/.well-known/acme-challenge/xxx这个临时路径,服务器返回指定的 token 内容即可。这个方式的优点是简单,但致命缺点是:它只能验证具体的一个域名。你想签发*.example.com这种通配符,CA 不知道该去访问哪个域名的这个路径,所以泛域名验证直接不支持 HTTP-01。

TLS-ALPN-01 验证是通过特定端口的 TLS 握手完成的,需要在 443 端口上临时跑一个特殊配置的 HTTPS 服务,同样只适用于单个具体域名。而且这个方式对服务器配置要求高,很多反向代理环境下很难操作,实际使用率很低。

DNS-01 验证是唯一支持泛域名的方式。CA 要求你在域名的 DNS 解析里添加一条 TXT 记录,记录名通常是_acme-challenge.example.com,记录值是一串随机生成的验证字符串。CA 会去查询这条 TXT 记录,匹配成功就能证明你对这个域名的所有权。泛域名之所以能用 DNS-01,是因为你控制example.com这个域名的 DNS,就意味着你有权签发该域名下的所有子域名和通配符。

2.2 DNS-01 验证的完整流程:从请求到签发

certbot 手动模式下的 DNS-01 流程大致是这样的:

  1. certbot 向 Let‘s Encrypt 的 ACME 服务器发起证书签发请求,附带你的域名列表。

  2. CA 返回一个验证挑战,要求你提供_acme-challenge.example.com的 TXT 记录值。

  3. certbot 会在终端显示这条 TXT 记录的内容,并暂停等待。

  4. 你登录 DNS 服务商的控制台,在example.com的解析记录里添加一条_acme-challenge的 TXT 记录。

  5. 等待 DNS 生效,通常在 10 秒到 5 分钟之间,然后按回车让 certbot 继续。

  6. CA 验证通过,向 certbot 签发证书文件,包括证书本体、私钥、中间证书链。

  7. 你删除刚才添加的 TXT 记录,避免留下不安全的验证入口。

这套流程手动走起来的心理负担其实不大,很多人是被“泛域名感觉很高级”吓住了,实际上核心就一句话:我得临时改一次 DNS。改完等生效,证书就下来了。

2.3 泛域名证书到底覆盖哪些域名,边界要想清楚

泛域名证书的*.example.com有几个边界规则必须提前搞清楚。第一,它只匹配一级子域名,api.example.com能覆盖,但dev.api.example.com覆盖不了,后者需要用*.api.example.com单独签一张证书。第二,它不覆盖裸域名example.com本身,如果你想让主站也走 HTTPS,必须在签发时同时指定example.com和*.example.com两个域名,Let‘s Encrypt 允许一张证书里包含多个域名。第三,泛域名证书的匹配是严格的一对一,不会跨域名,*.example.com对*.example.org无效。

我在实际项目里见过有人只签了泛域名就以为万事大吉,结果裸域名example.com访问时浏览器直接报证书不匹配。所以签发命令里一定要把裸域名和通配符域名一起列入,虽然这会增加 DNS 验证的 TXT 记录条数,但对后续维护是值得的。

3. 动手前必须准备好的三样东西:域名权限、DNS 控制台和 certbot 环境

3.1 域名必须是你自己能管理 DNS 的

泛域名签发的前提是你对域名的 DNS 有完全控制权。这里的“控制权”具体指的是你能登录域名注册商或者 DNS 托管服务商的控制台,手动添加、删除 TXT 记录。如果你用的是 Cloudflare、阿里云 DNS、DNSPod、Route 53 这类托管解析服务,只要你能登录账号,就满足条件。

特别提醒一下:如果你当前域名的 DNS 解析本身不在你需要部署证书的这台服务器上管理,比如域名是在 A 服务商注册、DNS 解析托管在 B 平台,那你必须在 B 平台操作 DNS 记录。这个和你服务器上装没装 DNS 服务毫无关系。另外,如果 DNS 之前刚做过迁移或者换过解析商,建议先确认_acme-challenge这类子域名的解析权限没有被旧服务商残留记录干扰。

3.2 certbot 安装与版本确认

Ubuntu 系统上安装 certbot 有两套常见方案,一套是 Ubuntu 官方软件源自带的版本,另一套是 certbot 官方 PPA 里的最新版。我的建议是直接使用官方 PPA,因为 Let‘s Encrypt 的 ACME 协议和服务端也在迭代,软件源里的 certbot 版本如果过旧,可能出现兼容性问题。

执行以下命令添加 PPA 并安装:

sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:certbot/certbot sudo apt update sudo apt install certbot

安装完成后确认版本:

certbot --version

正常情况下你会看到类似certbot 2.x.x的输出。如果版本号很低,比如0.x.x,说明源有问题,先解决版本再继续。

这里要注意,certbot 的官方文档明确推荐用 pip 安装到虚拟环境里的方式,但我在生产服务器上更倾向于 PPA 方案,因为后续卸载和管理更符合 Ubuntu 系统习惯,apt升级也会自动处理 certbot 的依赖。对于手动配置泛域名证书这个场景,PPA 版本的功能已经完全够用。

3.3 DNS 服务商控制台的准备

在开始签发之前,先登录你的 DNS 服务商控制台,找到example.com的解析记录管理页面。不同平台的界面不一样,但核心操作是一致的:添加记录时,类型选择 TXT,主机记录填_acme-challenge,记录值留空,等 certbot 生成后填入。

有一个小技巧推荐给大家:先在控制台里把页面打开到“添加记录”这一步,不要关。等 certbot 跑到等待验证的环节时,你只需要复制粘贴记录值、提交、等生效,整体流程顺畅很多。否则等你收到验证提示再手忙脚乱地登录控制台,DNS 生效时间可能已经过去一半了。

4. 手动签发全流程实操:从生成 CSR 到拿到证书文件

4.1 Certbot 手动模式的命令解析

手动签发泛域名证书的核心命令是这个:

sudo certbot certonly --manual --preferred-challenges dns -d example.com -d '*.example.com'

我来逐参数解释一下这样写的原因,避免你只是机械复制的:

  • certonly:只获取证书,不自动修改 Nginx 或 Apache 配置。这一步对手动配置场景特别重要,因为很多人的服务器上可能同时跑了多个 Web 服务,certbot 的自动配置插件有时会直接改配置导致服务异常。手动模式就是要文件,配置自己来。
  • --manual:开启交互式手动验证模式。certbot 不会自动帮你添加 DNS 记录,而是把需要添加的 TXT 记录信息显示出来,等你手动操作完成后继续。
  • --preferred-challenges dns:指定使用 DNS-01 验证方式,这是泛域名签发必须的参数。
  • -d example.com -d '*.example.com':声明证书要覆盖的域名。这里用了两次-d参数,注意'*.example.com'需要用单引号包裹,防止 shell 把星号当做通配符展开。为什么把裸域名和泛域名都写上,我在前面已经讲过了,为了覆盖主站和所有一级子域名。

执行这个命令后,certbot 会先输出一些协议确认信息,要求你同意 Let‘s Encrypt 的服务条款,并且询问是否愿意将邮箱分享给 EFF 基金会。第一次使用的时候按提示输入邮箱并同意即可,后续续期也会用到联系邮箱。

4.2 关键环节:添加 TXT 记录并等待 DNS 生效

命令执行到中间环节,certbot 会输出类似这样的内容:

Please deploy a DNS TXT record under the name: _acme-challenge.example.com with the following value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Before continuing, verify the record is deployed.

这段提示的意思非常明确:去你的 DNS 控制台,在example.com的解析记录里添加一条 TXT 记录,主机记录填_acme-challenge,记录值填输出的那一长串字符。如果你同时指定了裸域名和泛域名两个-d参数,certbot 可能要求你添加两条 TXT 记录,分别验证两个域名,不要漏看。

添加完成后,不要急着按回车。先花几秒钟检查一下:

  • 主机记录是否填的是_acme-challenge而不是_acme_challenge?下划线和连字符的区别很要命。
  • 记录值是否完整复制,有没有遗漏字符?可以点开记录查看详情确认。
  • 当前生效的解析记录里,是否之前已经存在同名的 TXT 记录?如果有,先删掉旧的,避免冲突。

检查无误后,可以提前验证一下 DNS 是否生效,这个动作能帮你省下不少等待时间。用dig命令在本地查询:

dig TXT _acme-challenge.example.com @8.8.8.8

如果你本机没有dig工具,可以用nslookup:

nslookup -type=TXT _acme-challenge.example.com 8.8.8.8

看到返回结果中包含你刚才添加的字符串,就说明 DNS 已经全球生效了。注意查询时指定@8.8.8.8或114.114.114.114这样的公共 DNS 服务器,避免本地缓存造成误判。

确认生效后,回到终端按回车,certbot 会继续完成验证并签发证书。

4.3 签发完成后生成的文件清单

签发成功后,certbot 会输出证书保存路径,并提示你已经成功获取证书。CentOS 或 Ubuntu 上默认路径都是/etc/letsencrypt/live/example.com/,这个目录里包含四个文件,它们在 Nginx 或其他 Web 服务器配置中各自承担不同角色:

文件作用对应的 Nginx 配置项
fullchain.pem完整的证书链,包含站点证书和中间证书ssl_certificate
privkey.pem私钥文件,服务器端持有ssl_certificate_key
cert.pem仅站点证书,不包含中间证书一般不直接用,除非你需要单独合并
chain.pem中间证书链拼接时使用

最需要注意的是fullchain.pem和privkey.pem,Nginx 配置里就靠这两个文件。很多人图省事直接拿cert.pem作为证书配置,结果 Android 客户端或某些浏览器会报证书链不完整,原因就是缺少中间证书。fullchain.pem才是“证书 + 中间证书”的完整组合,务必优先使用它。

签完证书后,还有一件重要的事没有做:删除刚才添加的 TXT 记录。因为验证已经完成,这条记录已经没有价值,留着反而给了别人可乘之机。回到 DNS 控制台,把刚才添加的 TXT 记录删除并确认生效,这一步一定要做。

4.4 私钥权限一定要收紧:一个容易忽略的安全细节

/etc/letsencrypt/live/example.com/目录里的私钥文件privkey.pem是敏感文件,Nginx 的 worker 进程通常以 www-data 用户运行,能够读取证书目录即可,但这不代表权限可以随意放开。

默认情况下,certbot 会将私钥权限设置为0600,属主为 root。这个权限意味着只有 root 用户能读写,安全性没有问题。你在配置 Nginx 时,确保 Nginx master 进程以 root 启动、reload 后能读取到私钥即可。不建议为了省事把私钥权限改成0644或者把证书文件复制到 Web 根目录下,一旦私钥泄露,攻击者就可以用它解密你所有用户的 HTTPS 流量,后果非常严重。

如果因为特殊原因必须把私钥复制到其他目录,务必将权限设置为600,且属主为运行 Web 服务的用户。

5. Nginx 配置落地:认证链完整配置和常见坑位

5.1 一个可以直接套用的 Nginx 配置片段

拿到证书文件后,配置 Nginx 不算难,但有几个细节容易翻车。先看一个完整的 HTTPS server 配置示例:

server { listen 443 ssl http2; server_name api.example.com; 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; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 其他业务配置 location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } }

这个配置里有几个关键点解释一下。ssl_protocols只开启 TLS 1.2 和 1.3,TLS 1.0 和 1.1 已经进入退役阶段,为了安全不要再开。ssl_ciphers选用常见的强加密套件,HIGH:!aNULL:!MD5是简洁且安全的默认组合,如果你没有特殊兼容性需求,直接用即可。http2选项是让 Nginx 在 TLS 上启用 HTTP/2,对性能有提升,如果你的 Nginx 版本较低不支持,去掉这个参数即可。

配置完成后,用以下命令测试配置文件是否有语法错误:

sudo nginx -t

看到syntax is ok和test is successful输出后,再执行 reload 让配置生效:

sudo systemctl reload nginx

5.2 多个子域名如何共用一张证书:server_name 的灵活配置

泛域名证书的最大价值就是一张证书配多个子域名站。如果api.example.com、admin.example.com、static.example.com都在同一台服务器上,你可以为每个服务各写一个server块,每个块里的ssl_certificate和ssl_certificate_key都指向同一份文件。证书会按域名匹配,浏览器验证时只要域名在证书的覆盖范围内,就不会报错。

如果你希望用一个server块统一处理所有泛域名下的请求,可以这样配置:

server { listen 443 ssl http2; server_name *.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # 根据子域名分发请求 ... }

不过生产环境我一般不建议这么干,因为多站点架构下,不同子域名的业务逻辑、静态资源路径、反向代理目标差异很大,写在一个块里会让 Nginx 配置变得难以维护。按子域名拆分server块,配置语义清晰,后续扩展也不容易误伤其他服务。

5.3 HTTP 强制跳转 HTTPS 的配置思路

证书部署之后,HTTP 访问应该统一跳转到 HTTPS,否则用户输入http://api.example.com时浏览器不会自动升级到安全连接。在 80 端口的server块里配置一次强制跳转即可:

server { listen 80; server_name api.example.com; return 301 https://$host$request_uri; }

注意这里用的是$host而不是$server_name,这样即使用户通过子域名访问,也能正确跳转到对应的 HTTPS 地址。如果你有多个子域名,每个子域名都需要一个对应的 80 端口跳转块,或者写一个默认的 80 端口跳转块统一处理。

6. 续期实战:泛域名证书的续期方式和踩坑记录

6.1 为什么泛域名证书不能像单域名那样自动续期

这是泛域名证书和单域名证书最大的区别,也是最多人栽坑的地方。单域名证书如果走 HTTP-01 验证,certbot 的自动续期任务可以在服务器上直接完成验证,因为文件服务是现成的。而泛域名证书的 DNS-01 验证需要手动添加 TXT 记录,certbot 自身没有 DNS 服务商的 API 权限,没法替你把记录加上去,所以不能完全自动化。

这意味着续期时你还是得手动操作一遍 DNS:添加 TXT 记录、等生效、让 certbot 验证、删记录。好消息是 Let‘s Encrypt 的证书有效期是 90 天,意味着你每 90 天需要手动操作一次。听起来麻烦,但实际熟练后整个过程也就是 10 分钟的事。你可以把提醒设在手机上,也可以用系统日历提醒,或者干脆在服务器上写个 cron 任务:提前 30 天检查证书有效期,如果剩余天数小于 30 天就发邮件提醒你,到期前手动续签。

6.2 续期命令:一样的参数,但要注意有效期检查

证书到期前 30 天时可以执行续期命令:

sudo certbot certonly --manual --preferred-challenges dns -d example.com -d '*.example.com' --force-renewal

这里多了一个--force-renewal参数,作用是强制重新签发一张新证书,而不是检查现有证书后跳过。如果你不加这个参数,certbot 在证书还有效时会直接提示证书未到期、无需续期。

执行续期时,流程和首次签发完全一样。certbot 会再次生成新的 TXT 记录值。重点提醒一下:DNS 控制台里旧的 TXT 记录已经删掉了吗?续期时一定要确认没有同名记录残留,否则新记录和旧记录并存,CA 查询时会看到多个 TXT 值,验证可能失败。

续期成功后,certbot 会更新/etc/letsencrypt/live/example.com/下的文件。Nginx 不需要重启,只需要重新加载:

sudo systemctl reload nginx

6.3 一个常见的 SSL 连接报错:证书链不完整导致客户端验证失败

部署泛域名证书后,最常遇到的报错之一是:

curl: (60) SSL certificate problem: unable to get local issuer certificate

这个错误的核心原因是服务器返回的证书链不完整,客户端无法验证证书的信任链。大多数时候是因为你把cert.pem当成了fullchain.pem来配置。注意我前面那张表格里的说明,cert.pem只有站点证书,不包含中间证书,所以客户端中间证书缺失就断了验证路径。解决方法是把 Nginx 配置里的ssl_certificate改成/etc/letsencrypt/live/example.com/fullchain.pem。

还有一种情况是配置没问题,但客户端本地 CA 证书库过旧,不认识 Let‘s Encrypt 的中间证书。这种情况一般不会出现在主流浏览器上,但会出现在一些老系统或定制系统的curl命令上。解决办法是更新客户端的 CA 证书库,Ubuntu 上用:

sudo apt install ca-certificates

6.4 端口和防火墙问题:证书配置正确但外网访问失败

证书签发成功、Nginx 配置无误,但外网访问https://api.example.com超时或者拒绝连接,这时候十有八九是防火墙没放行 443 端口。Ubuntu 默认的ufw如果之前没有放行该端口,会拦截所有 HTTPS 请求。执行:

sudo ufw allow 443/tcp sudo ufw status

如果你用的是云服务器,还需要去云控制台的安全组或防火墙规则里放行 443 端口。云服务器上配了 ufw 但没同步安全组规则的情况非常常见,排查时要两头一起看。

7. 常见问题速查表:一次性解决你绕不开的 SSL 报错

报错或现象根本原因解决方案
SSL certificate problem: unable to get local issuer certificateNginx 配置使用了cert.pem而不是fullchain.pem修改ssl_certificate为fullchain.pem,reload Nginx
curl: (60) SSL certificate problem客户端 CA 证书库过旧更新客户端ca-certificates包
NET::ERR_CERT_COMMON_NAME_INVALID访问域名不在证书覆盖范围内,比如dev.api.example.com超出了*.example.com的匹配范围重新签发包含该域名的证书,或确保访问域名是一级子域名
SSL connection required but not provided服务端未启用 TLS,数据库客户端要求 SSL 连接检查服务端配置是否开启 SSL 支持
DNS 验证失败,提示 TXT 记录未找到添加 TXT 记录有误或 DNS 尚未生效检查主机记录是否为_acme-challenge,使用dig查询公共 DNS 确认生效
nginx: [emerg] cannot load certificate key私钥权限过大或属主不对,Nginx 无法读取设置privkey.pem权限为600,确保属主为 root 或 Nginx 运行用户
访问 HTTPS 超时防火墙或云安全组未放行 443 端口ufw allow 443/tcp,并确认云控制台安全组规则

8. 手动配置完成后,我建议你做的一件事:写一个续期脚本提示自己

说实话,泛域名证书手动配置唯一的痛点是续期。我踩过两次证书到期才发现没续的坑之后,在服务器上写了一个简单的 Shell 脚本,用来检查证书剩余有效期,不足 30 天就输出警告并在 Nginx 访问日志里记录。你完全可以照这个思路自己做:

#!/bin/bash expire_date=$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/cert.pem | cut -d= -f2) expire_epoch=$(date -d "$expire_date" +%s) now_epoch=$(date +%s) days_left=$(( (expire_epoch - now_epoch) / 86400 )) echo "Certificate days left: $days_left" if [ $days_left -lt 30 ]; then echo "Warning: certificate will expire in $days_left days" fi

把脚本放到/usr/local/bin/check_ssl_expiry.sh,配合 cron 定期执行:

0 9 * * * /usr/local/bin/check_ssl_expiry.sh >> /var/log/ssl_check.log 2>&1

这样做的好处是每次登录服务器时可以先看一眼有没有警告,提前安排好续期时间,不用每次都等到浏览器报“您的连接不是私密连接”才开始手忙脚乱。我个人习惯是每次续期完顺手把下一次的续期提醒写到手机日历里,双重保险。

以我自己的体会来说,手动配置泛域名证书这件事难度不在命令本身,而在于对 DNS 验证流程的理解和对证书文件链路的把握。只要把 DNS-01 验证的原理想明白,后面所有的问题排查都只是顺藤摸瓜的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询