给一个静态站点配上 HTTPS,本质上就三步:签发证书、配置 Nginx、设置自动续期。 这篇是我反复用过几次后整理的最小流程备忘。

1. 先用 HTTP 起站,留好验证路径

证书签发(HTTP-01 验证)需要让签发机构能通过 http://你的域名/.well-known/ acme-challenge/ 访问到一个临时文件。所以 Nginx 里要先保证 80 端口可达,并把 这个路径指向 webroot:

server {
    listen 80;
    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example;
    }

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

注意:80 → 443 的跳转要把验证路径排除在外,否则签发时会被 301 带走导致验证 失败。

2. 用 acme.sh 签发证书

我用的是 acme.sh,纯脚本、不依赖 root 守护进程:

# 签发(ECC 证书,webroot 方式)
acme.sh --issue -d example.com -w /var/www/example \
  --keylength ec-256 --server zerossl

# 安装到固定路径,并配置签发后自动 reload
acme.sh --install-cert -d example.com --ecc \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd      "systemctl reload nginx"

--install-cert 这一步很关键:它会把证书复制到稳定路径,并记住 reload 命令, 之后续期时会自动执行。

3. 配置 443

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    root /var/www/example;
    index index.html;
    location / { try_files $uri $uri/ =404; }
}

改完 nginx -t 检查语法,再 systemctl reload nginx。

4. 自动续期

acme.sh 安装时会自动加一条 cron(或 systemd timer),每天检查一次,证书到期前 30 天左右自动续期并执行上面配置的 reload 命令。一般不用手动管,只要确认定时任 务在跑:

crontab -l | grep acme

踩过的坑:DNSSEC 导致 Let’s Encrypt 签发失败

这是最花时间的一个坑。我有个域名挂在开启了 DNSSEC 的后缀下,但子域没有配置对应 的 DNSKEY/DS 记录。结果 Let’s Encrypt 在做 DNS 校验时会因为 DNSSEC 链不完整 而查询超时(报错类似 DNSKEY Missing)。

解决办法是换用不强制做该校验的签发机构,比如 ZeroSSL:上面命令里的 --server zerossl 就是为此。同一套 acme.sh 流程,换个 CA 立刻就签出来了。

小结:遇到莫名其妙的签发超时,除了看 80 端口和防火墙,也记得排查一下域名的 DNSSEC 配置。