ARTICLE DETAIL

资讯详情

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

全站HTTPS自动化:从证书申请到自动续期一次搞定

全站HTTPS自动化:从证书申请到自动续期一次搞定 这个标题我看到的第一反应也是“又一个标题党”但等我自己把整套流程跑通之后确实想说一句真香。做网站这么多年最烦的就是 HTTPS 证书的申请、部署、续期这一套尤其当服务器上挂着好几个域名、十几个子站点的时候手动操作一次少说半个小时还得定个闹钟每三个月去续一次稍微忘了就等着浏览器满屏红字警告。所以后来我写了一个自动化脚本把证书申请、站点配置、自动续期、HTTP 强制跳转全部串成一条流水线跑完之后所有站点默认使用 HTTPS 访问而且全程零费用。这篇记录专门聊聊这套脚本的设计思路和落地方案。如果你手上有自己的服务器不管是 Nginx、Apache 还是 Caddy甚至只是一个小型个人站这篇文章都能帮你把 HTTPS 这件事彻底“焊死”在系统里一劳永逸地跑下去。1. 为什么我坚持要给所有站点上 HTTPS早期很多个人站长觉得 HTTPS 只是“大厂专属”小站用 HTTP 完全够用这种想法放在现在其实挺危险的。HTTP 协议最大的问题是流量以明文方式传输用户在浏览器里输入的任何内容比如登录密码、手机号、支付信息中途只要经过一个不安全的 Wi-Fi 或者被运营商做链路劫持数据就等于裸奔。我之前调试一个后台系统用 HTTP 访问测试环境结果在路由器上抓包账号密码明文直接躺在数据包里那一刻真的冷汗都下来了。另外一个现实压力是浏览器的强制策略。你现在用 Chrome 打开一个 HTTP 网站地址栏直接标“不安全”用户点进来第一反应就是怀疑站点是不是钓鱼网站。做电商、做博客、做企业官网信任感一旦丢了转化率跌得很难看。还有一点是 HTTP/2 和未来的 HTTP/3它们都明确要求基于 HTTPS 才能启用。我在给一个 WordPress 站点切换协议之后页面加载速度实测提升了不少因为 HTTP/2 的多路复用机制减少了大量 TCP 连接开销。而且搜索引擎对 HTTPS 站点有明确的权重倾斜同样的内容HTTPS 页面的收录和排名通常优于 HTTP 页面。所以结论很简单不管你是个人博客还是企业系统上 HTTPS 已经不是“要不要做”的问题而是“什么时候做、怎么做才省心”的问题。免费证书的出现把成本直接打到零剩下的难点只有自动化维护。2. 脚本整体设计与技术选型解读敲定“全站升级 HTTPS”这个目标之后我首先面对三件事证书从哪来、怎么部署、怎么续期。这三件事如果不自动化人肉维护迟早出问题。2.1 为什么我选 acme.sh 而不是 Certbot目前主流的免费证书申请工具无非两个一个是 EFF 出品的 Certbot另一个是纯 shell 实现的 acme.sh。两个都能用但我最后选了 acme.sh原因很实在acme.sh 本身就是纯 Shell 脚本不依赖 Python 环境在服务器上部署非常轻量而且它对各大 DNS 服务商的 API 支持特别全阿里云、腾讯云、Cloudflare 都在列表里。它默认就是“申请后自动安装续期任务”的设计哲学装上之后几乎不用额外配置。所有中间文件都集中在~/.acme.sh/目录下面出了问题排查起来非常直观。另外我还看中它的一个能力支持 DNS API 模式验证域名所有权而不只是 80 端口验证。这就意味着即使我一个 80 端口都不开、站点还挂在 CDN 后面也能成功签发证书这一点后面实操环节会详细讲。2.2 脚本的三段式结构设计这套脚本核心分成三步证书签发阶段根据你传入的域名列表批量调用 ACME 协议完成证书申请。配置部署阶段将证书安装到对应 Web 服务器的站点配置中并生成 HTTP 强跳 HTTPS 的规则。续期守护阶段写入 crontab 定时任务每 60 天自动检查一次证书有效期到期前自动续签并重载服务。之所以把这三步拆开而不是一个大函数是为了方便出错时单独重跑某一步。比如某个新站点上线时我只需要单独运行第一步和第二步完全不影响其他站点。2.3 用 for 循环批量处理多个域名标题里说“一个脚本”其实最核心的驱动就是 shell 脚本里最简单的 for 循环。很多人一想到批量处理就觉得要上复杂的配置管理工具其实完全没必要。我定义了一个数组DOMAINS( example.com www.example.com blog.example.com api.example.com )然后循环遍历这个数组每个域名跑一遍申请和安装流程。这个思路非常直接但效果极好。新加一个站点只需要在数组里添一行然后脚本一跑等上十几秒证书就位、服务重载、HTTPS 强制跳转全部完成。有人可能会问为什么不用通配符证书一张搞定所有子域名通配符证书确实可以做到*.example.com全部覆盖但它的签发通常要求 DNS API 验证而且证书文件本身只有一张如果某些子域名挂在不同的服务器上管理起来反而不灵活。我这里既能用单域名证书也能按需升级为通配符证书脚本兼容两种模式后面会说要改哪个参数。3. 脚本核心细节解析与实操要点这一节直接进正题把脚本里的关键函数一个个拆开讲。为了让你能照着复现我会把核心代码贴出来并解释每一段的用途。3.1 证书签发调用 acme.sh 申请证书证书签发是整个流程的地基。我封装了一个函数issue_cert() { local domain$1 if [ $DNS_API no ]; then $ACME_SH --issue -d $domain --webroot /var/www/html --force else $ACME_SH --issue --dns dns_ali -d $domain --force fi }这里有两种验证模式webroot 模式acme.sh 会在你的网站根目录下放一个临时验证文件CA 通过 HTTP 请求这个文件来确认你对域名的控制权。这种方式要求 80 端口能正常访问好处是配置简单。DNS API 模式通过调用域名 DNS 服务商的 API自动添加一条 TXT 记录完成验证。这种方式不依赖 80 端口适用于站点在 CDN 后面或者完全不想暴露 HTTP 服务的情况。我在生产环境强烈推荐 DNS API 模式。你只需要在环境变量里配置好 DNS 服务商的密钥例如阿里云export Ali_Key你的AccessKey ID export Ali_Secret你的AccessKey Secret然后 acme.sh 自己会定时刷新这些密钥完全不需要手动干预。这里有个容易被忽略的坑--force参数不是必须的但加上它之后脚本会忽略本地缓存强制重新签发。好处是如果 CA 侧数据异常可以通过强制签发绕过坏处是会增加调用频率。建议只在首次部署或排障时使用平时让它自己判断是否过期就好。3.2 安装证书到 Nginx 并重载配置证书签下来之后必须安装到 Web 服务器对应位置。Nginx 的配置方式是在 server 块里指定两个路径listen 443 ssl http2; ssl_certificate /etc/nginx/ssl/example.com/fullchain.cer; ssl_certificate_key /etc/nginx/ssl/example.com/example.com.key;脚本里边安装证书的动作长这样install_cert() { local domain$1 local cert_dir/etc/nginx/ssl/$domain mkdir -p $cert_dir $ACME_SH --install-cert -d $domain \ --key-file $cert_dir/$domain.key \ --fullchain-file $cert_dir/fullchain.cer \ --reloadcmd systemctl reload nginx }这段代码干了几件事为每个域名创建独立的证书目录避免证书文件互相覆盖。把密钥文件和证书链文件复制到目标路径。每次续期成功后自动执行systemctl reload nginx让新证书立即生效。我强烈不建议在 Nginx 里直接引用~/.acme.sh/目录下的文件。因为 acme.sh 的存储目录结构将来可能变化而且目录权限默认只有 root 可读一旦 Nginx worker 进程权限不够就会导致启动失败。复制到/etc/nginx/ssl/之后证书文件可以只读、永久存放和工具本身解耦。3.3 HTTP 自动跳转 HTTPS 的写法如果只配 HTTPS 而不管 HTTP用户访问老链接时还是能落到 80 端口体验就断了一半。所以脚本里我会生成一个默认的 HTTP 跳转配置server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; }$host变量会保留用户访问时用的域名这样不管是example.com还是www.example.com都能统一跳到 HTTPS 版本且不丢路径参数。如果你的站点用了多级目录$request_uri也会完整保留原始路径。有人可能会想为什么要用 301而不是 302因为 301 是永久重定向浏览器会缓存跳转结果后续访问直接跳过 HTTP 请求、省一次往返。而 302 每次都要先访问 HTTP 再跳到 HTTPS白白增加一次响应时间。唯一需要注意的是301 一旦被浏览器缓存想改回 HTTP 就很麻烦所以务必确认 HTTPS 配置完全正常之后再启用。3.4 定时续期任务crontab 里的那行命令免费证书最大的心理负担就是“90 天过期”但其实只要写好自动续期它就约等于永久有效。acme.sh 安装时会自动写一条 crontab但我习惯自己再补一条保障任务0 3 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh /dev/null每天凌晨 3 点跑一次检查。acme.sh 会判断证书剩余天数只有少于 60 天才真正执行续签。这个设计非常稳因为你不用担心每天跑会不会触发频率限制——它在内部已经做了控制。我可以很负责任地说这套机制我在生产环境跑了两年多一次“证书过期导致服务不可用”的事故都没有发生过。只要服务器不宕机、crontab 不被清掉证书永远不会过期。4. 完整实操从一台裸服务器到全站 HTTPS这一节我按照实际部署的完整路径走一遍你跟着操作即可。4.1 安装 acme.sh 并初始化环境首先登录服务器用 root 或具有 sudo 权限的用户执行curl https://get.acme.sh | sh -s emailyouexample.com这个命令会自动完成几件事把仓库克隆到~/.acme.sh/创建 alias方便后续直接用acme.sh命令自动写入 crontab 定时任务安装完成后执行acme.sh --version看到版本号输出就说明装好了。注意如果你的服务器在中国大陆GitHub 有时候连接不稳定可以改用git clone方式从镜像站拉取源码再手动执行安装脚本。这一步不用太纠结工具只要能装上就行。4.2 准备域名列表和 Web 服务器配置假设我现在要升级的站点有example.com、www.example.com、blog.example.com。站点根目录都在/var/www/下Nginx 的站点配置放在/etc/nginx/conf.d/。我先手动为每个域名建好基础配置但暂时只写 HTTP 段落不做 HTTPS 相关配置因为证书还没签下来写了也会报错。等脚本跑完它会自动往对应 server 块里追加 SSL 参数。这里有一个小技巧如果你嫌手动改 Nginx 配置麻烦可以在你的站点配置模板中预留一段变量占位符脚本用 sed 命令自动替换。我用的容器镜像就是这种方式每次新起服务脚本自动生成完整配置全程无人工介入。4.3 一键执行升级脚本将下面的脚本保存为/usr/local/bin/ssl-upgrade.sh#!/bin/bash # 全站 HTTPS 自动升级脚本 # 支持 CentOS / Ubuntu / DebianNginx 或 Apache ACME_SH/root/.acme.sh/acme.sh NGINX_CONF/etc/nginx/conf.d DOMAINS( example.com www.example.com blog.example.com ) issue_cert() { local domain$1 $ACME_SH --issue --dns dns_ali -d $domain --force } install_cert() { local domain$1 local cert_dir/etc/nginx/ssl/$domain mkdir -p $cert_dir $ACME_SH --install-cert -d $domain \ --key-file $cert_dir/$domain.key \ --fullchain-file $cert_dir/fullchain.cer \ --reloadcmd systemctl reload nginx } gen_http_redirect() { local domain$1 local conf_file$NGINX_CONF/redirect-$domain.conf cat $conf_file EOF server { listen 80; server_name $domain; return 301 https://\$host\$request_uri; } EOF } for domain in ${DOMAINS[]}; do echo 正在处理 $domain ... issue_cert $domain if [ $? -eq 0 ]; then install_cert $domain gen_http_redirect $domain echo $domain 升级完成 else echo $domain 证书签发失败跳过安装 fi done systemctl reload nginx echo 所有站点 HTTPS 升级流程执行完毕然后执行chmod x /usr/local/bin/ssl-upgrade.sh /usr/local/bin/ssl-upgrade.sh等待过程中你会看到每个域名的 ACME 验证日志。DNS API 模式一般需要等待 DNS 记录生效通常在 10 秒到 1 分钟之间。全部跑完之后你可以用curl -I https://example.com验证响应头里是否出现HTTP/2 200再用浏览器打开站点确认地址栏的小锁标志。4.4 验证 HTTP 跳转是否生效有时候脚本跑完HTTPS 能打开但 HTTP 不自动跳转。这时候先检查是不是有旧的 server 配置覆盖了return 301指令。Nginx 配置里 server_name 相同的块存在多个时会优先匹配最前面的或者默认 server。我踩过这个坑之后养成了一个习惯清空conf.d下的旧配置重新生成一套确保没有冗余冲突。另外还要注意防火墙。云服务商的安全组如果没放行 443 端口HTTPS 无论如何都访问不通。用telnet example.com 443或nc -vz example.com 443测一下如果端口不通去安全组里添加入站规则协议 TCP端口 443来源 0.0.0.0/0。4.5 验证自动续期是否已经挂上执行crontab -l | grep acme如果你看到类似下面这行输出说明续期任务已经存在18 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh /dev/null如果没有手动执行acme.sh --install-cronjob补上。这一步非常关键很多人的证书突然过期就是因为当初手动安装 acme.sh 时没有自动写 crontab。5. 踩坑记录与排查速查纸上得来终觉浅这套脚本我前后在十几台服务器上部署过积累了不少实战问题。这一节把最高频的坑和排查思路整理出来。5.1 证书签发失败的几种常见原因DNS 解析没生效刚买的域名DNS 记录还在缓存中等半小时再试。DNS API 密钥权限不足阿里云密钥只给了只读权限导致无法添加 TXT 记录给它加上AliyunDNSFullAccess权限。网络原因连不上 CA 服务器境外 CA 的 API 在某些网络环境下可能超时设置代理或切换 DNS 验证模式但注意涉及网络代理时务必考虑合规和稳定性不建议轻易尝试。域名本身有特殊字符或 IDN中文域名需要先转成 punycode 编码。我用一个死循环重试机制处理了偶尔的网络抖动问题for retry in {1..5}; do $ACME_SH --issue --dns dns_ali -d $domain break sleep 30 done5.2 证书文件明明存在Nginx 却启动失败这通常是因为证书文件权限不对或 SELinux 上下文限制。Nginx 读取证书文件需要 nginx 用户有读权限所以复制证书后建议执行chmod 644 /etc/nginx/ssl/*/fullchain.cer chmod 600 /etc/nginx/ssl/*/*.key如果用的是 CentOS还要检查 SELinuxrestorecon -Rv /etc/nginx/ssl否则 Nginx 会因权限被拒绝而无法读取证书。这类问题不会直接显示“拒绝访问”而是报“cannot load certificate”之类的错误。5.3 页面能打开但没有小锁标志这种称为“混合内容”问题。原因是页面里某些资源图片、CSS、JS仍然通过 HTTP 加载浏览器会认为页面不安全不显示完整 HTTPS 标识。排查方法打开浏览器开发者工具Console 面板里会列出所有被阻止的混合内容地址。修复思路有两个数据库中批量替换资源 URL把http://改成https://。使用 CSP 头强制升级Content-Security-Policy: upgrade-insecure-requests让浏览器自动把页面中的 HTTP 子资源请求升级为 HTTPS。全站迁到 HTTPS 后一定别忘了这一步否则用户看到页面能打开但地址栏里那个“不安全”提示还是阴魂不散。5.4 续期任务执行了但证书文件没更新这大概率是install-cert指定的--reloadcmd在续期时执行失败了。比如 nginx 二进制路径不对、systemd 服务名不对或者服务器上同时有多个 nginx 实例。建议手动跑一次/root/.acme.sh/acme.sh --cron --home /root/.acme.sh --debug加--debug参数会输出完整日志直接定位是哪一步失败。5.5 常见问题速查表症状可能原因处理方式证书签发后 80 端口无法访问防火墙未放行检查安全组和 iptables网站加载正常但不显示锁混合内容控制台检查并替换 HTTP 资源Nginx 启动报证书路径错误证书路径配置不对检查ssl_certificate路径是否存在续期后证书没变reload 命令失败手动执行--reloadcmd并加--debugDNS 验证超时域名 DNS 服务商 API 配置错误确认密钥和授权范围HTTP 不跳转 HTTPS旧配置覆盖了 redirect清理旧 server 块后重新生成移动端访问 HTTPS 慢TLS 握手开销大开启 OCSP Stapling 和 HTTP/2OCSP Stapling 这个点值得单独说。它能让浏览器跳过向 CA 查询证书有效性的网络请求直接在 TLS 握手阶段由服务器附带查询结果。Nginx 配置里加上ssl_stapling on; ssl_stapling_verify on;配合resolver 8.8.8.8;使用HTTPS 首次握手的响应速度能明显提升。我在一个国外节点上实测开启后首字节时间缩短了大概 30ms别小看这几十毫秒它能直接影响用户感知。5.6 关于迁到 HTTPS 之后的一点点心得体会全部切换完成后最重要的就是观察一到两周。重点看三块搜索引擎后台的抓取异常、第三方统计里的访问量变化、以及浏览器控制台里有没有漏网的资源报错。我当时的经验是搜索流量在前几天会有小幅波动属于正常现象等搜索引擎重新抓取完 HTTPS 页面之后就会恢复甚至比之前更好。要说有没有什么后悔的点唯一后悔的是没有更早做。之前一直觉得免费证书麻烦、自动化脚本复杂真正把脚本跑通之后才发现整个过程比想象中简单太多。而且这件事的长期收益非常划算——一次部署永久省心。证书自动续期、HTTP 自动跳转、多域名批量处理全都交给脚本之后新上线任何站点往域名列表里加一行就行。这套脚本我还做了个扩展版支持多台服务器集中管理证书也就是把所有证书申请都放在一台“跳板机”上然后通过 SSH 把证书文件分发到各业务服务器。这样证书数量多的时候每台服务器不需要装 acme.sh。不过这是后话了如果你的域名数量在十个以内单机脚本完全足够。最后再给一个建议脚本跑完别急着删留着。因为你以后肯定还会加新站点、换服务器、迁机房有了这套流程整套 HTTPS 配置的部署时间会从原来的小时级压缩到秒级。这个性价比值了。
返回列表