跳过正文

使用Traefik和Let's Encrypt实现自动TLS证书

锦李本鲤
作者
锦李本鲤
光锥之内,皆为命运。
目录
反向代理系列 - 这篇文章属于一个选集。
§ 3: 本文

背景
#

我之前一直使用Cloudflare Tunnel免费版来反向代理Nextcloud容器。虽然Cloudflare Tunnel使用方便,但免费版有 100MB上传限制,这成为我使用Nextcloud时的主要障碍。

本文记录了我从Cloudflare Tunnel迁移至直接使用Traefik反向代理,并配置Let’s Encrypt TLS证书的完整过程,包括设置、遇到的问题和解决方案。

迁移原因
#

Cloudflare Tunnel免费版的优点
#

  • ✅ 自动SSL/TLS终止
  • ✅ 无需端口转发
  • ✅ 支持动态IP
  • ✅ 内置DDoS防护

Cloudflare Tunnel免费版的缺点
#

  • 100MB上传限制(主要问题)
  • ❌ 带宽限制
  • ❌ 有tunnel额外延迟
  • ❌ 依赖Cloudflare基础设施

为什么选择Traefik + Let’s Encrypt
#

  • 无上传限制(可配置)
  • ✅ 直接连接(更低延迟)
  • ✅ 自动证书管理
  • ✅ 完全控制路由
  • ✅ 可处理多个服务

当前架构
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
┌────────────────────────────────────────────────────────────┐
│                      Internet                              │
└────────────────────────────┬───────────────────────────────┘
                  ┌──────────▼──────────┐
                  │     路由器            │
                  │   端口转发            │
                  │   80, 443 → 服务器    │
                  └──────────┬───────────┘
                  ┌──────────▼──────────┐
                  │     Traefik          │
                  │   (TLS 终止)         │
                  └──────────┬──────────┘
                  ┌──────────▼──────────┐
                  │     Nextcloud        │
                  │   (Docker)           │
                  └─────────────────────┘

之前: 互联网 → Cloudflare Tunnel → Traefik → Nextcloud
现在: 互联网 → Traefik → Nextcloud

