Traefik + Docker:自托管服务的终极反向代理方案

如果你在一台 VPS 上自托管多个服务——比如 Nextcloud、n8n、Vaultwarden 和个人博客——你一定需要一个反向代理。Traefik 是基于 Docker 部署的金标准方案:它能自动发现运行中的容器,自动申请 Let’s Encrypt TLS 证书,零配置即可完成流量路由。

本文你将学会:

  • 使用 Docker Compose 部署 Traefik v3
  • 配置 Let’s Encrypt 自动 HTTPS(HTTP-01 + TLS-ALPN-01)
  • 使用 Docker labels 实现零配置路由
  • 链式组合中间件实现限流、压缩、IP 白名单
  • 用 HTTP Basic Auth 保护管理面板
  • 生产环境安全加固 Traefik

为什么选 Traefik 而不是 Nginx 或 Caddy?

特性TraefikNginx Proxy ManagerCaddy
自动服务发现✅ 原生 Docker API❌ 手动❌ 手动
自动 SSL + 续期✅(原生)✅(界面)✅(原生)
动态配置免重启
中间件链✅ 丰富的生态⚠️ 有限✅ 基础
Kubernetes 支持
Prometheus 指标⚠️ 需插件

如果你在 VPS 上运行 5 个以上 Docker 服务,并且经常增删,Traefik 的自动发现能力能为你节省大量时间。

第一步:目录结构

创建专门的 Traefik 目录:

mkdir -p /opt/traefik
cd /opt/traefik
mkdir -p data config
touch data/acme.json
chmod 600 data/acme.json

acme.json 存储 Let’s Encrypt 证书——chmod 600 是必需的,否则 Traefik 会拒绝启动。

第二步:Docker Compose 配置

创建 /opt/traefik/docker-compose.yml

services:
  traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CF_API_EMAIL=${CF_API_EMAIL}
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/acme.json:/acme.json
      - ./config:/config
      - ./data/logs:/var/log/traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      # 管理面板 Basic Auth
      - "traefik.http.routers.traefik.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_ADMIN_USER}:${TRAEFIK_ADMIN_PASSWORD_HASH}"

networks:
  proxy:
    external: true

重要: 先创建 proxy 网络:docker network create proxy

第三步:静态配置文件(traefik.yml)

创建 config/traefik.yml

api:
  dashboard: true
  debug: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    watch: true
  file:
    directory: /config
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: youremail@example.com
      storage: /acme.json
      # 使用 DNS-01 挑战以支持通配符证书
      # dnsChallenge:
      #   provider: cloudflare
      #   resolvers:
      #     - "1.1.1.1:53"
      #     - "8.8.8.8:53"
      httpChallenge:
        entryPoint: web

log:
  level: INFO
  filePath: /var/log/traefik/traefik.log

accessLog:
  filePath: /var/log/traefik/access.log
  bufferingSize: 100
  format: json

第四步:环境变量文件

创建 .env

CF_API_EMAIL=youremail@example.com
CF_DNS_API_TOKEN=你的_cloudflare_token
TRAEFIK_ADMIN_USER=admin
# 用以下命令生成:$(htpasswd -nb admin securepassword)
TRAEFIK_ADMIN_PASSWORD_HASH=admin:$2y$05$xxx...

生成密码哈希:

apt install apache2-utils -y
htpasswd -nb admin "你的强密码"

将输出复制到 .env 文件中。

第五步:动态文件配置(中间件)

创建 config/middlewares.yml

http:
  middlewares:
    # 限流 — 每 IP 每分钟 100 次请求
    rate-limit:
      rateLimit:
        average: 100
        burst: 50
        period: 1m
        sourceCriterion:
          ipStrategy:
            depth: 1

    # 安全响应头
    sec-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"
        referrerPolicy: "strict-origin-when-cross-origin"
        permissionsPolicy: "camera=(), microphone=(), geolocation=()"

    # IP 白名单(管理员专用服务)
    whitelist:
      ipWhiteList:
        sourceRange:
          - "10.0.0.0/8"
          - "192.168.0.0/16"
          - "你的家庭IP/32"
          - "100.64.0.0/10"

    # Gzip 压缩
    compress:
      compress:
        excludedContentTypes:
          - "text/event-stream"
          - "image/webp"
          - "image/png"

    # CORS(用于 n8n 等 API 服务)
    cors:
      headers:
        accessControlAllowMethods:
          - "GET"
          - "POST"
          - "PUT"
          - "DELETE"
          - "OPTIONS"
        accessControlAllowOriginList:
          - "*"
        accessControlMaxAge: 100
        accessControlAllowCredentials: true
        addVaryHeader: true

