# Docker Compose 部署 Nginx + Certbot 自动 HTTPS

> 用 Docker Compose 运行 Nginx 和官方 certbot/certbot 镜像：共享验证目录和证书卷、首次签发、HTTPS 配置，以及宿主机定时续期并重载 Nginx。

Source: https://ssl.sh/zh/guides/docker-nginx-certbot/
Last updated: 2026-11-10

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` 放证书。

```bash
sudo mkdir -p /opt/web/nginx/conf.d /opt/web/certbot/www /opt/web/certbot/conf
cd /opt/web
```

```yaml
# /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/` 指向共享的验证目录：

```nginx
# /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;
    }
}
```

```bash
cd /opt/web && sudo docker compose up -d nginx
```

## 第三步：申请证书

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

```bash
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 配置：

```nginx
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;
    }
}
```

```bash
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 证书检查](https://ssl.sh/zh/tools/ssl-checker/) 从外部检查，或在终端运行：

```bash
curl ssl.sh/example.com
```

在宿主机上：

```bash
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 也没有副作用。

```ini
# /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
```

```ini
# /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
```

```bash
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 监控](https://ssl.sh/zh/monitor/)（开发中）。

## 常见问题排查

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