前置条件
#

  • Docker和docker-compose已安装
  • Traefik容器正在运行
  • 域名(如nextcloud2.example.com
  • Cloudflare账户且域名DNS由其管理
  • 服务器有公网IP(或使用动态DNS)

步骤1: 移除Cloudflare Tunnel
#

停止cloudflared容器
#

1
2
cd /path/to/cloudflared
docker compose down

更新cloudflared配置(可选)
#

如果想保留cloudflared为其他服务,从config.yaml中移除Nextcloud条目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ingress:
  # 删除以下行:
  # - hostname: "cloud.example.com"
  #   service: https://traefik:443
  #   originRequest:
  #     noTLSVerify: true
  # - hostname: "nextcloud2.example.com"
  #   service: https://traefik:443
  #   originRequest:
  #     noTLSVerify: true
  # 保留其他条目...
  - service: http_status:404

步骤2: 在Cloudflare更新DNS
#

为每个要使用Traefik的域名:

  1. 进入 Cloudflare仪表板DNS
  2. 找到DNS记录(如nextcloud2.example.com
  3. 从橙色云(代理)改为灰色云(仅DNS)
  4. 指向服务器的公网IP地址

重要: 为了让Let’s Encrypt DNS challenge正常工作,流量必须直接到达你的服务器,而不是通过Cloudflare代理。

步骤3: 配置Traefik的Let’s Encrypt DNS Challenge
#

更新traefik.yml
#

确保有ACME配置和Cloudflare DNS challenge:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
certificatesResolvers:
  le:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

设置Cloudflare API Token
#

创建或更新traefik目录下的.env文件:

1
2
3
CF_API_KEY=your_cloudflare_api_token
CF_API_EMAIL=[email protected]
TZ=UTC

注意: DNS challenge需要Cloudflare API Token(不是全局API密钥),具有以下权限:

  • Zone DNS: 编辑区域DNS

更新docker-compose.yml
#

确保环境变量传递给Traefik:

1
2
3
4
environment:
  - CF_API_KEY=${CF_API_KEY}
  - CF_API_EMAIL=${CF_API_EMAIL}
  - TZ=${TZ:-UTC}

步骤4: 配置动态路由器带TLS
#

traefik/dynamic/目录下创建文件(如nextcloud2.yml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http:
  routers:
    nextcloud2-router-http:
      rule: "Host(`nextcloud2.example.com`)"
      entryPoints:
        - web
      middlewares:
        - redirect-https
      service: nextcloud2-service

    nextcloud2-router-https:
      rule: "Host(`nextcloud2.example.com`)"
      entryPoints:
        - websecure
      tls:
        certResolver: le  # 重要:指定Let's Encrypt解析器
      service: nextcloud2-service
  
  services:
    nextcloud2-service:
      loadBalancer:
        servers:
          - url: "http://nextcloud:80"  # Nextcloud容器名称
        passHostHeader: true

  middlewares:
    redirect-https:
      redirectScheme:
        scheme: https
        permanent: true

关键配置点
#

  1. 需要两个路由器:

    • HTTP路由器用于重定向(端口80)
    • HTTPS路由器用于实际流量(端口443)
  2. certResolver: le至关重要 - 没有它,Traefik使用内部自签名证书

  3. passHostHeader: true - 保留原始Host头

步骤5: 重启Traefik
#

1
2
cd /path/to/traefik
docker compose up -d --force-recreate traefik

Traefik将:

  1. 监听新配置文件
  2. 通过DNS challenge请求Let’s Encrypt证书
  3. 创建_acme-challenge.nextcloud2.example.com TXT记录
  4. 等待DNS传播
  5. 验证所有权
  6. 下载证书

步骤6: 验证设置
#

检查证书是否已颁发
#

1
2
3
4
5
# 查看所有证书
cat /path/to/traefik/acme.json | python3 -m json.tool

# 检查特定域名
cat /path/to/traefik/acme.json | grep -A 5 'nextcloud2'

测试HTTPS连接
#

1
2
3
4
5
6
7
8
# 验证证书
echo | openssl s_client -connect nextcloud2.example.com:443 -servername nextcloud2.example.com 2>/dev/null | openssl x509 -noout -issuer -dates

# 验证域名匹配
echo | openssl s_client -connect nextcloud2.example.com:443 -servername nextcloud2.example.com 2>/dev/null | openssl x509 -noout -subject -text | grep -E "(Subject:|DNS:)"

# 测试连接
curl -I https://nextcloud2.example.com

添加更多域名
#

要添加更多域名,只需创建更多配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 为另一个域名创建配置
cat > /path/to/traefik/dynamic/blog.yml <<'EOF'
http:
  routers:
    blog-router-http:
      rule: "Host(`blog.example.com`)"
      entryPoints:
        - web
      middlewares:
        - redirect-https
      service: blog-service

    blog-router-https:
      rule: "Host(`blog.example.com`)"
      entryPoints:
        - websecure
      tls:
        certResolver: le
      service: blog-service

  services:
    blog-service:
      loadBalancer:
        servers:
          - url: "http://blog:80"
        passHostHeader: true

  middlewares:
    redirect-https:
      redirectScheme:
        scheme: https
        permanent: true
EOF

Traefik每10秒自动重载配置,不需要重启。

常见问题及解决方案
#

1. 证书显示为自签名
#

问题: 浏览器显示"不安全"或自签名证书警告

解决方案: 在路由器配置中添加certResolver: le。没有它,Traefik使用内部证书。

2. DNS Challenge失败
#

问题: 证书未颁发,日志中有错误

解决方案:

  • 验证Cloudflare API token有正确权限
  • 检查Traefik能否通过环境变量访问API token
  • 确保DNS未被Cloudflare代理(灰色云)
  • 查看日志:docker logs traefik

3. 证书存在但不适用于域名
#

问题: Let’s Encrypt证书存在,但不是你的域名

解决方案: Traefik是按需创建证书的。只需访问该域名,Traefik将自动请求证书。

4. HTTP到HTTPS重定向不工作
#

问题: HTTP请求不重定向到HTTPS

解决方案: 确保你配置了两个路由器(HTTP和HTTPS)并带有重定向中间件。

5. Nextcloud显示"受信任域名错误"
#

问题: Nextcloud不信任新域名

解决方案: 更新Nextcloud的受信任域名:

nextcloud/docker-compose.yml中更新:

1
2
environment:
  - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud2.example.com,cloud.example.com,previous-domain.com

或在Nextcloud配置中(config/config.php):

1
2
3
4
5
'trusted_domains' => [
    'nextcloud2.example.com',
    'cloud.example.com',
    'previous-domain.com',
],

高级配置
#

启用调试日志
#

1
2
3
# traefik.yml
log:
  level: DEBUG

配置证书TTL
#

1
2
3
4
5
6
7
8
9
certificatesResolvers:
  le:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
      caServer: "https://acme-v02.api.letsencrypt.org/directory"  # 生产
      # caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"  # 测试

多个域名对应单个服务
#

1
2
3
4
5
6
7
8
9
http:
  routers:
    multi-domain-router-https:
      rule: "Host(`domain1.com`) || Host(`domain2.com`) || Host(`domain3.com`)"
      entryPoints:
        - websecure
      tls:
        certResolver: le
      service: my-service

监控与维护
#

检查证书过期
#

1
2
3
4
5
6
7
8
# 脚本检查所有证书
cat /path/to/traefik/acme.json | python3 <<'EOF'
import json, sys, datetime
data = json.load(sys.stdin)
for domain in data['le']['Certificates']:
    cert = domain['certificate']
    print(f"Domain: {domain['domain']['main']}")
EOF

手动证书续期
#

Let’s Encrypt证书每90天自动续期。强制续期:

1
docker compose exec traefik traefik renew-certs

备份acme.json
#

1
cp /path/to/traefik/acme.json /path/to/traefik/acme.json.backup-$(date +%Y%m%d)

性能对比
#

指标Cloudflare TunnelTraefik + Let’s Encrypt
上传限制100MB ❌无限制 ✅
延迟较高(tunnel开销)较低(直接) ✅
SSL证书自动 ✅自动 ✅
成本免费(有限制)免费(无限制) ✅
配置简单中等
DDoS防护内置 ✅需单独添加

总结
#

从Cloudflare Tunnel迁移到Traefik + Let’s Encrypt是直截了当的,提供:

  • 无上传限制
  • 更好的性能
  • 完全控制
  • 自动证书管理

关键要点:

  1. 移除Cloudflare Tunnel for the domain
  2. 更新DNS为仅DNS(非代理)
  3. 配置Traefik with ACME + Cloudflare DNS challenge
  4. 在路由器配置中设置certResolver: le
  5. 重启Traefik请求证书

这个设置非常适合自托管服务,并且随着你添加更多域名而扩展。

参考
#

更新日志
#

  • 2026-07-05 - 初始发布,记录迁移过程
反向代理系列 - 这篇文章属于一个选集。
§ 3: 本文