十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无80/443端口签发SSL证书:acme.sh与certbot DNS验证实战

无80/443端口签发SSL证书:acme.sh与certbot DNS验证实战 手上有一台服务器域名也解析好了业务跑在内网或者非标端口上80 和 443 因为各种原因端口被占用、防火墙策略、前置网关占用、云厂商侧拦截根本开不出来——这时候想给域名上一张 SSL 证书很多人第一反应是那不就没戏了。其实完全不是。acme.sh和certbot这两个最主流的 ACME 客户端都提供了绕开80和443端口的签发路径核心就是走 DNS-01 验证不去连你的域名而是往你的 DNS 里写一条 TXT 记录CA 侧查到了就给你签。这条路不仅能在端口不可用时把SSL证书签下来还能顺带拿到泛域名证书一张证书覆盖一堆子域名。这篇内容我会把两条路线的完整实操、参数选择、凭证管理和踩坑排查都摊开讲适合手上有域名、有 DNS 控制权、但端口条件受限的运维和开发者参考。1. 为什么没有 80/443 端口证书照样能签下来1.1 三种 ACME 验证方式到底卡在哪要搞清楚这件事得先明白 ACME 协议在签发证书前要干什么。CA 机构比如 Lets Encrypt、ZeroSSL、Buypass不可能凭你一句话就把example.com的证书发给你它必须先确认你确实控制这个域名。确认手段就是挑战验证主流有三种验证方式依赖端口验证位置能否签泛域名端口受限时可用性HTTP-0180http://域名/.well-known/acme-challenge/xxx不能差TLS-ALPN-01443TLS 握手时的 ALPN 扩展不能差DNS-0153出站_acme-challenge.域名的 TXT 记录能好HTTP-01 是最常见的默认方式acme.sh 的--webroot模式、certbot 的--nginx/--apache插件本质都是在 80 端口上放一个临时文件让 CA 来抓。问题在于80 端口在很多环境里就是不归你管有的是被前置的负载均衡或者老旧服务占着有的是云上的安全组压根没放行有的是域名解析根本没指到你这台机器上。这时候你会看到类似Timeout during connect、Connection refused、Invalid response from http://xxx/.well-known/acme-challenge/...这类报错反复重试还会触发失败次数限制。TLS-ALPN-01 更尴尬它直接要 443 端口配合握手条件比 HTTP-01 还苛刻现实中基本只在前两者都不行、且 443 恰好空闲时才考虑。所以当 80 和 443 同时不可用时只剩 DNS-01 这一条路。它不是退而求其次在纯自动化的场景下反而是最省心的选择。1.2 DNS-01 成为唯一稳妥通道的底层逻辑DNS-01 的验证过程可以类比成物业登记CA 不会跑到你家门口敲门连端口而是去物业的公告栏DNS 系统贴一张纸条问你这户人家是不是你只要你能在公告栏上贴出对应的答复内容物业就认了。具体到技术层面CA 会生成一段随机的 token并告诉你期望的 TXT 值通常是 token 的 SHA256 Base64 编码。你需要把它写到_acme-challenge.example.com这条 TXT 记录上。CA 从它自己的递归解析器去查这条记录查到且值匹配就判定验证通过然后签发证书。这个机制带来三个直接好处。第一完全不碰你的服务器端口不依赖入站 80/443服务器只要能有出站 HTTPS 去访问 CA 接口就够了。第二支持泛域名*.example.com只能走 DNS-01这是 ACME 规范硬性规定的HTTP-01 拿不到通配符证书。第三部署位置彻底解耦证书可以在 A 机签然后拷到 B 机、C 机、内网机、Docker 容器里用签发和部署分离非常适合多机分发。代价也很明确你需要对域名的 DNS 有写入权限而且要拿到云 DNS 服务商的 API 凭证。这就引出了下一个话题——碰谁的 DNS、用什么凭证、怎么保管。2. 动手前必须确认的几件事2.1 域名与 DNS 解析权限在敲第一条命令之前先把三件事确认清楚能省掉后面一大半的排错时间。第一确认你的域名托管在哪家 DNS 服务商。是国内某云的云解析还是 DNSPod还是 Cloudflare还是自建的权威 DNS这直接决定你用哪个插件、导出哪几个环境变量。混用会出问题比如域名在 A 商解析你拿着 B 商的 API 密钥去用插件会报找不到域名或者无权限。判断方法很简单dig NS example.com short看一眼返回的 NS 记录归属就行。第二确认你拿到的是有权限改 DNS 记录的账号。最忌讳的是直接拿主账号的永久 AK/SK 到处跑一旦泄露别人能改你所有域名的解析后果比证书泄露严重得多。正确做法是创建一个子账号RAM 用户 / 子用户只授予该域名所在解析产品的读写权限其他一律不给。权限最小化这件事很多人嘴上认同动手就忘我在团队里都是直接卡住评审的。第三确认_acme-challenge这条记录没有被占用或者被 CNAME 劫持。有些历史遗留配置里_acme-challenge.example.com被 CNAME 到了别的地方或者已经存在一条同名的 TXT 记录。ACME 客户端添加新 TXT 时不会帮你清理旧的多条 TXT 记录并存虽然规范上允许但个别服务商的 API 行为不一致建议先手工清干净。注意验证用的 TXT 记录名固定是_acme-challenge加上你的域名。签example.com和*.example.com时两条记录名是同一个_acme-challenge.example.com但值不同客户端会依次写入、依次验证别看到两条同名的就以为出错了。2.2 acme.sh 与 certbot 的安装取舍这两个客户端的定位差别挺明显选哪个主要看你更在意什么。acme.sh 是纯 Shell 脚本几乎零依赖只需要 curl 或 wget、openssl装完就是一个~/.acme.sh目录所有东西自包含迁移和排错都很直观。它内置了上百个 DNS 服务商的 API 插件--dns dns_ali、--dns dns_dp、--dns dns_cf直接一把梭不用额外装 Python 包。它的续期是靠 crontab 驱动的安装时会自动往 crontab 里塞一条--cron任务。certbot 是 Python 写的功能更官方一点插件体系规范跟 Nginx、Apache 的集成更深但 DNS 插件往往要单独装 Python 包而且版本和 Python 环境容易打架。在一台只有 80/443 受限、不需要 Web 服务器集成的机器上certbot 的 DNS 路线反而要多装不少东西。我个人的实际选择是纯 DNS 签发场景acme.sh 优先需要--nginx/--apache那种自动改配置的集成能力或者团队已经统一在用 certbot那就走 certbot。两个都装也不是不行但要注意别让它们的续期任务互相冲突证书文件路径也最好分开管。安装 acme.sh 的标准姿势官方脚本走的是 curl 管道执行curl https://get.acme.sh | sh -s emailyouexample.com装完之后重开一个 shell或者手动source ~/.bashrc让acme.sh这个别名生效。如果你对curl 管道执行有安全顾虑可以走 git 方式git clone https://github.com/acmesh-official/acme.sh.git cd acme.sh ./acme.sh --install -m youexample.com安装位置默认是~/.acme.sh如果你想装到系统级目录或者给别的用户用强烈建议先规划好路径别装完一堆证书再改迁移起来很麻烦。2.3 API 凭证的准备与安全存放凭证是整条链路里最敏感的部分我单独拿出来说。以某云 DNS 为例你需要在控制台创建一个子账号授予该域名解析的读写权限然后拿到一对 AccessKey ID 和 AccessKey Secret。注意只读权限不够ACME 客户端必须能添加和删除 TXT 记录所以至少要有解析记录的增删改查权限。拿到之后不要直接把密钥写进命令行因为 shell 历史会记录history一敲什么都露了。正确做法是export到当前会话或者干脆让客户端把凭证落到配置文件里、权限设成 600。export Ali_Key你的AccessKeyId export Ali_Secret你的AccessKeySecret chmod 600 ~/.acme.sh/account.conf 2/dev/nullacme.sh 在首次使用 DNS 插件后会把凭证自动保存在~/.acme.sh/account.conf里后面续期时就不需要再 export 了。这个文件必须锁死权限chmod 600是底线而且它属于哪个用户就必须用哪个用户去跑续期任务否则读不到凭证会报缺少 API 密钥。certbot 这边走的是独立凭证文件例如/etc/letsencrypt/dns-aliyun.ini格式是键值对同样要chmod 600。下面会细说。注意凭证泄露的杀伤力远大于证书本身。证书泄露顶多是别人能冒充你的域名凭证泄露意味着别人能完全接管你的域名解析可以指向任意地址。所以子账号 最小权限 文件权限 600这三条一条都不能省。3. acme.sh 全流程实操从签发到自动续期3.1 默认 CA 的坑先切回 Lets Encrypt这是最容易踩、又最容易被忽略的一个坑。acme.sh 从 3.0.0 版本开始默认的 CA 从 Lets Encrypt 换成了 ZeroSSL。ZeroSSL 也能用但免费额度和账号注册流程跟 Lets Encrypt 不同很多人照着三四年前的教程敲命令结果拿到的是 ZeroSSL 的证书或者卡在需要先注册 ZeroSSL 账号这一步。我一般会显式把默认 CA 设回 Lets Encrypt避免歧义acme.sh --set-default-ca --server letsencrypt如果你想用别的 CA也可以在第一阶段就指定。签发时用--server参数覆盖Lets Encrypt--server letsencryptLets Encrypt 测试环境不消耗正式额度但证书不被浏览器信任--server letsencrypt_testZeroSSL--server zerosslGoogle Public CA--server google调试验证流程时强烈建议先用letsencrypt_test环境把整个链路跑通确认 DNS 写入、验证、签发、安装、reload 全都正常再切正式环境。Lets Encrypt 正式环境有速率限制同一注册域名每周最多 50 张证书重复验证失败每小时最多 5 次一次没搞明白就硬刚很容易把自己关在门外一小时。3.2 某云域名自动 DNS 验证签发以国内常见的某云解析为例整条命令其实非常短export Ali_Key你的AccessKeyId export Ali_Secret你的AccessKeySecret acme.sh --issue --dns dns_ali \ -d example.com \ -d *.example.com \ --keylength ec-256拆开看每一步在干什么。--issue是签发动作--dns dns_ali指定用某云的 DNS 插件来做验证插件会自动调用 OpenAPI 往_acme-challenge写 TXT 记录、等 CA 查完、再删掉记录全程无需人工干预-d可以写多个第一个是主域名后面的都是 SAN备用域名通配符要加单引号防止 shell 展开--keylength ec-256是密钥算法ECC 256 位比默认的 RSA 2048 更短更快现代客户端兼容性也很好是现在的推荐选择。如果你还是需要 RSA就换成--keylength 2048或者4096。4096不是不能选但握手时计算开销明显更大除非有合规硬要求一般2048足够。跑完这条命令正常情况下你会看到一段带Cert success的输出证书文件落在~/.acme.sh/example.com_ecc/目录下里面有example.com.cer域名证书、ca.cer中间证书、fullchain.cer包含域名证书和中间证书的链、example.com.key私钥。另一个常见服务商是 DNSPod凭证变量不一样export DP_Id你的ID export DP_Key你的Key acme.sh --issue --dns dns_dp -d example.com -d *.example.com --keylength ec-256Cloudflare 这边用的是 Token 而不是全局 Keyexport CF_Token你的APIToken export CF_Account_ID你的AccountID # 用 Zone 级 Token 时通常可以省 export CF_Zone_ID你的ZoneID acme.sh --issue --dns dns_cf -d example.com -d *.example.com关键在于插件的命名dns_ali对应某云dns_dp对应 DNSPoddns_cf对应 Cloudflaredns_gd对应 GoDaddy等等。这个后缀写错是最低级的错误报错信息通常只是找不到插件或者没有可用的 DNS 提供方看到这类提示第一件事就是核对后缀。3.3 手动 DNS 模式给不能给 API 密钥的域名兜底有些域名托管在你不方便拿 API 密钥的地方比如客户自己的账号、公司总部统一管的解析、或者某个只提供网页控制台的小服务商。这时候可以用 acme.sh 的手动 DNS模式acme.sh --issue --dns \ -d example.com \ -d *.example.com \ --yes-I-know-dns-manual-mode-enough-go-ahead-please这条命令会返回两条待添加的 TXT 记录格式大概是Add the following TXT record: Domain: _acme-challenge.example.com TXT value: xxxxxxxxxxxxxxxxxxxxxxxxxxx你把这两条记录手动加到 DNS 控制台等解析生效可以用dig _acme-challenge.example.com TXT short反复确认直到能查到值然后执行acme.sh --renew \ -d example.com \ --yes-I-know-dns-manual-mode-enough-go-ahead-please注意这里用的是--renew而不是--issue这不是笔误手动模式的设计就是这样第一次--issue只负责告诉你该写什么第二次--renew才真正发起验证和签发。手动模式的硬伤是不能自动续期因为没人去帮你改 DNS。它只适合一次性签发或者配合自己写的自动化脚本调用服务商的网页接口或者上游 API。如果你的域名数量多、续期频率高还是老老实实想办法拿到 API 凭证长期看手动模式的时间成本远高于申请子账号的成本。注意手动模式下DNS 记录写入后不要着急立刻执行 renew。TXT 记录生效时间取决于你设置的 TTL 和各地递归的缓存短则几十秒长则十几分钟。用dig从多个公共解析查一下确认都看到了再继续否则会白白消耗一次失败额度。3.4 证书安装、部署与 reloadcmd 联动acme.sh 签发后证书是放在自己的工作目录~/.acme.sh/里的不要直接把 Nginx 配置指到那个路径那个目录是 acme.sh 的私有空间它会在续期时整体替换文件而且结构可能随版本变动。正确做法是用--install-cert把证书安装实质是拷贝 注册续期钩子到你指定的位置acme.sh --install-cert -d example.com --ecc \ --key-file /etc/nginx/cert/example.com.key \ --fullchain-file /etc/nginx/cert/example.com.chain.crt \ --reloadcmd systemctl reload nginx几个要点。--ecc是必须的因为你签发时用了ec-256不加这个参数它会去 RSA 目录里找找不到就报错。--key-file和--fullchain-file分别对应私钥和完整证书链路径自己规划建议统一放在/etc/nginx/cert/或/etc/ssl/private/这类位置。--reloadcmd是这套方案里最有价值的参数之一。它会被写入 acme.sh 的配置每次续期成功后自动执行。这样一来续期完成到服务生效就是闭环的不需要你另写监控脚本去 reload。注意命令里的 reload 要选热加载而不是重启systemctl reload nginx比restart平滑得多不会中断现有连接。安装完之后可以看一眼 acme.sh 的续期任务有没有挂上crontab -l | grep acme.sh正常应该能看到类似这样一行0 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh /dev/nullacme.sh 默认每天凌晨跑一次--cron它会检查所有已签发证书的剩余有效期快到期的默认剩余 60 天内可以通过--days调整自动续期。这个检查非常轻量一天一次完全没问题。如果你想让某个特定证书用不同的续期提前量可以在 issue 时加--days 30。实践中我建议保持默认或者 30 天太短没有容错空间太长也没必要。4. certbot 的 DNS 路线插件与 hook 双方案4.1 dns 插件模式的凭证文件写法certbot 走 DNS 验证有两种方式用现成的 DNS 插件或者用 manual hook 自己实现。先说插件方式。以某云为例需要先装插件# 用 pip 装推荐在虚拟环境里 pip3 install certbot certbot-dns-aliyun # 或者用系统包管理器 yum install -y certbot python3-certbot-dns-aliyun装完之后创建凭证文件/etc/letsencrypt/dns-aliyun.inidns_aliyun_access_key 你的AccessKeyId dns_aliyun_access_key_secret 你的AccessKeySecret然后锁权限chmod 600 /etc/letsencrypt/dns-aliyun.ini签发命令certbot certonly \ --authenticator dns-aliyun \ --dns-aliyun-credentials /etc/letsencrypt/dns-aliyun.ini \ --dns-aliyun-propagation-seconds 30 \ -d example.com \ -d *.example.comcertonly表示只申请证书、不改动任何 Web 服务器配置这跟我们要做的事情完全吻合——反正 80/443 也没开。--dns-aliyun-propagation-seconds 30是让 certbot 在写入 TXT 记录后等 30 秒再去请求 CA 验证这个值怎么定主要看你的 DNS 服务商解析生效速度。国内大型云解析一般 10 到 30 秒足够小服务商可能要 60 秒甚至更久。设置得太短会频繁报验证失败太长就是纯粹浪费时间。DNSPod 的插件叫certbot-dns-dnspod凭证键名是dns_dnspod_api_id和dns_dnspod_api_key命令结构完全一致把dns-aliyun替换成dns-dnspod就行。Cloudflare 的插件是certbot-dns-cloudflare凭证文件里写dns_cloudflare_api_token xxx。证书签发后的存放位置在/etc/letsencrypt/live/example.com/里面有cert.pem域名证书chain.pem中间证书fullchain.pem完整链域名证书 中间证书privkey.pem私钥注意这个目录下都是软链接指向../../archive/example.com/下的实际版本文件。Nginx 配置里引用fullchain.pem和privkey.pem这两个软链接就行续期后软链接会自动指向新版本不用改配置。4.2 manual hook 脚本自己控制 DNS 写入如果找不到对应的插件或者你的 DNS 是自建的、内部管理的那就用 manual hook。原理很简单certbot 在需要写 TXT 记录时会调用你指定的脚本并往环境变量里塞两个值——CERTBOT_DOMAIN要验证的域名和CERTBOT_VALIDATION要写入的 TXT 值。你的脚本负责把它写进 DNS验证完成后再由 cleanup 脚本删掉。写一个 auth 脚本/etc/letsencrypt/hooks/auth.sh#!/bin/bash set -e # certbot 通过环境变量传入 DOMAIN${CERTBOT_DOMAIN} TOKEN${CERTBOT_VALIDATION} # 这里替换成你自己的 DNS API 调用 # 例如调用内部 DNS 管理接口 curl -s -X POST https://dns-api.internal/record \ -H Authorization: Bearer ${DNS_API_TOKEN} \ -H Content-Type: application/json \ -d { \domain\: \_acme-challenge.${DOMAIN}\, \type\: \TXT\, \value\: \${TOKEN}\, \ttl\: 60 } # 等待解析生效这个时间要根据实际环境调整 sleep 20对应的 cleanup 脚本/etc/letsencrypt/hooks/cleanup.sh#!/bin/bash DOMAIN${CERTBOT_DOMAIN} curl -s -X DELETE https://dns-api.internal/record \ -H Authorization: Bearer ${DNS_API_TOKEN} \ --data-urlencode domain_acme-challenge.${DOMAIN} \ --data-urlencode typeTXT两个脚本都要chmod x。然后签发certbot certonly \ --manual \ --preferred-challenges dns \ --manual-auth-hook /etc/letsencrypt/hooks/auth.sh \ --manual-cleanup-hook /etc/letsencrypt/hooks/cleanup.sh \ -d example.com \ -d *.example.com这里有个非常重要的细节必须同时提供--manual-auth-hook和--manual-cleanup-hook否则 certbot 会进入交互模式停下来等你手动按回车这样是没法自动续期的。很多人只写了 auth hook结果certbot renew时卡在交互提示上定时任务里表现为永远跑不完。--preferred-challenges dns也不能省它明确告诉 certbot 用 DNS 验证而不是默认的 HTTP-01。在 80 端口不可用的环境下少了这个参数会直接报连接错误。关于 hook 的另一个坑CERTBOT_DOMAIN在签泛域名时值可能是example.com而不是*.example.com。因为_acme-challenge记录本身就写在example.com的下一级所以你的脚本里不要再拼接*.直接用_acme-challenge.${CERTBOT_DOMAIN}就对了。我见过有人在脚本里判断通配符再特殊处理反而把记录名写成了_acme-challenge.*.example.com这种记录在 DNS 里是非法的。4.3 续期调度与 systemd timercertbot 的续期命令是certbot renew它会遍历/etc/letsencrypt/renewal/下所有证书配置检查剩余有效期剩余不足 30 天默认才真正续期。手工测试时可以用--dry-run走一遍不消耗额度或者--force-renewal强制续期这个会消耗额度慎用调试时很容易把速率限制耗光。在较新的系统上certbot 包会自动安装一个certbot.timer你只要确认它是启用的就行systemctl list-timers | grep certbot systemctl status certbot.timer老一些的系统或者用 pip 装的 certbot需要自己配定时任务。比较通用的做法是写一个 cron每天跑两次0 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook systemctl reload nginx--deploy-hook是关键它只在真正发生了续期时才执行比放在 renew 后面无条件执行更精准。用--quiet减少日志噪音但建议第一次调试时不要加先把输出看全。--dry-run在配好 hook 后一定要跑一次certbot renew --dry-run它会完整走一遍写 DNS → 验证 → 签发测试 CA→ cleanup的流程只不过用的是 staging 环境签出来的证书不被信任但能真实验证你的 hook 脚本、权限、网络通不通。这一步过了正式环境基本就不会出问题。5. 证书落地没有 443 端口的服务怎么用5.1 非标端口上的 TLS 服务配置证书签下来了接下来是怎么用。既然 443 开不出来那就把 TLS 服务放到别的端口比如 8443、9443或者其他任意未被占用的高位端口。Nginx 的配置大致是这样server { listen 8443 ssl; server_name example.com; ssl_certificate /etc/nginx/cert/example.com.chain.crt; ssl_certificate_key /etc/nginx/cert/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有几个取舍值得说。ssl_protocols只留 TLSv1.2 和 TLSv1.3TLSv1.0/1.1 早该淘汰了浏览器也都标记为不安全。ssl_ciphers里ECDHE开头表示前向保密万一私钥将来泄露历史流量也解不开。如果你签的是 ECC 证书会看到ECDHE-ECDSA-那组如果签的是 RSA就是ECDHE-RSA-那组。两套都写上去换算法时不用改配置没有坏处。ssl_prefer_server_ciphers off是个常被写错的地方。老教程里习惯写on但在 TLS 1.3 下客户端偏好优先已经是趋势写off让客户端选更稳妥同时避免服务端列出的密码套件跟客户端不匹配导致的握手失败。另外提醒一句访问地址会变成https://example.com:8443浏览器地址栏会显示端口号这是正常的不是证书问题。有些内部的健康检查、SDK、回调地址需要显式带上端口别忘了同步改。5.2 证书链完整性自查与常见部署错误签完证书、配好 Nginx别急着交付先做三件事自查。第一件看证书链条是否完整。网站检测出 SSL 证书不完整是最常见的用户反馈绝大多数情况是部署时只装了域名证书没装中间证书。CA 签发的证书是三级链根证书内置在浏览器/系统里→ 中间证书 → 你的域名证书。服务端必须把域名证书 中间证书一起发出去客户端才能把链条接上。这就是为什么我一直强调要用fullchain而不是cert/cer。检查方法openssl s_client -connect example.com:8443 -servername example.com -showcerts /dev/null输出里会依次列出服务端返回的证书正常情况下应该有 2 张域名证书 中间证书。如果只看到 1 张就是漏了中间证书。也可以用一条命令直接统计文件里的证书数量grep -c BEGIN CERTIFICATE /etc/nginx/cert/example.com.chain.crtfullchain文件里应该有 2 个BEGIN CERTIFICATE。注意私钥文件里不能混进证书内容二者必须分开存放。第二件验证 SNI 是否正确返回对应证书。一台服务器上挂多个域名时-servername参数模拟的就是浏览器发 SNI 的过程openssl s_client -connect example.com:8443 -servername example.com /dev/null 2/dev/null | openssl x509 -noout -subject -dates看输出的subject里域名对不对notAfter到期时间是否符合预期。第三件实际发个请求确认整条链路通curl -vI https://example.com:8443 --resolve example.com:8443:你的服务器IP--resolve这个小技巧在证书刚配好、DNS 还没切过去或者你不想走公网解析时特别有用它强制把域名解析到指定 IP但依然按域名做 SNI 和证书校验能精确验证证书 域名 端口三者是否匹配。如果看到SSL certificate verify ok基本就没问题了。6. 踩坑实录与问题速查6.1 高频报错对照表实际操作中遇到的问题就那么几类我整理成表方便对着查报错或现象大概率原因处理方式Timeout during connect/Connection refused客户端在用 HTTP-01去连 80 端口显式加--preferred-challenges dns或--dns参数确认走 DNS 验证No TXT record found at _acme-challengeTXT 还没生效或记录名写错用dig从多个公共解析确认核对记录名拼写Invalid validation值不匹配多条 TXT 记录并存CA 查到了旧的那条DNS 控制台清理同名 TXT只留当前要验证的值签名成功但浏览器报证书不完整部署时只用了域名证书没带中间证书改用fullchain文件Cannot find the dns_xxx plugin插件后缀名写错核对服务商对应的插件后缀续期任务静默失败cron 环境缺少环境变量或凭证文件权限不对用绝对路径调用检查account.conf或凭证文件权限too many failed authorizations短时间重复失败触发速率限制换 staging 环境调通链路等一小时后再回正式环境签出来的证书不被浏览器信任误用了 Lets Encrypt staging 环境去掉--server letsencrypt_test切回正式环境重签certbot renew卡住不退出manual 模式只配了 auth hook没配 cleanup hook补齐--manual-cleanup-hook证书日期不对、指向旧版本服务没 reload或配置文件写的是 acme.sh 工作目录检查--reloadcmd确认 Nginx 引用的是安装后的路径这张表里最常见的是前四条。尤其是多条 TXT 记录并存这个很多人不知道自己什么时候加过一条DNS 控制台里历史操作记录翻一下通常能看到。CA 查询 TXT 记录时拿到的是一个集合只要集合里包含期望值就算通过但如果你的旧值还留着而重复签发时产生了新值两条并存虽然理论上仍能通过但某些解析服务商返回顺序不固定极端情况下会出问题。最干净的做法是每次签发前先手工清一遍。6.2 几条掏心窝的经验第一条先用测试环境跑通全链路再动正式域名。这条听起来像废话但它能帮你省掉 90% 的痛苦。链路里的环节其实不少凭证权限、DNS API 通不通、TXT 记录写入是否成功、传播延迟多久、验证能不能过、证书安装路径对不对、reload 命令有没有生效。用 staging 环境一个个排查错了顶多重来不消耗额度用正式环境硬刚三次失败就把你一小时的额度锁死。第二条把续期当成一个独立项目来验证而不是签发成功就完事。签发成功只说明当前这一刻是对的。真正决定生产稳定的是三个月后自动续期那一次。我的做法是签发完成后立刻做两件事一是手工执行一次acme.sh --cron或者certbot renew --dry-run确认整个自动化路径通畅二是把续期相关的日志加一个监控哪怕只是每天早上grep一下日志里有没有error也比完全不管强。证书过期导致的线上事故几乎全部源于以为它会自动续。第三条路径规划一次性做好别中途改。证书文件放哪、哪个用户跑续期、reload 命令怎么写这三件事最好在第一次签发前就想清楚。中途改路径意味着要重新--install-cert改用户意味着凭证文件和 cron 都要迁移改 reload 命令意味着要去翻 acme.sh 的配置。我见过一个团队因为最初把证书放在了部署目录里后来做容器化时整个证书路径全乱返工了两天。第四条别在一个域名上堆太多 SAN。有人图省事把十几个不相关的域名塞进一张证书里想着管理方便。问题在于任何一个域名解析失效或者不再归你所有续期验证就会失败一失败整张证书都续不上一损俱损。合理的做法是按业务域分组一个主域名加它的通配符再加几个必要的二级域名控制在五六个以内既够用又不会因为局部问题影响全局。第五条泛域名证书和普通证书的私钥安全级别要区分开。泛域名证书的私钥一旦泄露攻击者可以伪造你任意一个子域名的服务危害范围比单域名证书大得多。所以泛域名证书的私钥文件权限要更严落地到多台机器时要走安全的分发通道别用明文拷贝、别放公共目录、别提交到代码仓库。这一点在团队协作里尤其重要因为经手的人越多出问题的概率越大。我自己的习惯是每台需要用到证书的机器上用统一的配置管理工具从签发机拉取拉取过程走内网加密通道落地后立刻chmod 600并且把证书文件权限加进例行巡检。这套流程跑熟之后端口能不能开 80 和 443就真的只是个部署细节了跟证书的签发和续期完全脱钩。
返回列表