Ccyriak.xyz

acme.sh 与 HTTP-01:把证书续期交给 cron

目录

免费证书的坑不在签发,在九十天之后。手动签一次谁都会,真正的区别在于:到期那天你记不记得这件事。这篇是我在一台 1GB 内存的小机器上做的完整配置,从 80 端口的准备到续期后自动热加载。

先给 80 端口留一条通道

HTTP-01 验证要求 http://域名/.well-known/acme-challenge/<token> 能返回指定内容。最省事的做法是让 Nginx 在 80 上只开放这条路径,其余请求一律跳转到 HTTPS。

nginx
server {
    listen      80;
    listen      [::]:80;
    server_name example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/acme;
        default_type text/plain;
        try_files $uri =404;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

注意 ^~ 不能省:它让这个前缀匹配直接生效,不再去撞后面的正则规则。目录要能被 Nginx 的 worker 用户读到,权限 755 就够,别给 777。

签发

acme.sh 是纯 shell 脚本,装完就一个目录,没有运行时依赖——这对小内存机器很友好。

bash
curl -s https://get.acme.sh | sh
~/.acme.sh/acme.sh --version

# 指定默认 CA(不指定会走 ZeroSSL,也能用,但两者流程略有差异)
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

# 签发,用 webroot 模式
~/.acme.sh/acme.sh --issue -d example.com -w /var/www/acme --keylength ec-256

--keylength ec-256 生成的是 ECDSA 证书。它比 RSA 2048 小很多,握手也快,现代浏览器全部支持。如果还要照顾极老的客户端,可以再加一套 --keylength 2048 的双证书,但绝大多数情况下没必要。

安装脚本会自动往 crontab 里写一条续期任务。如果你看到「参数被重复拼接」之类的报错,通常是安装命令里多带了参数——直接 sh install.sh 不带参数最稳。

装到 Nginx 能读的位置

acme.sh 把证书存在自己的目录里,但 Nginx 不该去读那个目录——路径带用户名、权限也偏宽松。用 --install-cert 拷到标准位置,并挂上热加载命令:

bash
~/.acme.sh/acme.sh --install-cert -d example.com --ecc \
  --key-file       /etc/ssl/sites/example.com/privkey.pem \
  --fullchain-file /etc/ssl/sites/example.com/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

这个命令会被记录进续期配置:三个月后自动续期完成,acme.sh 会自己执行一次 reloadcmd。很多人漏掉它,结果证书文件更新了、进程还在用内存里的旧证书,浏览器继续报警——这是最容易误判成「续期失败」的一种情况。

确认续期真的会跑

别相信「应该是自动的」。两条命令就能验证:

bash
# 1. 续期任务是否已存在
crontab -l | grep acme

# 2. 强制走一遍完整续期流程(消耗一次签发额度,但能验证全链路)
~/.acme.sh/acme.sh --renew -d example.com --force

如果第 2 步成功、并且 systemctl status nginx 显示刚刚 reload 过,那这套东西就算配好了。之后可以隔三个月看一眼 openssl x509 -enddate 的结果,确认它在往前滚。

几个容易踩的点

  • 80 端口被别的东西占着。--standalone 模式要求 80 空闲,如果已经有 Web 服务在跑,老老实实用 -w webroot 模式。
  • 域名解析还没生效就签。ACME 服务器是从外网回访的,本地 curl 通不代表它能通。签发前先用公共 DNS 查一次。
  • 只签了裸域,忘了 www。一个证书要覆盖多个名字就重复 -d,别等上线了才发现访问 www 报错。
  • 用 IP 直接访问。证书绑的是域名,客户端对裸 IP 不会发 SNI,握手拿不到正确证书,这条路走不通。

顺手把 OCSP stapling 关掉

如果配置里还留着这两行,现在可以删了:

nginx
ssl_stapling        on;
ssl_stapling_verify on;

Let's Encrypt 已经停止提供 OCSP 响应,新签发的证书里根本没有 OCSP 地址。保留这两行不会报错,只会让每次 nginx -t 多一条「无法获取响应」的警告。删掉更干净。

最后,别忘了系统时间。证书有效期是拿本机时钟去比的,时钟慢了几分钟就可能被判定为「尚未生效」。这一点放在另一篇里说。