用 Traefik 替代 Nginx 做 VPS 反向代理:Docker 标签自动 HTTPS

Traefik 和 Docker 是天生一对:加几个 label 就自动反代+申请证书。对比 Nginx,讲清它适合谁、坑在哪。

在 VPS 上跑了一堆 Docker 服务之后,最烦的事之一就是给每个服务配反代。我们先厘清一个基础概念:什么是反向代理?想象一栋写字楼只有一个前台,所有访客(外部请求)都先到前台,前台按"你找哪家公司"把人领到对应楼层(内部服务)。这个前台就是反向代理,它有三件核心本领:统一入口——所有流量只从 80 / 443 进来,不再为每个服务各开一个公网端口;SSL 终止——证书只在反向代理这一层配置和终止,后端服务可以纯 HTTP 跑在内部网络,省去各自申请、续期证书的麻烦;负载均衡——同一服务起多个容器实例时,反向代理把请求轮流分发,挂掉一个也不影响整体可用。对自托管玩家来说,反向代理就是把"十几个 Docker 服务、十几个端口"收敛成"一个干净入口 + 一张证书"的关键一步。

和"正向代理"不同:正向代理代表客户端去访问外网(比如公司内网的统一出口),而反向代理是代表后端服务面对公网,对客户端隐藏了背后有几台机器、各自跑在哪个端口。用 Nginx 的话,每加一个站点你就要新建一个 server 块、改完 reload、证书还得自己用 certbot 跑一遍,服务一多配置文件就变成一团乱麻。Traefik 的思路完全不同:它直接盯着 Docker 的套接字,你只要在容器上贴几个 label,它就自动帮你建路由、申请并续期 Let's Encrypt 证书。下面把 Traefik 和 Nginx 的核心差别讲清楚,再手把手用 Docker label 做自动 HTTPS,聊中间件、仪表盘、多域名证书管理,最后把新手最容易踩的坑和排错清单摊开说。

延伸阅读

更多相关攻略推荐:2026 独立站 PCI-DSS 自查清单:SAQ A 还是 A-E2026 只选月付 VPS:不锁年付、随时退的试错策略2026 海外仓/ERP 系统自建部署:独立服务器还是云 VPS?2026 实测:VPS 上自托管 AI 编程助手——Continue【知识库自托管 02】2026 实测:VPS 自托管 Anythin

一、Traefik 和 Nginx 到底差在哪

先说结论:两者不是谁取代谁,而是工作模型不同。Nginx 诞生于 2004 年(作者 Igor Sysoev,现属 F5),用 C 编写、BSD 2-clause 许可,是"性能与稳定"的代名词;Traefik 2015 年由 Traefik Labs(原 Containous)用 Go 编写,代理部分 MIT 许可,生来为云原生设计。两者最大的分歧在配置模型:Nginx 是"配置驱动"——你手写 nginx.conf 里的 server / location / upstream 块,新增一个站点就要改文件、然后 nginx -s reload 才生效,它对静态站点、复杂 rewrite 规则、超高并发的原始性能非常强,是久经考验的瑞士军刀。Traefik 是"声明驱动 + 自动发现"——它作为 Docker provider 挂载 /var/run/docker.sock,实时监听容器的启停和标签变化,容器一上线路由自动出现,容器一删路由自动消失,全程零 reload、零手写配置文件,这对"服务经常增删"的 Docker 场景简直是天作之合。

第三方评测给出了直观的性能差距:相同负载下 Traefik 约比 Nginx 少 26% 的每秒请求数(约 7.4 万 vs 10 万 RPS),平均延迟高约 3 毫秒。但代价换来了巨大的便利:Traefik 自带 ACME 客户端,Let's Encrypt 证书自动签发加续期开箱即用,而 Nginx 要另外挂 Certbot 并配续期钩子,多一个会出错的环节。Traefik v3 还带来了 HTTP/3、WASM 中间件、原生 OpenTelemetry 与 Kubernetes Gateway API,并内置实时仪表盘。

