干了七八年运维,我最烦的不是半夜服务挂掉,而是 SSL 证书永远在我最忙的时候悄悄到期。以前手工管证书的时候,每张证书都要记到期日,三个月一续,一忙起来就忘,忘了就收到浏览器“连接不安全”的告警,用户追、领导问,特别狼狈。后来我把所有证书收编进 QClaw 龙虾智能运维平台统一托管,免费 SSL 的申请、部署、续期全走自动化,总算把这件事从高危手工活变成了平台日常任务。这篇就把我完整的部署过程、踩过的坑和现在的巡检习惯写下来,给还在手工续证书的朋友一个参考。
1. 为什么我会把 SSL 证书这摊事交给 QClaw 龙虾智能运维
1.1 手工管证书的三种疼法
早年管证书完全是日历提醒驱动的。每次申请一张证书,第一件事是在手机日历里设置到期前两周的提醒,然后在服务器上执行一串命令,生成私钥、生成 CSR、提交验证、下载证书、配置服务、重启进程。听起来不复杂,但站点一多,事情就变味了。
第一种疼是时间碎片化。我有过好几台服务器,每台上挂了不同域名,到期时间参差不齐。今天这个域名要续,明天那个域名要换,每周都得抽时间处理证书的事情,而且每张证书的操作流程几乎一样,重复劳动非常消磨精力。
第二种疼是容易乱。域名、私钥、证书文件散落在不同机器的不同目录里,时间一长根本不记得哪台机器上放的是哪张证书。有一次我为了找一张老域名的证书文件,把服务器翻了个底朝天,最后发现那张证书早就过期了,纯粹是白忙一场。
第三种疼是没记录。手动改配置、手动 reload,这些操作没有任何留痕。等出了问题想追溯“谁动过证书、什么时候动的”,两眼一抹黑。对于需要协作的团队来说,这种黑盒状态基本不可接受。
1.2 脚本方案为什么不够用
也有人说,证书续期不是有现成脚本吗?确实,用 ACME 客户端配合定时任务,能解决一部分自动续期问题。但脚本方案有几个绕不过去的短板:
- 脚本只管一台机器。如果我有五台服务器、二十个域名,脚本要在每台机器上单独部署、单独维护,本身就成了新的运维负担。
- 脚本没有状态管理。申请失败、校验失败、部署失败,全靠日志。日志没看,就等于没发生。
- 脚本没法协作。团队成员想看一眼当前证书还剩多少天,必须登录服务器去查,权限也不好收敛。
所以我一开始就把目标定为:找一个能统一管理多台服务器、多个域名的证书托管方案,而 QClaw 龙虾智能运维恰好把“申请—校验—部署—续期—告警”这条链路串成了完整的平台能力,这才是我决定用它管证书的核心原因。
2. 先摸清 QClaw 龙虾的证书流转机制再动手
2.1 证书被当成任务而不是脚本
用 QClaw 龙虾之前,我习惯把证书续期理解成“定时跑一遍脚本”。但平台的处理逻辑不太一样,它把每一张证书都抽象成独立的任务,带着完整生命周期状态:待申请、验证中、已签发、部署中、已生效、续期中、异常终止。
这个设计带来的好处是,每一个环节都有明确状态和可查询日志。比如申请失败时,任务会停在“异常”状态,并告诉你具体是哪一步出了问题;而脚本方案往往只能甩给你一串非零退出码。
我第一次用的时候,平台创建完任务后能看到一张完整的时间线,从提交申请到 DNS 验证到最终下发到 Web 服务,每一步都有时间戳和操作记录。后来排查问题的时候,这种状态机设计帮了大忙,定位问题从“猜”变成了“看状态”。
2.2 免费证书的签发与验证方式
免费 SSL 证书的签发走的是 ACME 协议,本质逻辑很简单:你向公共 CA 证明你确实拥有这个域名,CA 确认后就签发证书。证明方式主要有两种,平台里也都支持:
| 验证方式 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| HTTP-01 | 在域名根路径下放一个临时校验文件,CA 通过 HTTP 访问来确认域名归属 | 网站 80 端口可达 | 简单,但必须占用 80 端口,且不允许端口被代理遮挡 |
| DNS-01 | 在域名的 DNS 解析里加一条 TXT 记录,CA 通过 DNS 查询来确认 | 任意场景 | 不依赖 80/443 端口,更适合自动化,但需要 DNS 服务商开放 API |
我最终在平台里选择了 DNS-01 验证。主要原因是它不依赖端口连通性。之前我遇到过 80 端口被云安全组挡掉导致 HTTP-01 验证一直失败的情况,换了 DNS-01 之后完全绕开了这个限制,只要域名解析在支持 API 的 DNS 服务商手里,就能稳定跑通。
2.3 证书存储与分发的设计
证书签下来之后,平台会统一管理证书文件和私钥。这里有个容易被忽略的点:私钥权限。平台默认把私钥文件权限设置为 640,属主为 root、属组为 Web 服务运行组,避免普通用户可读。这个细节比我手动操作时严谨得多,毕竟我自己写脚本的时候经常图省事直接 chmod 644,等于把私钥晾在所有人面前。
分发环节也省了很多事。平台可以把同一张证书一键分发到多台服务器的指定目录,并自动执行服务 reload。不需要我再手动 SSH 到每台机器去复制文件、改配置。对我这种多服务器场景来说,光这一步就省了至少半天的重复劳动。
3. 动手前的边界梳理:域名、DNS 与权限
3.1 域名与 DNS 解析准备
在 QClaw 龙虾里创建证书任务之前,有一项前置条件必须先确认:域名得托管在支持 API 配置解析记录的 DNS 服务商处。因为 DNS-01 验证需要平台自动往解析里添加_acme-challenge的 TXT 记录,如果服务商不开放 API,这一步就只能手动操作,自动化链条就断了。
我自己的做法是提前把域名的 TTL 调短。以前默认 TTL 是 600 秒,也就是 10 分钟,验证记录生效会比较慢。我提前改成 60 秒,这样平台添加验证记录后,传播等待时间大幅缩短,整个签发过程会更流畅。需要注意的是,TTL 调整要提前做,不要在签发的当口才想起来,DNS 缓存是业界老大难问题,临时抱佛脚没用。
3.2 服务器接入与目标位置梳理
证书最终还是得落到 Web 服务器上,所以目标服务器要提前接入平台。我是在 QClaw 龙虾里录入主机信息,装上对应的采集组件,建立管理通道。这一步做完之后,证书分发才能在平台界面里直接操作,否则部署环节只能停在“已签发”状态,没法继续往下走。
接入的同时,我逐一梳理了每台服务器上的 Web 服务类型和配置文件路径。比如某台 Nginx 服务器的证书目录是/etc/nginx/ssl,另一台网关机的证书目录是/data/certs。把这些信息记下来,后续创建证书任务时可以直接选“部署到指定路径”,不用每次重新填。
3.3 权限最小化:API 密钥与文件权限
自动化绕不开 API 密钥。我的原则是:能用只读权限就不用写权限,能限定单域名就不给全域名权限。在 DNS 服务商后台单独创建了一把 API 密钥,只授权 DNS 记录的新增和删除操作,并限定只作用于需要签发证书的域名列表。这样即使平台侧万一被攻破,攻击者能做的也只是加几条 TXT 记录,破坏面小得多。
服务器侧的权限也要提前理清。Web 服务运行用户是谁、证书目录归属谁、reload 命令是否需要 sudo,这些在部署前测试一遍。我踩过一次挺低级的坑:证书部署上去了,但 Nginx 进程因为读不到私钥文件导致启动失败,排查半天发现是属主不对。所以现在我在接入阶段就把目录属主、权限核对清楚,权限问题尽量在部署前暴露。
4. 完整部署实录:从录入站点到证书生效
4.1 在平台上创建证书任务
部署流程的起点是创建证书任务。我一般按域名维度来建:一个域名对应一个任务,多个域名如果挂在同一个 Web 服务上,也可以合并成一张多域名证书任务。平台里需要填的核心参数有三个:
- 域名列表:支持填多个域名,第一项作为主域名。
- 证书类型:选择免费证书线路,走 ACME 协议向公共 CA 申请即可。
- 验证方式:我选 DNS-01 自动验证,前提是前面提到的 DNS API 密钥已经配置好。
创建完成后,任务进入“待申请”状态。初次看到这个界面时我还有点不适应,因为我习惯的是命令行操作,但实际用下来发现界面化的好处是参数一目了然,团队里其他人也能看懂这个证书到底是怎么配的。
4.2 配置 DNS 自动验证
这是整个流程中最关键的一步。我在平台的凭据管理里添加了 DNS 服务商的 API 配置,填上 Access Key 和 Secret(用最小权限的那把密钥),平台就能在每次申请证书时自动添加、删除验证记录。
触发申请后,平台的操作顺序是这样的:先向 CA 提起验证请求,CA 给出需要验证的随机值;平台调用 DNS API 添加一条_acme-challengeTXT 记录,记录值就是随机值;然后平台进入轮询等待状态,定期检查这条记录是否已被权威 DNS 解析到;确认解析生效后,平台再通知 CA 进行验证;验证通过后,平台调用 DNS API 把临时记录清理掉。
整个过程里我最担心的其实是对接失败。第一次运行时我一直盯着日志看,看到“TXT record has been published”的日志出现,又等了大约 30 秒,看到“Validation successful”,才真正放下心来。平台等验证记录生效的时间默认是 30 到 60 秒,如果 TTL 调得长或者权威解析更新慢,会适当多等几轮。
4.3 签发与部署:从证书文件到 Web 服务
DNS 验证通过后,CA 返回证书文件和私钥,平台生成完整的证书文件包,包括终端证书、中间证书和私钥。我习惯在平台里直接查看证书信息的摘要,确认签发域名、生效时间和到期时间都对得上,再进行部署。
我的目标服务器跑的是 Nginx,部署配置大概是这样的:
server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem; # 其他站点配置省略 }平台部署动作的本质,就是把这套文件推送到目标服务器的指定目录,并执行一次nginx -s reload。这里值得多说一句:reload 之前平台上会自动跑一次配置检查,确认证书文件路径、权限都没问题才发信号,避免因为配置错误把服务 reload 挂掉。
4.4 验证部署结果
部署完成后,验证环节不能省。我在本机用浏览器访问域名,确认地址栏出现小锁标志,这只是第一步。更准确的验证是用命令行看证书链路:
openssl s_client -connect example.com:443 -servername example.com这条命令会输出服务器返回的完整证书链,我重点看两个地方:一是subject和issuer,确认证书名称和签发机构正确;二是证书生效时间范围,确认没有被系统时间偏差影响。同时也可以顺手检查一下证书链是否完整,中间证书有没有漏发。
最后回到平台,刷新任务详情,看到状态变为“已生效”,到期时间列显示 90 天后的日期,整个部署流程才算走完。
5. 连续踩过的三个坑以及完整排查链路
5.1 坑一:DNS 验证失败,日志提示 TXT 记录未找到
第一次正式启用 DNS-01 自动验证时,平台的申请任务卡在“验证中”状态,日志里报了一句类似“TXT record not found”的错。当时我第一反应是 DNS API 配置有问题,但检查发现密钥权限正常,临时记录也显示添加成功。
继续排查的思路是这样的:先确认记录是否真的添加到了正确的域名下,再确认权威解析能否查询到这条记录。我直接在命令行用dig工具查:
dig @8.8.8.8 _acme-challenge.example.com TXT +short结果返回为空。明明 API 返回“添加成功”,为什么权威解析查不到?再一细看,发现问题出在域名有多级解析上——我配置的是二级域名,但证书任务里填的域名带了www前缀,平台在www.example.com这个子域下添加_acme-challenge记录时,DNS 服务商默认继承主域配置,导致记录出现在了example.com这一层,而 CA 却去查_acme-challenge.example.com,两边没对上。
解决方式很简单:把证书任务的域名列表直接改成不带www的主域名,申请完再在 Nginx 里把www域名的请求统一指向同一张证书。平台之后会按主域名自动管理验证记录,再没出过同类问题。
5.2 坑二:部署成功但浏览器提示证书链不完整
第二个坑更隐蔽。平台的部署状态显示“已生效”,命令行检查证书也没问题,但浏览器访问时却提示“安全证书不受信任”。排查链路从浏览器扩展到了服务器返回的证书链。
用openssl s_client -showcerts拉出服务器返回的全部证书,发现只返回了终端证书,中间证书缺失。浏览器手里只有内置根证书,没有中间证书,没法把链路补全,自然判定不受信。
根因出在我的部署配置上。我填写的部署目标是cert.pem,而正确的方式应该用fullchain.pem——这是一个包含终端证书和中间证书的合并文件。如果只部署终端证书,Nginx 不会自动帮你去补中间证书,链路就断了。修改 Nginx 配置指向fullchain.pem后 reload,问题立刻消失。
这个坑提醒我:平台提供的文件里,fullchain.pem几乎永远是唯一正确的选择,单独的cert.pem只适合特殊场景。后来我再建任务,部署路径一律指向fullchain.pem。
5.3 坑三:reload 之后端口起不来,服务挂了
最后一次踩坑发生在我给另一台服务器部署证书时。平台显示部署完成,Nginx reload 返回成功,但站点访问立刻报 502,进程状态检查发现 Nginx 实际上已经退出了。
排查链路先从服务状态开始:
systemctl status nginx看到日志里有cannot load certificate的报错,指向私钥文件权限问题。原来目标服务器上安装 Web 服务的用户和我之前测试的那台不同,平台按默认属主写入私钥文件后,Nginx 进程用户没有读取权限,启动时直接拒绝加载。
修复方法是调整文件属主和权限,让 Web 服务运行用户能读私钥:
chown root:www-data /etc/nginx/ssl/example.com/privkey.pem chmod 640 /etc/nginx/ssl/example.com/privkey.pem再 reload 一次就正常了。这个教训让我意识到,每台服务器的运行用户很可能不一样,平台默认配置无法覆盖全部场景。现在的做法是,接入新服务器时第一步就确认 Web 服务运行用户和证书目录的预期属主,提前在平台的主机配置里设好,而不是等服务起不来再查。
6. 续期与日常巡检:别让证书在半夜悄悄过期
6.1 自动续期的时间轴设计
免费证书的有效期一般是 90 天,平台内置了自动续期机制。我在任务详情页里看到默认策略是到期前 30 天开始检查,如果距离到期少于 30 天,平台会自动触发重新申请;如果续期失败,会进入重试,同时等待下一次检查周期。
这个时间轴设计很合理。90 天有效期减掉 30 天提前量,实际证书平均在生效后 60 天左右被替换一次,留出了充足的错峰空间。而且每次续期成功后,平台会重新执行分发和 reload,等于整个生命周期完全托管,不需要我人工干预。
我有一次满打满算验证过这个机制:证书到期前第 27 天,平台主动发起了续期任务,日志显示 DNS 验证、签发、部署、reload 全部在 3 分钟内完成,之后证书剩余天数回到了 90。整个过程我没有敲过一条命令。
6.2 告警订阅怎么配才有用
纯自动续期不代表可以完全不看,万一 DNS API 密钥过期、服务商接口变更,任务就会卡住。我在平台里接了通知渠道,绑定到团队的即时通讯机器人上,并设置了三类事件通知:
- 续期成功:静默处理,不打扰。
- 续期失败:立即通知,附带上一次出错日志摘要。
- 证书即将在 7 天内过期:警告通知,提醒人工介入。
这里有个小经验是通知分级。如果每张证书每次续期都发消息,群里的信息噪音会非常大,最后大家都麻木了。像我这样只对异常和临期事件发通知,反而能在真正出问题时第一时间看到。
6.3 我的固定巡检习惯
即便有了自动化,我依然保持着定期巡检的习惯。每个月初,我会打开平台的证书列表页,按“剩余天数”排序,一眼扫过去就能看到所有托管证书的健康状态。凡是剩余天数在 40 天以上的,基本不用担心;剩余天数在 30 到 40 天的,说明平台刚完成续期或即将开始续期;剩余天数低于 20 天的且状态不是“已生效”,就需要人工过问一下。
在新域名上线流程里,我也把它固化成了固定动作:域名解析配置完成后,第一件事就是在 QClaw 龙虾里创建证书任务,等证书状态变为“已生效”,再去做流量切换。这样新站点从来没经历过“裸奔”阶段,访问量再小也始终有 HTTPS 兜底。
7. 用顺之后的一些补充看法
现在回头看,最值的不是省掉了敲命令的时间,而是让证书从“一把梭的手工活”变成了带状态、带日志、带告警的常规运维对象。我能随时回答“现在有哪些证书、还剩多少天、上次续期是什么时候”,这在以前几乎是不可想象的。
最后分享一个小技巧:如果你有一批域名挂在同一个 Web 服务上,别一个一个申请,直接在任务里把它们填进同一张多域名证书里。平台会在签发时把多个域名都纳入覆盖范围,后面续期也是一次搞定,工作量直接减半。
踩过几次坑之后,我现在的态度是:能用平台管的就不自己写脚本,能用自动验证就不手动上传文件,能提前验证权限就绝不部署完再查。这套思路目前跑得很稳,希望能给同样在折腾 SSL 证书的朋友省点时间。