Blog

Traefik 反代与自动 HTTPS 证书

服务一多,每个容器自己映射 443 会打成一团。Traefik 做边缘:统一听 80/443,按域名路由到后端,顺手申请 Let's Encrypt 证书。和手写 Nginx server 块比,Docker 标签更贴容器生命周期;和 Caddy 比,我更熟它的中间件链。本文不按「安装—配置—上线」流水账写,而是把一份能跑的 Traefik 配置拆开,逐段说明每一行在干什么,以及省略它时会怎样。

entrypoints:流量从哪进门

command:
  - --entrypoints.web.address=:80
  - --entrypoints.websecure.address=:443

web 与 websecure 只是名字,却会在后面所有路由的 entrypoints= 里反复出现。80 不仅给人类访问,更常给 ACME 的 HTTP-01 挑战用;443 才是业务主入口。家庭栈里若 1Panel 或其它面板也想占 80/443,必须二选一做边缘,另一个做上游——两人抢 443 时,证书失败往往表现为「浏览器报错证书,日志里其实是绑定端口失败」。把 HTTP 自动跳转到 HTTPS 时,跳转本身也应挂在 web 上,避免用户长期停留在明文入口。

容器 ports 把宿主 80/443 映射进来后,宿主机上就不要再让 Nginx 静默听着同一端口。ss -lntp | grep -E ':80|:443' 是改配置前的固定动作。IPv6 若已启用,确认 entrypoint 同时听在 v6 上,否则会出现「手机蜂窝网能开、家里 v6 优先却打不开」的假性故障。

Docker provider:谁有资格被路由

- --providers.docker=true
- --providers.docker.exposedbydefault=false
volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

exposedbydefault=false 是这条配置里我最不愿意删的一行。默认暴露意味着:忘了写标签的容器也可能被扫进路由表。只读挂载 docker.sock 降低一点风险面,但 sock 本身仍是高权限通道,Traefik 所在主机的 SSH 与系统更新不能松。

业务服务要被发现,必须显式:

labels:
  - traefik.enable=true
  - traefik.http.routers.app.rule=Host(`app.example.com`)
  - traefik.http.routers.app.entrypoints=websecure
  - traefik.http.routers.app.tls.certresolver=le
  - traefik.http.services.app.loadbalancer.server.port=8080

rule 用 Host() 最常见;同一容器多域名可用 Host() 与逻辑或拼接。server.port 写的是容器内监听端口,不是宿主映射口。漏写 port 时,Traefik 有时能猜,有时默默指错,表现为 502 而应用日志完全干净——先查标签再查应用。

certificatesresolvers:证书从哪来

- [email protected]
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

le 这个名字会在路由的 tls.certresolver=le 里引用。acme.json 所在目录要持久化,权限建议 600,丢了就等于集体重新签发。HTTP-01 要求公网 80 能打到这个 entrypoint;域名没指到本机、CDN 橙云挡了挑战路径、80 被占用,是申请失败的三大常见原因。排错看 Traefik 日志里的 ACME 段落,不要只刷新浏览器。

TLS-ALPN 挑战适合不想在 80 上折腾的场景,但家庭网络和部分隧道组合下 HTTP-01 更直观。续期是自动的,前提是 80/443 一直在 Traefik 手里;到期前日志里就会有失败痕迹,可用探测 HTTPS 或盯 acme.json 变更时间做粗监控。

middlewares:横切逻辑不要抄进每个服务

安全头、压缩、鉴权、限流适合做成可复用中间件。我倾向 file provider 放一份通用头:HSTS、X-Content-Type-Options、Referrer-Policy、适度 CSP,再在路由上:

- traefik.http.routers.app.middlewares=sec-headers@file

所有 HTTPS 路由挂同一条链,避免每个 compose 抄一遍又抄错。Websocket 类应用若出现莫名断开,再按应用补 headers 或调超时中间件,而不是一上来改全局默认。

api.dashboard:路由表不是风景

- --api.dashboard=true

仪表盘能看见全部 router 与 service,对调试极友好,对攻击者也一样。务必挂 BasicAuth / ForwardAuth,或只绑定 WireGuard 网段,绝不要公网裸奔。exposedbydefault=false 管的是业务容器;dashboard 是 Traefik 自己的入口,要单独收口。

内网解析与标签之外的现实

家庭访问 Immich 一类上传型服务时,把内网 DNS 指到内网 IP,避免照片绕公网再回来。标签只能描述「容器网络里怎么走」,解释不了「客户端从哪条路径进来」。split DNS 或在路由器里写主机覆盖,比让手机 App 走公网域名再绕回客厅交换机要诚实得多;上传型流量尤其敏感,CDN 计费与延迟会同时惩罚你。

网络平面也值得单独想一层:Traefik 与业务容器共处同一 Docker network(上文示例里的 proxy),业务端口不必再映射到宿主。只让 Traefik 暴露 80/443,等于把「谁能被扫到」收敛到一个进程。新服务入网时,先加入该 network,再写 labels,顺序反了会出现「容器在跑、Traefik 报后端不可达」。

概念入门建议只接两个后端:一个静态页、一个带 Websocket 的应用,把 entrypoint、router、service、middleware、certresolver 五条线走通,再批量铺 labels。排错口诀可以记成:先 curl -v 看证书与路由是否命中,再看 Traefik 自己的 access 日志,最后才怀疑应用。铺开之后,加一个服务往往只是几行标签——这正是我想要的日常体验。

← Back to Blog