# Traefik v3 配置 Let's Encrypt：Docker 自动 HTTPS

> 让 Traefik v3 自动申请 Let's Encrypt 证书：证书解析器、acme.json 存储、Docker 标签、HTTP 跳转 HTTPS，以及用阿里云 DNS 申请通配符证书。

Source: https://ssl.sh/zh/guides/traefik-lets-encrypt/
Last updated: 2026-11-10

Traefik v3 用证书解析器自己申请和续期 Let's Encrypt 证书。Docker 容器加上四个标签，就能通过 `https://example.com` 访问，HTTP 自动跳转到 HTTPS。适用于 Docker Compose 部署的 Traefik v3.x（截至 2026 年 11 月参考文档对应 v3.7）。

## 前提条件

Traefik 需要独占主机的 80 和 443 端口。

- 装有 Docker Engine 和 Compose 插件的 Linux 主机。
- `example.com` 的 A/AAAA 记录指向主机，例如 `203.0.113.10`；服务器在中国大陆的需已备案。
- 防火墙和安全组放行 TCP 80、443。
- 一个项目目录，例如 `/opt/traefik`。

## 第一步：编写静态配置

静态配置文件 `traefik.yml` 定义 80 和 443 两个入口、HTTP 跳转和名为 `le` 的 Let's Encrypt 证书解析器：

```yaml
# /opt/traefik/traefik.yml
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  le:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false
```

Traefik 会在跳转生效之前处理 `web` 入口上的 ACME HTTP 验证请求，所以把所有 HTTP 流量跳转到 HTTPS 是安全的。

## 第二步：编写 Compose 文件

Compose 文件启动 Traefik 和一个示例服务，服务用 Docker 标签声明域名和证书解析器：

```yaml
# /opt/traefik/compose.yaml
services:
  traefik:
    image: traefik:v3.7
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

  whoami:
    image: traefik/whoami
    labels:
      - traefik.enable=true
      - traefik.http.routers.whoami.rule=Host(`example.com`)
      - traefik.http.routers.whoami.entrypoints=websecure
      - traefik.http.routers.whoami.tls.certresolver=le
```

镜像标签用 Traefik 文档里当前的 v3 版本。把 `whoami` 换成你自己的服务，保留这四个标签。

## 第三步：准备 acme.json 并启动

Traefik 把证书存在 `acme.json` 里，这个文件要提前创建并设为 600 权限：

```bash
cd /opt/traefik
mkdir -p letsencrypt
touch letsencrypt/acme.json && chmod 600 letsencrypt/acme.json
sudo docker compose up -d
sudo docker compose logs -f traefik
```

调试阶段在 `acme:` 下加 `caServer: https://acme-staging-v02.api.letsencrypt.org/directory` 使用测试环境，正式上线前删掉这行并清空 `acme.json`。

## 第四步（可选）：用 DNS 验证申请通配符证书

把 `httpChallenge` 换成 lego 支持的 DNS 服务商，用环境变量传入密钥，在路由上声明通配符域名。以阿里云 DNS 为例：

```yaml
certificatesResolvers:
  le:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: alidns
```

```yaml
    environment:
      - ALICLOUD_ACCESS_KEY=your-access-key-id
      - ALICLOUD_SECRET_KEY=your-access-key-secret
    labels:
      - traefik.http.routers.whoami.tls.domains[0].main=example.com
      - traefik.http.routers.whoami.tls.domains[0].sans=*.example.com
```

`environment` 写在 `traefik` 服务下，标签写在你的应用上。想申请 Let's Encrypt 的 45 天证书，在解析器里加 `profile: tlsserver` 和 `certificatesDuration: 1080`，见 [Let's Encrypt 证书配置文件](https://ssl.sh/zh/guides/lets-encrypt-certificate-profiles/)。

## 验证

配置正确时，证书颁发者是 Let's Encrypt，而不是 `TRAEFIK DEFAULT CERT`，HTTP 请求跳转到 HTTPS。用 [SSL 证书检查](https://ssl.sh/zh/tools/ssl-checker/) 检查，或在终端运行：

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

在主机上：

```bash
curl -sI http://example.com | head -n 3
sudo docker compose logs traefik | grep -iE 'acme|certificate' | tail -n 20
```


## 自动续期

Traefik 运行期间会在后台自动续期，不需要 cron。默认 `certificatesDuration` 为 2160 小时，即按 90 天证书处理、到期前 30 天开始续期。续期时间取决于这个设置而不是证书本身，所以有效期变化时要同步修改：Let's Encrypt 默认证书 2027 年 2 月 10 日改为 64 天、2028 年 2 月 16 日改为 45 天，届时以 Traefik 发布说明推荐的取值为准。把域名加到 [ssl.sh 监控](https://ssl.sh/zh/monitor/)（开发中），确认续期确实发生。

## 常见问题排查

- **permissions 644 for /letsencrypt/acme.json are too open**：对文件执行 `chmod 600`。
- **浏览器显示 TRAEFIK DEFAULT CERT**：路由没有引用解析器或签发失败，见 [NET::ERR_CERT_AUTHORITY_INVALID](https://ssl.sh/zh/errors/net-err-cert-authority-invalid/) 和 Traefik 日志。
- 日志里出现 **Timeout during connect**：80 端口到不了 Traefik（安全组、运营商封端口、未备案拦截），原因和 [certbot 连接超时](https://ssl.sh/zh/errors/certbot-timeout-during-connect/) 相同。
- **too many certificates already issued**：`acme.json` 没有持久化，重启后反复申请，见 [Let's Encrypt 频率限制](https://ssl.sh/zh/errors/lets-encrypt-rate-limit/)。
- **router uses a nonexistent resolver**：`tls.certresolver` 里的名字和 `certificatesResolvers` 里定义的不一致。