一句话选型:纯 Docker 全家桶、想省心自动 HTTPS,选 Traefik;跑静态站、要压榨极限性能、或已有成熟 Nginx 配置,继续用 Nginx。两者也能共存,Traefik 在前、Nginx 当某个 upstream 也常见。如果你的服务列表半年不变,Nginx 更稳;如果你的服务像韭菜一样一茬茬地长,Traefik 省下的精力远超那点性能差异——而且对绝大多数个人和小团队的真实流量(每秒几十到几百请求),这点转发开销完全可以忽略,瓶颈几乎永远在应用本身而不是反代。

二、动手前准备:域名、端口、目录与权限

动手前做几项一次性准备:①把域名 A 记录(@ 以及可能需要的 * 通配符)指向 VPS 公网 IP;②确认防火墙放通 80 和 443(用 sudo ufw allow 80,443/tcp 或云厂商安全组);③建一个专用 Docker 网络供 Traefik 与后端互通:docker network create traefik;④建目录并创建证书存储文件,权限必须严格:touch acme.json && chmod 600 acme.json——Traefik 发现权限过松会直接拒绝启动;⑤准备一个联系邮箱用于 Let's Encrypt 的到期通知。

这里呼应我们《Docker Compose 部署》的思路:把后端服务只绑到 127.0.0.1(例如 -p 127.0.0.1:8080:8080),让它只接受本机也就是反向代理的流量,公网唯一暴露的只有 Traefik 的 80 / 443,安全面最小。Traefik 是唯一面向公网的"前台",其余服务都躲在它身后。详见 用 Docker Compose 在 VPS 拉起全家桶

三、最小可用的 Traefik 静态配置

Traefik 的配置分两层。静态配置(traefik.yml)定义入口、证书解析器、provider,一般启动后不怎么动;动态配置(标签或动态文件)才描述具体路由。先写一个最基础的静态配置(已顺手开启 dashboard):

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

api:
  dashboard: true

providers:
  docker:
    endpoint: unix:///var/run/docker.sock
    exposedByDefault: false
    network: traefik
  file:
    filename: /config.yml
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /acme.json
      httpChallenge:
        entryPoint: web

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

几个要点:entryPoints 定义 80 和 443 两个入口,并把 80 全部 301 跳到 443;providers.docker 挂了 Docker 套接字,exposedByDefault: false 表示默认不暴露任何容器,只在你显式打 label 时才接管,这是个重要的安全习惯;certificatesResolvers.letsencrypt 用 HTTP-01 挑战自动签发证书,证书存进 /acme.json。注意这个 acme.json 权限必须设成 600,否则 Let's Encrypt 会拒绝写入:

touch acme.json
chmod 600 acme.json

HTTP-01 挑战要求 80 端口对公网可达,如果你的 VPS 在 NAT 后面或运营商封了 80/443,就得改用 DNS-01 挑战(比如 Cloudflare 的 API token),那种情况证书也能自动续,只是配置里要换成 dnsChallenge。调试排错时务必先接 staging 环境(见下文),不要拿生产限额练手。

四、用 Docker label 自动反代 + 自动 HTTPS

把 Traefik 本身也用 compose 拉起来,关键是把 docker.sock 挂进去,并建一个外部网络 traefik 让各服务互通:

services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
    networks:
      - traefik
    restart: unless-stopped

networks:
  traefik:
    external: true

然后任何一个业务容器,只要贴上这几行 label,Traefik 就会自动给它建路由并申请证书。规则字符串在 Traefik 里用反引号包裹,但为避免和某些模板冲突,下面统一用转义双引号写法(两者官方都认可):

services:
  blog:
    image: wordpress:6.6-apache
    container_name: blog
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.blog.rule=Host("blog.example.com")"
      - "traefik.http.routers.blog.entrypoints=websecure"
      - "traefik.http.routers.blog.tls.certresolver=letsencrypt"
      - "traefik.http.services.blog.loadbalancer.server.port=80"
    restart: unlessstopped

networks:
  traefik:
    external: true

