ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自托管服务实战(5):反向代理与自动 HTTPS(Caddy/Traefik)

自托管服务实战(5):反向代理与自动 HTTPS(Caddy/Traefik)

前四篇的应用都优先绑定回环地址。本篇增加统一入口:由反向代理终止 TLS、按域名选择上游并记录请求结果。入口只负责传输与路由,不替代应用登录、主机防火墙或备份。

一、先画清一条请求链

请求依次经过 DNS、路由器或防火墙、代理监听端口、证书选择、路由规则和应用上游。浏览器报证书错误,应先核对域名、系统时间和证书链;返回 502,则通常是代理无法连接上游;返回应用自己的 401,反而说明前面的网络和 TLS 多半已经通了。按层定位比反复重启容器更可靠。

Caddy 适合用简洁配置自动申请证书;Traefik 擅长从容器标签动态发现路由。家庭环境选任何一个都可以,关键是只保留一个权威入口。代理与应用加入专用网络,数据库仍在内部网络。上游使用容器服务名和容器端口,不能把localhost误认为宿主机;在代理容器里,localhost指代理自己。

公开域名申请证书通常需要 DNS 正确解析且验证端口可达,或使用 DNS-01。内网域名可使用私有 CA,但每台客户端必须信任根证书。不要跳过证书校验,也不要把私钥提交到 Git。证书状态目录需要持久化和备份,否则每次重建都重新申请,可能触发频率限制。

下面的程序静态审查一组路由:域名不能重复,上游不能指向数据库,公开入口必须要求 TLS。把路由清单转成这种中间表示,换 Caddy、Traefik 或其他代理时仍可复用规则。

fromdataclassesimportdataclass@dataclass(frozen=True)classRoute:host:strupstream:strtls:boolpublic:boolroutes=[Route("vault.example.net","vaultwarden:80",True,True),Route("notes.example.net","knowledge:3000",True,True),Route("status.home.arpa","uptime:3001",True,False),]seen=set()errors=[]forrouteinroutes:valid_host=route.hostnotinseen valid_tls=route.tlsornotroute.public safe_upstream=notroute.upstream.startswith(("postgres:","redis:"))ok=valid_hostandvalid_tlsandsafe_upstreamprint(f"{route.host}:{'PASS'ifokelse'FAIL'}->{route.upstream}")ifnotok:errors.append(route.host)seen.add(route.host)print(f"unique_hosts={len(seen)}")print(f"route_gate={'PASS'ifnoterrorselse'FAIL'}")

运行输出:

vault.example.net: PASS -> vaultwarden:80 notes.example.net: PASS -> knowledge:3000 status.home.arpa: PASS -> uptime:3001 unique_hosts=3 route_gate=PASS

二、自动 HTTPS 也需要验收

自动续期并不意味着永不失效。监控应从客户端视角读取证书的域名、签发者和到期时间,并在剩余 30 天、14 天等阈值告警。代理健康检查只证明进程活着;还应通过域名访问一个受控路径,验证 DNS、TLS、路由与上游整体链路。日志保留状态码、耗时和请求 ID即可,密码、Cookie、授权头与查询中的令牌必须脱敏。

以下程序计算证书剩余天数并给出分级结论,使用固定时间保证输出可复现。生产脚本可将expires替换为 TLS 握手读到的日期。

fromdatetimeimportdatetime,timezone now=datetime(2026,8,4,tzinfo=timezone.utc)certificates={"vault.example.net":datetime(2026,10,20,tzinfo=timezone.utc),"notes.example.net":datetime(2026,9,18,tzinfo=timezone.utc),"status.home.arpa":datetime(2027,8,4,tzinfo=timezone.utc),}states=[]forhost,expiresincertificates.items():days=(expires-now).daysifdays<14:state="critical"elifdays<30:state="warning"else:state="ok"states.append(state)print(f"{host}: days_left={days}state={state}")healthy=all(state=="ok"forstateinstates)print(f"checked={len(certificates)}")print(f"certificate_gate={'PASS'ifhealthyelse'FAIL'}")

运行输出:

vault.example.net: days_left=77 state=ok notes.example.net: days_left=45 state=ok status.home.arpa: days_left=365 state=ok checked=3 certificate_gate=PASS

三、上线与迁移顺序

先在本机 hosts 文件或测试子域验证新代理,不立即改生产 DNS。确认响应头、上传大小、WebSocket、流式请求和真实客户端 IP;不同应用对这些能力要求不同。再把 DNS TTL 临时降低,切换入口并观察错误率。旧入口保留短时间只读回退,稳定后关闭多余端口。

代理配置纳入版本控制,证书私钥与 DNS API 令牌不纳入。DNS 令牌只授予所需区域和记录权限。若使用容器自动发现,不要默认暴露所有容器;采用显式启用标签,且谨慎授予 Docker socket,因为它拥有很高的宿主控制能力。

上线后还应保存一份绕过代理的上游探测方法。外部域名失败而上游正常,问题在 DNS、TLS 或代理;两者同时失败,才继续检查应用和数据层。多域名共用入口时,为每个上游设置独立超时与请求体上限,避免一个慢服务耗尽代理连接,也避免上传型应用被默认限制意外截断。变更配置前运行语法校验,加载后确认旧连接如何处理,才能做到平滑而非碰运气式重启。

可迁移的成果是域名到上游的路由表、证书获取方式、秘密恢复位置与端到端探测,而非某个代理品牌。下一篇在此基础上讨论远程访问:对管理界面和家庭服务,私有组网往往比把更多域名暴露到公网更合适。

参考来源

  • Caddy:Automatic HTTPS
  • Traefik:Docker provider
  • Let’s Encrypt:验证方式

👍 觉得有用就点个赞 + 收藏,方便回头查阅;有疑问直接在评论区留言,我看到都会回。

🚀 本文属于《自托管服务实战》系列,持续更新,关注不迷路。

📌 文章里的代码都能直接跑。想要可直接 clone 的完整工程 + 配套部署脚本 / 踩坑清单?评论一声或发邮件到cj2664@qq.com,我免费发你。
如果你正好在做类似系统、或有工程化难题想找人做,也欢迎邮件聊一句——我按实际情况评估,能落地的就接单或出方案。评论和邮件都能直接找到我,不用跳别的平台。

返回列表