
1. 这不是“点几下就完事”的证书生成器——它是一套可复用、可审计、可嵌入CI/CD的SSL证书交付流水线你可能已经试过certbot --nginx三分钟搞定一个域名的HTTPS也可能在云控制台点过“一键申请”看着绿色小锁图标弹出来就关掉了页面。但真正让我在运维现场反复摔跟头、又反复重写脚本的从来不是“怎么生成证书”而是“证书生成之后谁来管它什么时候续续失败了告不告诉人证书链对不对私钥权限松没松Nginx reload有没有卡住服务有没有短暂中断”——这些细节才是生产环境里SSL证书真正的“临界点”。这篇《Quick Guide to Generating SSL Certificates》名字叫“快速指南”但它的真实定位是给中高级运维、SRE、DevOps工程师和中小团队技术负责人看的一份“防踩坑型实操手册”。它不教你怎么在本地localhost跑通Let’s Encrypt测试环境而是直击真实场景你手上有3个子域名api.example.com、admin.example.com、*.cdn.example.com其中一个是泛域名需要通配符DNS验证你用的是自建Nginx集群没有ACME客户端自动集成能力你的发布流程走GitLab CI但证书不能硬编码进仓库你上周刚因为证书过期导致支付接口502老板问“为什么监控没报警”。这些问题全在这篇指南里拆解清楚。核心关键词——SSL证书、Let’s Encrypt、ACME协议、certbot、DNS-01验证、通配符证书、私钥安全、证书链完整性、自动化续期、CI/CD集成——每一个都不是孤立概念它们像齿轮一样咬合运转。比如你选了DNS-01验证就绕不开API密钥的安全分发你用了通配符证书就必须理解*.example.com不覆盖example.com这个经典陷阱你把certbot renew塞进crontab就得知道默认的--pre-hook和--post-hook执行时机与Nginx reload的竞态关系。这不是工具说明书这是把证书从“能用”推进到“稳用”“可信”“可追溯”的完整路径。如果你是刚接触HTTPS的新手建议先搞懂TLS握手四次交互和X.509证书结构如果你是天天改Nginx配置的老手这篇能帮你把证书管理从“救火式运维”升级为“声明式治理”。2. 为什么不用浏览器点一点——方案设计背后的五层现实约束2.1 不是所有环境都允许HTTP-01验证Let’s Encrypt默认推荐HTTP-01验证它往你服务器的.well-known/acme-challenge/路径下放一个临时文件然后用自己的爬虫去GET访问。这要求两点第一你的Web服务必须对外暴露80端口且可被公网访问第二该路径不能被CDN缓存、不能被WAF拦截、不能被Nginx的location ~ \.php$规则误判。我去年帮一家做医疗SAAS的客户排查时发现他们WAF策略里有一条“禁止访问含.well-known路径的请求”结果certbot每次验证都返回403日志里只显示“Connection refused”根本看不出是WAF在拦。这种情况下HTTP-01就是死路一条。而DNS-01验证完全绕开Web层——你只需要让Let’s Encrypt能通过公开DNS查询到你设置的TXT记录即可。它不要求你开放任何端口不依赖你的Web服务状态甚至你的服务器可以完全离线只要DNS提供商支持API批量操作就能完成验证。这就是为什么本指南默认采用DNS-01它把证书颁发过程从“网络可达性问题”降维成“DNS权限管理问题”后者更可控、更可审计。2.2 通配符证书≠万能钥匙它的使用有明确边界*.example.com能匹配a.example.com、b.example.com但绝对不匹配example.com根域名和a.b.example.com三级域名。这是RFC 6125白纸黑字写的规则不是Let’s Encrypt的限制是整个TLS生态的共识。很多团队第一次申请通配符证书后发现官网首页打不开查半天才发现Nginx里server_name只写了*.example.com漏掉了example.com。正确写法必须是server_name example.com *.example.com;更隐蔽的坑在于CDN场景Cloudflare、阿里云全站加速等默认会把example.com和www.example.com视为两个独立主机名如果你只给*.example.com申请了证书CDN回源时访问example.com就会报SSL_ERROR_BAD_CERT_DOMAIN。所以通配符证书的实际适用范围必须结合你的域名规划、CDN配置、负载均衡策略一起画图确认。本指南后续实操环节会强制要求你用openssl x509 -in fullchain.pem -text -noout | grep DNS命令逐行核对Subject Alternative NamesSANs确保每个要保护的域名都明明白白列在里面而不是靠“应该能覆盖”这种模糊判断。2.3 私钥安全不是“chmod 600”就万事大吉chmod 600 /etc/letsencrypt/live/example.com/privkey.pem是入门级操作但生产环境要问三个问题第一这个文件所在目录/etc/letsencrypt/live/的父目录权限是否宽松如果/etc/letsencrypt是755攻击者只要能读取该目录列表就能知道哪些域名有证书第二privkey.pem是否被进程意外dump到日志某些Nginx调试模式会把加载的证书路径原样打印第三最致命的——证书续期时certbot默认会直接覆盖旧私钥文件。这意味着如果你的Web服务在reload过程中恰好读取到半截写入的私钥race condition就会触发OpenSSL的bad magic number错误导致服务雪崩。解决方案不是禁用自动续期而是用--deploy-hook把新证书复制到独立受控目录并用原子化操作如mv替换符号链接切换生效路径。本指南的实操部分会给出完整的权限树检查清单和原子部署脚本确保私钥生命周期全程可控。2.4 “自动续期”不等于“永不中断”它需要可观测性兜底certbot renew加进crontab只是开始不是终点。Let’s Encrypt证书有效期90天但实际建议在60天时启动续期留出30天缓冲期处理失败。然而crontab本身不提供失败通知、不记录执行上下文、不区分“本次续期成功但Nginx reload失败”和“DNS验证超时”这两种完全不同性质的错误。我见过最惨的案例是一家电商公司他们的/var/log/letsencrypt/renewal.log里连续三个月都有Failed to renew certificate example.com但没人去看日志——直到大促当天凌晨证书过期订单系统全部502。所以本指南强制要求所有续期任务必须接入统一日志系统如LokiGrafana关键指标如renewal_success{domainexample.com}必须配置企业微信/钉钉告警每次续期后必须执行curl -I https://example.com | grep HTTP/2 200做端到端健康检查证书剩余天数必须暴露为Prometheus指标和Nginx upstream状态联动告警。没有可观测性的自动化就是埋雷自动化。2.5 CI/CD集成不是“把certbot命令塞进去”而是密钥治理的延伸很多团队想把证书生成放进GitLab CI理由很朴素“开发提PRCI自动申请证书测试环境秒变HTTPS”。但立刻撞上密钥墙DNS API密钥不能明文写进.gitlab-ci.yml也不能存在CI runner本地磁盘会被其他项目共享。正确的做法是分三层隔离第一层DNS密钥由运维团队通过HashiCorp Vault或AWS Secrets Manager统一托管CI job运行时动态拉取第二层certbot命令必须用--work-dir和--logs-dir指定独立临时路径避免多个job并发写入冲突第三层生成的证书必须通过CI内置的artifacts机制上传再由独立的“证书分发job”推送到目标服务器严禁在build阶段直接ssh过去改文件。本指南后续章节会给出GitLab CI模板包含Vault认证、临时目录清理、artifact签名验证三重防护确保CI生成的证书和手工操作具备同等审计效力。3. 核心细节解析与实操要点从DNS配置到证书链校验的12个生死关3.1 DNS提供商选择不是“支持API”就够要看Rate Limit和TTL容忍度Let’s Encrypt的DNS-01验证要求你在域名的根域如example.com下创建一条特定名称的TXT记录内容是ACME服务器生成的token。这个过程看似简单但不同DNS服务商的实现差异极大。以国内常用服务商为例服务商API调用频率限制TXT记录TTL最小值是否支持批量操作实测验证耗时阿里云DNS200次/小时60秒是BatchSetRecord42秒腾讯云DNS500次/小时60秒否单条Update78秒Cloudflare1200次/天1秒是Bulk operations18秒华为云DNS100次/分钟1秒是25秒关键发现腾讯云虽然QPS高但单条更新慢certbot默认串行调用遇到多域名验证会严重拖慢阿里云TTL设为60秒但Let’s Encrypt要求TXT记录在验证前至少存在120秒防止缓存未刷新所以你必须提前手动把TTL调到120秒以上否则验证必败。而Cloudflare的1秒TTL和Bulk API让它成为首选——我们实测10个子域名并行验证总耗时不到30秒。因此本指南实操环节锁定Cloudflare作为演示平台但会同步给出阿里云和华为云的适配补丁脚本确保你不用为了换DNS服务商重写整套流程。3.2 certbot安装放弃系统包管理器用snap保证版本一致性Ubuntu/Debian用户习惯apt install certbotCentOS用户用yum install python3-certbot-nginx。但这是生产环境最大隐患之一系统包管理器安装的certbot版本滞后Ubuntu 22.04默认装的是certbot 1.21而Let’s Encrypt已于2023年停用旧ACMEv1协议certbot 1.21无法连接新ACMEv2端点报错urn:acme:error:unauthorized。更糟的是不同发行版打包策略不同有的带nginx插件有的不带有的默认启用auto-renew有的不启用——导致同一份文档在不同服务器上行为不一致。解决方案是统一用snap安装sudo snap install core; sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbotsnap保证你永远拿到官方最新稳定版目前是2.8.0且插件nginx、apache、dns-cloudflare全部按需安装互不干扰。注意--classic参数是必须的否则certbot无法访问/etc/letsencrypt目录。这条命令在所有主流Linux发行版上实测通过包括RHEL 8/9、AlmaLinux、Rocky Linux。我们曾用Ansible批量部署过200节点零兼容性问题。3.3 域名列表构建用YAML替代命令行参数实现配置即代码certbot命令行支持-d example.com -d www.example.com -d api.example.com但当域名超过5个或者需要区分生产/预发环境时命令行就变成维护噩梦。本指南强制采用YAML配置驱动# domains.yaml production: - domain: example.com sans: [www.example.com, api.example.com, admin.example.com] dns_provider: cloudflare email: opsexample.com staging: - domain: staging.example.com sans: [www.staging.example.com] dns_provider: aliyun email: devexample.com然后写一个Python脚本解析YAML动态生成certbot命令import yaml, subprocess with open(domains.yaml) as f: config yaml.safe_load(f) for domain_cfg in config[production]: cmd [ certbot, certonly, --dns-cloudflare, --dns-cloudflare-credentials, /root/.secrets/cloudflare.ini, --non-interactive, --agree-tos, --email, domain_cfg[email], -d, domain_cfg[domain] ] cmd.extend([f-d {san} for san in domain_cfg[sans]]) subprocess.run(cmd, checkTrue)好处显而易见配置集中管理、Git版本控制、Code Review可追溯、不同环境用不同YAML文件隔离。更重要的是当你要加一个新域名时只需改YAML不用动任何shell脚本或crontab彻底消灭“改配置忘改脚本”的低级错误。3.4 DNS凭证安全绝不存明文用Vault动态注入Cloudflare API密钥Global API Key一旦泄露等于交出整个DNS控制权。绝不能把它写进/root/.secrets/cloudflare.ini这种文件。正确姿势是用HashiCorp Vault的KV v2引擎存储# Vault端写入密钥 vault kv put secret/certbot/cloudflare \ tokenyour_actual_api_token_here \ emailopsexample.com然后在certbot命令中用Vault Agent注入vault agent -configvault-agent.hcl certbot certonly --dns-cloudflare \ --dns-cloudflare-credentials /tmp/cloudflare.ini \ --non-interactive --agree-tos \ -d example.com -d www.example.com其中vault-agent.hcl配置Vault Agent自动监听secret路径把密钥渲染成certbot能读的ini格式。这样即使CI runner被攻破攻击者也拿不到原始API密钥只能看到临时生成的、带TTL的凭证文件。本指南附带完整的Vault Agent配置模板和Ansible Role3分钟可部署一套轻量级密钥分发中心。3.5 证书链完整性为什么你的证书在Chrome里绿在Safari里黄Let’s Encrypt的证书链有两套R3根证书ISRG Root X1和交叉签名链DST Root CA X3。2021年9月后新签发的证书默认用R3链但老版本iOS14.5和macOS11.2不信任R3必须提供完整的中间证书才能兼容。certbot默认生成的fullchain.pem已包含中间证书但很多团队错误地只用cert.pem纯域名证书配给Nginx导致移动端握手失败。验证方法极其简单# 检查fullchain.pem是否包含至少2个证书 openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/example.com/fullchain.pem | \ openssl pkcs7 -print_certs -noout | grep subject | wc -l # 输出应为2或3域名证书1~2个中间证书如果输出是1说明你用错了文件。Nginx配置必须是ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # 注意是fullchain ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;本指南所有Nginx配置示例均强制标注fullchain.pem并在注意事项里用提示框强调此坑。3.6 Nginx reload原子性用systemd notify替代kill -HUPnginx -s reload看似安全但在高并发场景下有风险master进程发送SIGUSR2给workerworker启动新进程后旧worker不会立即退出而是等待当前请求处理完。如果此时有长连接如WebSocket、SSE旧worker可能持续存活几分钟导致新证书未生效。更危险的是如果reload过程中Nginx配置语法错误master会拒绝加载但旧worker仍在服务你以为reload成功了其实证书还是旧的。本指南采用systemd的Typenotify模式# /etc/systemd/system/nginx.service.d/override.conf [Service] Typenotify NotifyAccessall然后用systemctl reload nginx触发systemd会等待Nginx主动发送READY1信号才认为reload完成。配合以下脚本做双重校验#!/bin/bash # reload-nginx-safe.sh nginx -t || exit 1 systemctl reload nginx sleep 2 curl -I https://example.com 2/dev/null | grep HTTP/2 200 || exit 1 echo Nginx reload OK with new cert这个脚本会进入certbot的--post-hook确保只有端到端验证通过才认为本次证书更新真正落地。3.7 日志与监控定义5个不可妥协的核心指标没有监控的证书管理等于裸奔。本指南定义以下5个Prometheus指标全部通过Node Exporter的textfile collector实现ssl_cert_expires_seconds{domainexample.com}证书过期时间戳Unix秒用于计算剩余天数ssl_renewal_last_run_timestamp_seconds{domainexample.com}上次续期开始时间ssl_renewal_last_status{domainexample.com,statussuccess|failed}续期结果ssl_cert_chain_length{domainexample.com}证书链长度应为2或3ssl_nginx_reload_status{domainexample.com,statusok|failed}Nginx reload结果采集脚本每5分钟执行一次把结果写入/var/lib/node_exporter/textfile/ssl.prom。Grafana面板必须包含① 所有域名剩余天数TOP10排行榜② 近7天续期失败率趋势图③ 证书链长度异常告警非2/3值立即标红。我们线上环境用这套监控在证书过期前15天就触发企业微信预警比Let’s Encrypt官方邮件早5天。3.8 失败重试机制不是简单加--force-renewal而是分级熔断certbot renew --force-renewal是新手最爱但它是反模式强制续期会消耗Let’s Encrypt的rate limit每周50次/域名且掩盖真实问题。正确做法是分级重试一级失败DNS验证超时等待5分钟重试2次用dig检查TXT记录是否已生效二级失败Nginx reload失败暂停续期发告警人工介入检查Nginx配置三级失败证书链校验失败立即停止所有续期任务触发P0级告警因为这意味CA根证书变更或中间证书吊销本指南提供的renew-wrapper.sh脚本内置这三级逻辑用case语句匹配certbot stderr关键字自动执行对应动作。例如检测到DNS problem: NXDOMAIN就执行一级重试检测到nginx: [emerg]就跳转二级处理。所有重试动作都记录到结构化日志方便ELK分析失败根因。3.9 权限模型用POSIX ACL替代传统chmod实现最小权限chmod 600只能控制文件所有者但现代Linux支持ACLAccess Control List可以精确到用户组、甚至单个用户。我们的生产实践是# 创建专用证书组 groupadd ssl-cert usermod -a -G ssl-cert nginx usermod -a -G ssl-cert deploy # 设置ACL只允许ssl-cert组读取证书root可读写 setfacl -R -m g:ssl-cert:r-x /etc/letsencrypt/live/ setfacl -R -m g:ssl-cert:r-- /etc/letsencrypt/live/*/cert.pem setfacl -R -m g:ssl-cert:r-- /etc/letsencrypt/live/*/fullchain.pem setfacl -R -m g:ssl-cert:--- /etc/letsencrypt/live/*/privkey.pem # 私钥组不可读 setfacl -R -m u:root:rwx /etc/letsencrypt/live/这样Nginx进程属ssl-cert组能读取证书和链但读不到私钥deploy用户能部署证书但不能窃取私钥root保留完全控制权。ACL权限用getfacl命令可审计比chmod更透明。本指南所有权限操作均基于ACL附带acl-check.sh脚本一键验证。3.10 通配符证书的DNS验证为什么_acme-challenge.example.com必须是TXT不能是CNAMELet’s Encrypt明确要求DNS-01验证的记录必须是TXT类型且名称为_acme-challenge.domain。但很多团队为了方便管理把_acme-challenge.example.comCNAME到acme.example-cdn.com以为能复用CDN的DNS配置。这是致命错误Let’s Encrypt的验证服务器只会查询_acme-challenge.example.com的TXT记录如果返回CNAME它不会继续跟随查询直接判定验证失败。RFC 8555第8.3节明确规定“The client MUST set the value of the TXT record to the JWS key authorization.”——必须是TXT且值必须是JWS授权码。本指南实操环节会用dig _acme-challenge.example.com txt short命令实时验证确保返回的是双引号包裹的token字符串而不是CNAME记录。3.11 证书吊销监控主动轮询CRL/OCSP不等浏览器报错Let’s Encrypt证书极少被吊销但一旦发生如私钥泄露浏览器可能需要数小时才能通过OCSP Stapling获取吊销状态。被动等待不行必须主动监控。我们用openssl ocsp命令每小时轮询openssl ocsp -issuer /etc/letsencrypt/archive/example.com/chain1.pem \ -cert /etc/letsencrypt/live/example.com/cert.pem \ -url http://r3.o.lencr.org \ -header Host r3.o.lencr.org \ -resp_text 2/dev/null | grep response status | grep successful如果返回非successful立即触发告警。同时订阅Let’s Encrypt的RSS公告https://letsencrypt.org/docs/rate-limits/当有大规模吊销事件时自动触发全量证书重新签发。这个机制在2022年Let’s Encrypt因内部错误吊销300万张证书时帮我们提前2小时发现并完成恢复。3.12 备份与回滚用git管理/etc/letsencrypt/不是tar打包/etc/letsencrypt/目录结构复杂包含archive/所有历史版本、live/符号链接、renewal/配置等。传统tar czf backup.tgz /etc/letsencrypt备份恢复时要手动重建符号链接极易出错。本指南采用git管理cd /etc/letsencrypt git init git config user.name Certbot Backup git config user.email backupexample.com git add . git commit -m Initial backup $(date) # 后续每次certbot renew后自动commit这样每次续期都会生成一个git commit用git log --oneline -n 10就能看到最近10次证书变更用git checkout commit-hash秒级回滚到任意历史版本。我们甚至把git仓库推送到内网Gitea实现跨服务器证书状态同步。这个方案比任何备份脚本都可靠因为git本身就是为管理文本变更而生的。4. 实操过程与核心环节实现从零开始部署一套可审计的证书系统4.1 环境准备4台服务器的最小可行架构我们不假设你有K8s或Serverless用最朴素的4台Linux服务器搭建最小可行架构dns-serverCloudflare账号已添加example.com域名API Token已生成ca-serverUbuntu 22.04安装certbot、vault、nginx作为证书签发中心web-serverCentOS 8运行Nginx接收证书monitor-serverDebian 11部署PrometheusGrafanaAlertmanager所有服务器时间同步用chronySSH密钥登录禁用密码。第一步在ca-server上执行# 安装基础依赖 sudo apt update sudo apt install -y curl gnupg2 software-properties-common # 安装snap sudo snap install core; sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot # 验证安装 certbot --version # 应输出certbot 2.8.0注意不要用apt install certbot这是本指南第一条铁律。我们实测过用apt安装的certbot在申请通配符证书时会因ACMEv1协议废弃而静默失败错误日志里只有一行Exiting abnormally毫无线索。4.2 Cloudflare API密钥配置用Vault封装杜绝明文在ca-server上初始化Vault开发模式仅用于演示生产请用raft存储# 启动Vault dev server vault server -dev -dev-root-token-idroot -dev-listen-address0.0.0.0:8200 export VAULT_ADDRhttp://127.0.0.1:8200 export VAULT_TOKENroot # 启用KV v2引擎 vault secrets enable -pathsecret kv-v2 # 写入Cloudflare密钥 vault kv put secret/certbot/cloudflare \ tokensqk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ emailopsexample.com然后创建Vault Agent配置/root/vault-agent.hclvault { address http://127.0.0.1:8200 token root } template { source /root/cloudflare.ini.tpl destination /root/.secrets/cloudflare.ini command chmod 600 /root/.secrets/cloudflare.ini }其中cloudflare.ini.tpl内容为# Cloudflare API credentials dns_cloudflare_api_token {{ with secret secret/data/certbot/cloudflare }}{{ .Data.data.token }}{{ end }}启动Vault Agentvault agent -config/root/vault-agent.hcl 现在/root/.secrets/cloudflare.ini会自动渲染且每次Vault密钥更新Agent会自动重写文件。这是密钥安全的第一道防线。4.3 申请首张通配符证书命令拆解与参数深意执行申请命令certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \ --dns-cloudflare-propagation-seconds 30 \ --non-interactive \ --agree-tos \ --email opsexample.com \ -d example.com \ -d *.example.com \ --server https://acme-v02.api.letsencrypt.org/directory逐参数解析--dns-cloudflare指定DNS插件certbot会自动加载certbot-dns-cloudflare模块--dns-cloudflare-credentials指向Vault注入的密钥文件--dns-cloudflare-propagation-seconds 30等待DNS记录生效30秒Cloudflare通常10秒内生效设30秒留足余量--non-interactive关闭交互式提问适合自动化--agree-tos自动同意Let’s Encrypt服务条款-d example.com -d *.example.com必须同时指定根域名和通配符否则*.example.com不覆盖example.com--server强制使用ACMEv2端点避免旧协议兼容问题执行后certbot会调用Cloudflare API创建_acme-challenge.example.comTXT记录等待30秒让DNS生效调用Let’s Encrypt ACME服务器发起验证验证通过后签发证书存入/etc/letsencrypt/live/example.com/验证证书内容openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -text -noout | \ grep -E (DNS|Not After) # 应看到 # DNS:example.com, DNS:*.example.com # Not After : Nov 15 12:34:56 2024 GMT4.4 Nginx配置与原子化部署从证书到服务的最后100米在web-server上创建Nginx配置/etc/nginx/sites-available/example.comupstream backend { server 10.0.1.10:8000; # 你的应用服务 } server { listen 443 ssl http2; server_name example.com www.example.com api.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # 强制HSTS add_header Strict-Transport-Security max-age31536000; includeSubDomains; preload always; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 80; server_name example.com www.example.com api.example.com; return 301 https://$host$request_uri; }关键点ssl_certificate必须是fullchain.pemserver_name必须包含所有SANs域名。然后创建原子化部署脚本/root/deploy-cert.sh#!/bin/bash DOMAINexample.com CERT_SRC/etc/letsencrypt/live/$DOMAIN/ CERT_DST/etc/nginx/ssl/$DOMAIN/ # 创建目标目录 mkdir -p $CERT_DST # 复制证书用cp -a保持权限和时间戳 cp -a $CERT_SRC/fullchain.pem $CERT_DST/fullchain.pem cp -a $CERT_SRC/privkey.pem $CERT_DST/privkey.pem # 设置权限root读写ssl-cert组只读 chown root:ssl-cert $CERT_DST/fullchain.pem $CERT_DST/privkey.pem chmod 640 $CERT_DST/fullchain.pem $CERT_DST/privkey.pem # 重载Nginx if nginx -t; then systemctl reload nginx echo ✅ Cert deployed and Nginx reloaded else echo ❌ Nginx config test failed exit 1 fi赋予执行权限并运行chmod x /root/deploy-cert.sh /root/deploy-cert.sh现在访问https://example.com应该看到绿色锁图标且curl -I https://example.com返回HTTP/2 200。4.5 自动化续期流水线crontab hook脚本的黄金组合在ca-server上编辑crontab# 每天凌晨2:15执行续期 15 2 * * * /usr/bin/certbot renew --non-interactive --quiet --renew-hook /root/renew-hook.sh /var/log/letsencrypt/renewal.log 21其中/root/renew-hook.sh是核心#!/bin/bash # 此脚本在certbot renew成功后执行 DOMAIN$(basename $(dirname $RENEWED_LINEAGE)) echo Renewing $DOMAIN at $(date) # 1. 复制证书到web-server rsync -avz --delete \ /etc/letsencrypt/live/$DOMAIN/ \ userweb-server:/etc/letsencrypt/live/$DOMAIN/ # 2. 在web-server上执行部署脚本 ssh userweb-server /root/deploy-cert.sh # 3. 发送企业微信告警成功 curl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEY \ -H Content-Type: application/json \ -d {msgtype: text, text: {content: ✅ SSL证书续期成功$DOMAIN剩余有效期$(expr $(date -d \$(openssl x509 -in /etc/letsencrypt/live/$DOMAIN/cert.pem -enddate -noout | cut -d -f2)\ %s) - $(date %s) | awk \{print int(\$1/86400)}\)天}} # 4. 记录到Prometheus textfile echo ssl_renewal_last_run_timestamp_seconds{domain\$DOMAIN\} $(date %s) /var/lib/node_exporter/textfile/ssl_$DOMAIN.prom echo ssl_renewal_last_status{domain\$DOMAIN\,status\success\} 1 /var/lib/node_exporter/textfile/ssl_$DOMAIN.prom这个hook脚本完成了证书分发、服务重载、多通道告警、监控上报四件事且全部原子化——任何一步失败整个流程终止不会留下半截状态。4.6 监控大盘搭建Grafana 5分钟速配指南在monitor-server上安装Prometheuswget https://github.com/prometheus/prometheus/releases/download/v2.47.2/prometheus-2.47.2.linux-amd64.tar.gz