逐条解释:traefik.enable=true 打开接管(因为全局 exposedByDefault 是 false);rule=Host(...) 声明这个路由匹配哪个域名;entrypoints=websecure 表示走 443;tls.certresolver=letsencrypt 让 Traefik 用前面定义的解析器去申请并自动续期证书;loadbalancer.server.port=80 告诉它容器内部真正监听的端口(很关键,不写就不知道往哪转)。域名 DNS 先 A 记录指向这台 VPS,容器起来后几十秒内证书就自动签发好了,访问 https://blog.example.com 即可。加第二个服务?复制一份 label 把名字和域名改掉就行,完全不用动 Traefik 配置。想先快速验证整套机制,把上面的 wordpress 换成一个最小的 traefik/whoami 演示镜像同样好用。

五、ACME 与 Let's Encrypt:证书是怎么"自动"签出来的

Let's Encrypt 是一家免费证书颁发机构,它用 ACME 协议(Automatic Certificate Management Environment)让机器自动完成"申请—验证—签发—续期"全流程。验证你对域名的所有权,主要靠三种挑战(challenge):HTTP-01 要求你在 80 端口放一个指定内容的文件,最常见也最省心,但要求 80 端口对公网可达;DNS-01 要求你在 DNS 里加一条 TXT 记录,它能在不开放 80 端口的情况下签发证书,而且是实现通配符证书(*.example.com)的唯一方式;TLS-ALPN-01 走 443 端口,适合 80 端口被占、又不想动 DNS 的场景。证书有效期 90 天,Traefik 会在到期前自动续期,你基本不用管。回到本文用的 HTTP-01,上面的静态配置已经配好;若你的 VPS 在 NAT 后或运营商封了 80/443,就把 certificatesResolvers 里的 httpChallenge 换成 dnsChallenge(见第九节多域名部分)。

但 Let's Encrypt 有速率限制,调试时务必先接 staging 环境(限额宽松得多),否则很容易踩坑:每个注册域名每周最多 50 张证书;完全相同标识符集合的证书每周最多 5 张(约每 34 小时补 1 张额度);每个账户每 3 小时最多 300 个新订单;每个标识符每账户每小时最多 5 次验证失败。反复重装或删除 acme.json 重来,最容易撞上"重复证书"上限——所以排错时坚定地先上 staging(在 certificatesResolvers 里加 caServer: https://acme-staging-v02.api.letsencrypt.org/directory),确认无误再切回生产。

六、中间件:限流、压缩、重定向、鉴权一把抓

Traefik 的杀手锏是中间件(middleware)——在请求到达服务前对请求做加工,还能像流水线一样串联(chain)。最常见几个:压缩、安全响应头、限流、HTTP 重定向、基础鉴权。可以直接写进静态 config.yml,也可以继续用 label 引用。

http:
  middlewares:
    compress:
      compress:
        minLength: 1024
    security-headers:
      headers:
        frameDeny: true
        contentTypeNosniff: true
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        referrerPolicy: "strict-origin-when-cross-origin"
    ratelimit:
      rateLimit:
        average: 100
        burst: 50
        period: 1m

这段定义了三个中间件:compress 开启 Brotli/gzip 压缩(v3 默认支持 Brotli,客户端不支持时回退 gzip);security-headers 加上防点击劫持和 MIME 嗅探的响应头,并启用 HSTS 一年;ratelimit 限制每个来源平均每秒 100 个请求、突发 50 个,用来挡住暴力破解和简单的 DoS。把它们挂到路由上只需一行 label:

      - "traefik.http.routers.blog.middlewares=compress,security-headers,ratelimit"

多个中间件用逗号分隔,执行顺序从左到右,通常把限流放最前面,先把 abusive 流量挡掉再谈别的。还有个实用的:把 www 重定向到裸域,用 redirectregex 中间件把 ^https://www\.(.+) 换成 https://$1 即可,老域名迁新域名也同理。中间件还能用 file provider 的 @file 引用方式跨服务复用,比如把 security-headers 定义在动态文件里,多个路由都用 security-headers@file 引用,改一处全局生效,比在每个 label 里重复写一遍整洁得多。

需要给管理后台加 Basic Auth,用 htpasswd -nB admin 生成 bcrypt 哈希(形如 $2y$05$...),在 compose 标签里要把每个 $ 写成 $$(Docker Compose 把单 $ 当作变量插值),否则哈希会被吞掉。把这个 basicauth 中间件挂到后台路由上,等于加了一道密码门,不必再单独挂一层认证服务。