第六步:启动 Traefik

docker compose up -d
# 查看日志
docker compose logs -f

如果一切配置正确,你应该能看到:

time="..." level=info msg="Configuration loaded from provider: docker"
time="..." level=info msg="Starting provider aggregator ..."

访问 https://traefik.yourdomain.com 查看管理面板。

第七步:使用 Docker Labels 路由服务

将任何 Docker 服务通过 Traefik 暴露出去,无需修改任何配置文件。以 n8n 为例:

services:
  n8n:
    image: n8nio/n8n:latest
    networks:
      - proxy  # <-- 和 Traefik 在同一网络
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
      # 中间件链
      - "traefik.http.routers.n8n.middlewares=rate-limit,sec-headers,compress@file"

就这样。运行 docker compose up -d 启动 n8n,Traefik 会自动:

  1. 通过 Docker API 检测新容器
  2. 生成路由规则
  3. 从 Let’s Encrypt 获取 SSL 证书
  4. 应用中间件链
  5. 开始服务 HTTPS 流量——无需重启 Traefik

第八步:生产环境安全加固清单

在将 Traefik 暴露到公网之前:

  • 使用 DNS-01 挑战 替代 HTTP-01——支持通配符证书(*.yourdomain.com),且不需要 80 端口直接可达
  • 启用 fail2ban 保护 SSH 和 Traefik 访问日志:
    # /etc/fail2ban/jail.local
    [traefik]
    enabled = true
    port = http,https
    filter = traefik
    logpath = /opt/traefik/data/logs/access.log
    maxretry = 20
    findtime = 600
    bantime = 3600
    
  • 限制 Docker socket 权限——以只读方式挂载(:ro),切勿暴露给不受信任的容器
  • 对所有路由设置限流(使用上面配置的 rate-limit 中间件)
  • 启用 Prometheus 指标 用于监控:
    metrics:
      prometheus:
        addEntryPointsLabels: true
        addServicesLabels: true
        entryPoint: metrics
    
  • 使用环境变量 存储密钥(.env 文件),绝不硬编码
  • 保持 Traefik 更新docker compose pull && docker compose up -d
  • 配置日志轮转 防止磁盘溢出:
    log:
      filePath: /var/log/traefik/traefik.log
      format: json
      level: WARN  # 调试用 INFO,生产用 WARN
    

实战案例:$10 VPS 上的完整堆栈

在一台 10 美元/月的 VPS 上,Traefik 背后可以运行所有这些服务:

服务域名配置方式
Nextcloudcloud.yourdomain.com在 Nextcloud 容器加 labels
Vaultwardenvault.yourdomain.com在 Vaultwarden 容器加 labels
n8nn8n.yourdomain.com在 n8n 容器加 labels
Grafanagrafana.yourdomain.com在 Grafana 容器加 labels
Traefik 面板traefik.yourdomain.com在 Traefik 自身加 labels

全部运行在一台 2 vCPU / 2 GB RAM 的 VPS 上。每个服务独立隔离在各自容器中,Traefik 统一管理 SSL 和路由,增删服务只需一条 docker compose up -d

常见问题排查

“404 page not found” → 容器不在 proxy 网络里。给服务加上 networks: [proxy]

“Certificate is not valid for hostname”Host() 规则与实际域名不匹配,或者 DNS 尚未传播。

Traefik 因 acme.json 权限无法启动 → 运行 chmod 600 data/acme.json。这是最常见的坑。

“dial tcp 127.0.0.1:xxxx: connect: connection refused” → 服务没有在你指定的端口上监听。检查容器的内部端口。

限流太严格 → 调整中间件中的 averageburst 值。普通 Web 应用建议从 200/100 开始尝试。

总结

Traefik 彻底改变了管理自托管服务的方式。自动 SSL 证书管理、基于 Docker labels 的零配置服务发现、丰富的中间件生态——这些特性的组合使它成为在 VPS 上运行多个服务的最佳反向代理。

初次配置只需 15 分钟。但在服务器的整个生命周期中——每次你添加、删除或更新一个服务——节省的时间会不断累积。无论你运行 3 个还是 30 个服务,Traefik 都是让整个堆栈变得可控的基础。

下一步: 添加 Prometheus + Grafana 监控 Traefik 指标,设置 acme.json 的自动备份,通过 Traefik Pilot 插件生态探索 IP 白名单和机器人检测等高级功能。