容器与平台

Docker 中用 Certbot 为 Nginx 申请 Let's Encrypt 证书

作者: · 最后核实

Docker Compose 里的 Nginx 和官方 certbot/certbot 容器共享验证目录和证书卷:Certbot 用 webroot 方式为 example.com 申请 Let's Encrypt 证书,Nginx 直接读取证书,宿主机定时器负责续期和重载 Nginx。

适用于任意 Linux 宿主机上的 Docker Engine + Compose 插件(docker compose),Certbot 4.1 及以上镜像。

前提条件

Nginx 在宿主机上发布 80 和 443 端口,Certbot 只在需要时运行。

  • 装有 Docker Engine 和 Compose 插件的 Linux 主机,有 sudo 权限。
  • example.com、www.example.com 的 A/AAAA 记录指向主机,例如 203.0.113.10;服务器在中国大陆的需已备案。
  • 防火墙和安全组放行 TCP 80、443,宿主机上没有其他程序占用这两个端口。
  • 一个项目目录,例如 /opt/web。

第一步:创建 Compose 文件和目录

Nginx 和 Certbot 两个容器通过宿主机上的两个目录交换数据:certbot/www 放验证文件,certbot/conf 放证书。

sudo mkdir -p /opt/web/nginx/conf.d /opt/web/certbot/www /opt/web/certbot/conf
cd /opt/web
# /opt/web/compose.yaml
services:
  nginx:
    image: nginx:stable
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certbot/www:/var/www/certbot:ro
      - ./certbot/conf:/etc/letsencrypt:ro
    restart: unless-stopped

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    profiles: ["tools"]

tools profile 让 certbot 服务不随 docker compose up 启动,需要时手动运行。生产环境建议固定镜像标签,有计划地升级。

第二步:通过 HTTP 提供验证目录

Nginx 先只开 80 端口,把 /.well-known/acme-challenge/ 指向共享的验证目录:

# /opt/web/nginx/conf.d/example.com.conf
server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
cd /opt/web && sudo docker compose up -d nginx

第三步:申请证书

用一次性运行的 certbot 容器以 webroot 方式申请证书:

cd /opt/web
sudo docker compose run --rm certbot certonly --webroot -w /var/www/certbot \
  --non-interactive --agree-tos \
  -d example.com -d www.example.com

证书保存在宿主机的 ./certbot/conf/live/example.com/。Let's Encrypt 从 2025 年 6 月 4 日起不再发到期提醒邮件,不填邮箱也可以。

第四步:添加 HTTPS 配置

证书申请成功后,才能在同一个 Nginx 配置文件末尾追加 443 配置:

server {
    listen 443 ssl;
    http2 on;    # nginx 1.25.1 及以上,当前 nginx:stable 镜像满足
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        root /usr/share/nginx/html;
    }
}
cd /opt/web && sudo docker compose exec nginx nginx -t && sudo docker compose exec nginx nginx -s reload

要反代同一 Compose 网络里的其他容器,把 location / 换成 proxy_pass http://app:3000;。不要加 ssl_stapling,Let's Encrypt 证书没有 OCSP 地址。

验证

配置正确时,证书颁发者是 Let's Encrypt,HTTP 请求跳转到 HTTPS。用 SSL 证书检查 从外部检查,或在终端运行:

curl ssl.sh/example.com

在宿主机上:

cd /opt/web && sudo docker compose run --rm certbot certificates
curl -sI http://example.com | head -n 3

自动续期

Certbot 容器不常驻,续期交给宿主机的 systemd 定时器。certbot renew 会查询 ARI 窗口(Certbot 4.1+),到了续期时间才续期;没有续期时重载 Nginx 也没有副作用。

# /etc/systemd/system/certbot-docker.service
[Unit]
Description=Renew Let's Encrypt certificates (Docker)

[Service]
Type=oneshot
WorkingDirectory=/opt/web
ExecStart=/usr/bin/docker compose run --rm certbot renew --quiet
ExecStartPost=/usr/bin/docker compose exec -T nginx nginx -s reload
# /etc/systemd/system/certbot-docker.timer
[Unit]
Description=Twice-daily certificate renewal

[Timer]
OnCalendar=*-*-* 03,15:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload && sudo systemctl enable --now certbot-docker.timer
cd /opt/web && sudo docker compose run --rm certbot renew --dry-run

2027 年 2 月 10 日 Let's Encrypt 默认证书改为 64 天后照常工作。Let's Encrypt 不再发提醒邮件,建议把域名加到 ssl.sh 监控(开发中)。

常见问题排查

  • Nginx 启动报 cannot load certificate ... No such file or directory:证书还没申请就加了 443 配置,或只挂载了 live/,要挂载整个 ./certbot/conf。
  • Timeout during connect 或验证返回 404:80 端口没发布、两个容器的验证目录路径不一致、解析不对或未备案被拦截,见 certbot 连接超时。
  • too many certificates already issued:反复测试触发限制,调试时加 --test-cert,见 Let's Encrypt 频率限制。
  • 续期后线上还是旧证书:重载步骤没执行,查看 systemctl status certbot-docker.service。
  • App 或小程序报证书链不完整:Nginx 指向了 cert.pem,见 缺少中间证书。

常见问题

为什么不把 Certbot 装进 Nginx 容器?

分开以后 Nginx 和 Certbot 镜像可以各自升级,Web 服务器镜像也保持精简。想少一个组件,可以用在代理内部完成 ACME 的 Traefik 或 Caddy。

服务器 80 端口不通,能在 Docker 里用 DNS 验证吗?

可以。Certbot 官方有 certbot/dns-cloudflare 等 DNS 插件镜像;域名在阿里云 DNS 或 DNSPod 的,更简单的做法是在宿主机上用 acme.sh 的 DNS API 签发,再把证书目录挂进 Nginx 容器。

国内拉取 Docker Hub 镜像失败怎么办?

可以在 /etc/docker/daemon.json 里配置云厂商提供的镜像加速地址(具体地址以阿里云、腾讯云容器镜像服务的说明为准),或者把 certbot/certbot 和 nginx 镜像同步到自己的私有镜像仓库后再拉取。

资料来源

  1. certbot/certbot on Docker Hub
  2. Certbot: Get Certbot (Docker)
  3. Certbot User Guide
  4. Docker Compose documentation
  5. nginx: Configuring HTTPS servers