七、仪表盘 Dashboard:用 api@internal 暴露并加 basicauth

Traefik 内置一个实时仪表盘,能看到所有路由、中间件和服务健康状态,非常利于排查。上面静态配置已开 api.dashboard: true,现在在 traefik 这个容器上追加一组 label 把它暴露出来,并用 basicauth 保护(绝对不要裸奔到公网):

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host("traefik.example.com")"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$xxxxxxxxxxxx"

traefik.example.com 换成你真实的管理子域名,且它的 DNS 已解析到本机;basicauth.users 里填上面 htpasswd 生成的、转义好的哈希。这样仪表盘走 HTTPS、带账号密码,安全性才过关。日常监控证书状态也可以接 Prometheus 抓取 http://traefik:8080/metrics,里面有权威的证书过期指标,配合告警最稳。

八、多域名与 SSL 证书管理

多个域名共用一个容器也简单,规则用 || 拼起来:Host("a.example.com") || Host("b.example.com")。如果子域名特别多(比如十几个),逐个申请证书既啰嗦又容易撞 Let's Encrypt 每域名每周 50 张的限额,这时该上通配符证书:它必须走 DNS-01 挑战,因为 Let's Encrypt 要验证你控制整个 DNS 区域。需要 DNS 服务商的 API Token(如 Cloudflare 的 Zone:DNS:Edit),在 certificatesResolvers 里把 httpChallenge 换成 dnsChallenge:

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /acme.json
      dnsChallenge:
        provider: cloudflare
        delayBeforeCheck: 10

配好后,路由里把 tls.certresolver 指向同一个 letsencrypt,再给规则加个通配符主机(如 Host("*.example.com")),一张证书覆盖所有子域。证书续期是全自动的,Traefik 会在到期前 30 天自动续,不需要 cron,也不需要你操心。

九、新手最容易踩的坑与排错清单

  • acme.json 权限不对:忘了 chmod 600,Let's Encrypt 直接报错写不进证书,这是头号坑。Traefik 发现权限过松甚至会拒绝启动,执行 chmod 600 acme.json 再启动即可,不要用 644 或 777。
  • 容器没进 traefik 网络:服务容器和 Traefik 不在同一个外部网络里,Traefik 发现了容器却连不上它的端口,表现就是 502。务必让业务容器也挂 networks: traefik。
  • server.port 写错或漏写:loadbalancer.server.port 必须填容器内部真实监听端口,而不是宿主机映射端口,写错就转发到错误端口。
  • 80/443 被挡:用 HTTP-01 时公网必须能访问 80 端口,运营商封锁或 NAT 没转发就会 ACME challenge 失败,此时改 DNS-01。
  • 路由不匹配(打不开 / 404):检查标签拼写、rule 的 Host 域名是否与访问域名完全一致、服务是否和 Traefik 同网络、以及 exposedByDefault=false 时有没有加 traefik.enable=true。
  • 端口冲突:若 80 / 443 已被 Nginx 或其他程序占用,Traefik 起不来;用 sudo ss -tlnp | grep -E ':80|:443' 查占用者,停掉或改端口。
  • dashboard 打不开:确认 dashboard 路由的 Host 域名 DNS 已解析、tls.certresolver 已设、且 basicauth 哈希格式正确($$ 转义到位)。
  • 把 Docker 套接字暴露出去:docker.sock 等同 root,千万别把它映射到公网端口或挂进不受信任的容器,只读挂载 + 仅本地使用是底线。

最后提醒备份:acme.json 里存着你的证书和 Let's Encrypt 账号密钥,丢了就得重新申请(还要受每周 50 张限额约束),所以备份 VPS 时一定把 acme.json 和你的 compose 文件一起打包。

延伸阅读:Traefik 正是我们 Docker 全家桶里的反代担当,建议搭配 用 Docker Compose 在 VPS 拉起全家桶 一起看;安全响应头和限流属于基础加固,详见 VPS 安全基础;刚开始玩 VPS 还摸不着头脑的,先看 VPS 新手入门指南