自建 Traefik 网关:HTTPS 打不开的完整排查路径

4110 字
21 分钟
自建 Traefik 网关:HTTPS 打不开的完整排查路径

自建网关的 HTTPS 打不开,一次定位到五个层面的问题。环境是 NAS 上的 Coolify + Traefik,三条访问路径:内网直连、Cloudflare 隧道、VPS frp 透传。

结论先放前面:「打不开」包含至少五种完全不同的故障,必须分层验证。下面是从内到外的排查顺序和每个坑,四份 compose 附在文末。

一、环境与架构#

硬件与角色#

角色说明
NAS家里的主机,固定内网 IP,跑 Docker 和所有业务容器
境外 VPS跑 3x-ui,提供 VLESS-REALITY 出站代理(这台是怎么搭的)
国内 VPS跑 frps,提供国内低延迟的公网入口
Cloudflare托管域名,提供 DNS、Tunnel、证书签发的 DNS-01 验证

三条请求路径#

内网访问:浏览器 → DNS 解析到 NAS 内网 IP → Traefik :443 → 服务容器
隧道访问:浏览器 → Cloudflare 边缘 → cloudflared 容器 → Traefik :8888 → tinyauth → 服务容器
frp 访问:浏览器 → 国内 VPS :8443 → frpc 容器 → Traefik :443 → tinyauth → 服务容器

三条路径共用同一个 Traefik 和同一批服务容器,但入口不同、证书来源不同、中间件不同。「打不开」发生在哪条路径上,排查方向完全不一样。

一个常被忽略的点:部署面板(本文是 Coolify)不在流量链路上。它只负责起停容器,停掉面板网站照常跑。把控制面和数据面分开看,能少走很多弯路。

二、分层验证:五条命令定位问题#

配置都交代完了,现在进入排查。按这个顺序跑,第一条失败的就是问题所在。

第一层:容器自己活着吗#

从 Traefik 容器内部访问目标服务,绕开所有路由和证书:

Terminal window
docker exec coolify-proxy wget -qO- --timeout=5 http://<服务名>:<端口>/ping

有响应说明容器和 Docker 网络都正常。这一步失败就去看容器日志,不用管 Traefik。

注意这里用的是服务名而不是容器名,原因见坑五。

第二层:Traefik 认这个域名吗#

用 Host 头直接问入口,绕开 DNS 和证书:

Terminal window
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: 你的域名" http://<NAS内网IP>:8888/
返回含义
200 / 302 / 401路由和中间件都正常,问题在 DNS 或客户端
404Traefik 没认出这条路由,去查 labels
502路由认了但连不上后端,查网络和端口
000入口没起来,查端口绑定

401 是好消息,说明认证中间件正在正常工作。

第三层:HTTPS 证书签下来了吗#

access log 会淹没一切,先过滤掉:

Terminal window
docker logs coolify-proxy 2>&1 | grep -v '"RequestAddr"' | grep -i -E "acme|lego|certificate" | tail -30

关键词 Server responded with a certificate 表示成功。

直接看证书库里有哪些域名(acme.json 是压缩的单行 JSON,grep 抓不到,要用 json 解析):

Terminal window
cat /data/coolify/proxy/acme.json | python3 -c "import sys,json; d=json.load(sys.stdin); print([c['domain']['main'] for r in d.values() for c in (r.get('Certificates') or [])])"

第四层:DNS 解析到哪#

Terminal window
nslookup 你的域名

在 NAS 上和在客户端上各跑一次,结果可能不同。

第五层:客户端能到吗#

在客户端(不是服务器)上:

Terminal window
curl -v https://你的域名/ --max-time 10

curl 通但浏览器不通,是一个极强的信号,说明网络层没问题,问题在浏览器这一层。原因见坑三。

三、坑一:Cloudflare API Token 权限不全#

现象:所有域名都签不到证书,日志报

[status code 403] 9109: Invalid access token
cloudflare: failed to find zone example.com.

紧接着变成

[status code 429] 10502: Too many authentication failures

429 是结果不是原因。反复用无效 token 重试触发了 Cloudflare 的限流,看到 429 时要往前翻日志找第一个错误。

根因是 token 权限少了一条。DNS-01 challenge 需要两个权限:

  • 区域 → DNS → 编辑(写 TXT 记录)
  • 区域 → 区域 → 读取(先查出 zone id)

只给 DNS:Edit 会卡在第一步,报「找不到 zone」而不是「没有权限」,非常容易误导。创建 token 时权限区要有两行,区域资源限定到具体域名。

修复后还要清空证书库,否则失败记录会拖着:

Terminal window
rm -f /data/coolify/proxy/acme.json && touch /data/coolify/proxy/acme.json && chmod 600 /data/coolify/proxy/acme.json

然后重启 Traefik。如果还在 429 封禁期内,等 10 到 15 分钟再重启即可,不用改配置。

四、坑二:端口绑定与端口占用#

容器起不来,报

Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use

这是把 192.168.1.10:8080:8080 改成 8080:8080 引起的。宿主机上已有服务占用了 8080,只是它绑在另一个地址上,所以绑定具体 IP 时不冲突,改成全网卡就撞了。

查占用者:

Terminal window
ss -tlnp | grep :8080

两种写法的取舍:

写法好处代价
IP:端口:端口避免端口冲突,不对其他网卡暴露机器 IP 变化时容器起不来;本机 127.0.0.1 访问不到
端口:端口IP 变化无影响容易和宿主机服务冲突

家用 NAS 上 IP 一般固定,绑 IP 更安全。但要确认路由器上做了静态 IP 或 MAC 绑定,否则 DHCP 换地址会导致整个网关起不来。

五、坑三:客户端代理劫持内网域名#

最隐蔽的一个。服务端一切正常,curl 也通,唯独浏览器打不开。

原因:代理软件(v2rayN / Clash 等)把自有域名的请求送到了境外出口,境外服务器当然连不上你家的内网 IP。

为什么 curl 通而浏览器不通?curl 不读系统代理设置,浏览器读。这个差异本身就是诊断依据。

按客户端改例外#

修复方式因客户端而异:

客户端改哪里追加什么
v2rayN设置 → 系统代理设置 → 例外列表末尾;*.example.com
Clash 系配置文件 rules: 段最前面- DOMAIN-SUFFIX,example.com,DIRECT

注意两处语法不同:系统代理例外用 *.域名,Clash 规则用 DOMAIN-SUFFIX,域名。用通配符一劳永逸,以后新增子域名不用再改。

Tun 模式是个例外:它在网卡层接管全部流量,系统代理的例外列表不生效,需要另外在路由规则里排除。

有没有一劳永逸的办法#

想彻底摆脱客户端配置,唯一的办法是在路由器的 DNS 层做重写,把自有域名解析到内网 IP,这样全家所有设备自动直连。

但只要客户端开着 Tun 或 fake-ip,仍会绕过路由器 DNS——不存在 100% 免配置的方案。

六、坑四:浏览器缓存了失败的证书状态#

证书签好了,服务端 curl 干净通过,浏览器仍然标红「不安全」——HTTPS 就是打不开。

原因是浏览器在证书还没签好时访问过,缓存了坏证书和 HSTS 状态。

清理方法(Chrome / Edge),地址栏输入:

chrome://net-internals/#hsts
  1. 拉到最下面的 Delete domain security policies
  2. 输入域名,点 Delete
  3. 关掉所有该站点的标签页再重开

快速判定:开无痕窗口试一次。能开就 100% 是缓存问题。

七、坑五:容器名被面板改写#

Coolify 这类面板部署 compose 时会给容器名加 uuid 后缀(这条和另外九条一起写在在 NAS 上用 Coolify 部署的十条约定里):

tinyauth-a1b2c3d4e5f6g7h8

这会让人担心 http://tinyauth:3000 这类容器名互访失效。实际上不会——面板会把 compose 里的服务名注册成网络别名:

Terminal window
docker inspect <容器> --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}: {{$v.Aliases}}{{"\n"}}{{end}}'

输出里能看到 [tinyauth-a1b2c3d4e5f6g7h8 tinyauth],别名还在。

要记住的规则:网络别名来自 compose 里 services: 下面那个 key,不是 container_name。 所以凡是需要被其他容器按名字访问的服务,服务名必须写成你要用的那个名字,两者保持一致最省心。这也是为什么第二节第一层验证用服务名而不是容器名。

八、坑六:IP 白名单中间件与反向链路#

中间件的适用范围:

中间件内网 443隧道 / frp说明
web-default可以可以基础链,安全头 + 压缩;含 frameDeny,需要 iframe 嵌入的服务要另写
internal-only可以不行隧道流量源 IP 是 cloudflared 容器,挂了等于全放行
rate-limit不行可以内网没有 XFF,会把所有请求算作同一来源
retry可以可以POST 接口慎用,会重复提交

挂错位置就是 403#

如果内网路由挂了 internal-only,而流量是从 frp 或隧道进来的,会被直接拒绝——因为源 IP 是 frpc / cloudflared 容器的 IP,不是真实客户端 IP。

这个坑在「主域名 CNAME 到加速域名」的场景下必然踩到:访问 https://主域名:8443 时 Host 头是主域名,Traefik 匹配到 <app>-lan 那条,而它挂着 internal-only,直接 403。

同一域名两套策略:改用 ClientIP#

如果同一个域名既要内网免登录又要外网套登录,白名单中间件解决不了,要改用路由匹配阶段的 ClientIP 条件配合 priority:

# 内网来源优先匹配,免登录
- traefik.http.routers.app-lan.rule=Host(`app.example.com`) && ClientIP(`192.168.0.0/16`)
- traefik.http.routers.app-lan.priority=100
- traefik.http.routers.app-lan.middlewares=web-default@docker
# 其余落到这条,套登录
- traefik.http.routers.app-ext.rule=Host(`app.example.com`) || Host(`fast.example.com`)
- traefik.http.routers.app-ext.priority=1
- traefik.http.routers.app-ext.middlewares=web-default@docker,tinyauth@docker

区别在于:ClientIP 作用在路由匹配阶段,不匹配就落到下一条;白名单中间件作用在匹配之后,不通过就直接 403,没有退路。

九、附带发现:假的健康检查#

排查过程中发现一个长期存在但从没暴露的问题——cloudflared 的健康检查原本写成:

healthcheck:
test: ["CMD", "cloudflared", "--version"]

这只检查二进制文件存在,隧道断了照样显示 healthy。正确写法见附录的 cloudflared 配置,用它自带的 metrics ready 接口。

同类问题值得自查:健康检查探的是服务真实可用性,还是只探了进程存在?代理类容器应该通过自己探一次外网,Web 服务应该请求真实的健康接口。

十、一份可复用的排查清单#

遇到「网站打不开」,按顺序回答这几个问题:

  1. 浏览器具体报什么? 超时 / 证书警告 / 502 / 404 / 白屏——这一句往往能省掉一半工作
  2. 容器活着吗? → docker exec 从网关内部探服务
  3. 网关认这个域名吗? → curl -H "Host:" 打入口
  4. 证书签下来了吗? → 过滤掉 access log 看 acme 日志
  5. DNS 解析到哪? → 服务器和客户端各查一次
  6. 客户端能到吗? → 客户端上 curl
  7. curl 通但浏览器不通? → 代理例外、HSTS 缓存、浏览器扩展

最后一条经验:服务端 curl 返回 200 时,就该停止怀疑服务端了。这个信号非常可靠,继续在服务端翻配置只会浪费时间。

附录:完整配置文件#

上面排查用到的环境就是下面这套。排查时不需要读完,卡在哪一层再回来看对应的那份。

入口与中间件约定#

入口名端口用途
web80301 跳转到 websecure
websecure443内网直连,以及 frp 透传进来的流量
web-cf8888Cloudflare 隧道回源

路由命名约定:每个服务写两条 router 指向同一个 service,<app>-lan 走 websecure,<app>-cf 走 web-cf,走哪条由 DNS 记录决定(灰云 A 记录指 NAS 走 lan,橙云 CNAME 指隧道走 cf)。

网关:Traefik#

这是整套架构的核心。中间件库集中声明在 Traefik 自己的 labels 里,其他项目以 名字@docker 引用。

name: coolify-proxy
networks:
coolify:
external: true
traefik-net:
external: true
services:
traefik:
container_name: coolify-proxy
image: traefik:v3.7
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
environment:
- TZ=Asia/Shanghai
- CF_DNS_API_TOKEN= # 必改:权限需 Zone:DNS:Edit + Zone:Zone:Read
networks:
- coolify
- traefik-net
ports:
# 绑定内网 IP 而非全网卡:宿主机上 8080 常被占用,且避免对外暴露
- 192.168.1.10:80:80
- 192.168.1.10:443:443
- 192.168.1.10:8080:8080
- 192.168.1.10:8888:8888
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/ping || exit 1"]
interval: 4s
timeout: 2s
retries: 5
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /data/coolify/proxy/:/traefik
command:
- --ping=true
- --global.checknewversion=false
- --global.sendanonymoususage=false
- --log.level=INFO
- --accesslog=true
- --accesslog.format=json
- --api.dashboard=true
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=traefik-net
- --providers.file.directory=/traefik/dynamic/
- --providers.file.watch=true
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.web-cf.address=:8888
- --entrypoints.web-cf.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.0.0/16,172.16.0.0/12,10.0.0.0/8
- --certificatesresolvers.cloudflare.acme.dnschallenge=true
- --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
# 指定公共 DNS 解析器:本地 DNS 被污染或缓存时,传播检查会一直失败
- --certificatesresolvers.cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesresolvers.cloudflare.acme.dnschallenge.delaybeforecheck=30
- --certificatesresolvers.cloudflare.acme.email= # 必改:真实可收信地址
- --certificatesresolvers.cloudflare.acme.storage=/traefik/acme.json
labels:
- traefik.enable=true
# ---- secure-headers ----
- traefik.http.middlewares.secure-headers.headers.frameDeny=true
- traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.secure-headers.headers.referrerPolicy=strict-origin-when-cross-origin
- traefik.http.middlewares.secure-headers.headers.permissionsPolicy=camera=(), microphone=(), geolocation=()
- traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true
# ---- compress ----
- traefik.http.middlewares.compress.compress=true
# ---- internal-only:仅内网来源,只能挂 websecure 路由 ----
- traefik.http.middlewares.internal-only.ipallowlist.sourcerange=127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
# ---- rate-limit:按 XFF 第 1 跳判定,只给隧道路由用 ----
- traefik.http.middlewares.rate-limit.ratelimit.average=100
- traefik.http.middlewares.rate-limit.ratelimit.burst=200
- traefik.http.middlewares.rate-limit.ratelimit.sourcecriterion.ipstrategy.depth=1
# ---- retry ----
- traefik.http.middlewares.retry.retry.attempts=3
- traefik.http.middlewares.retry.retry.initialInterval=100ms
# ---- cors-allow ----
- traefik.http.middlewares.cors-allow.headers.accessControlAllowOriginList=*
- traefik.http.middlewares.cors-allow.headers.accessControlAllowHeaders=*
- traefik.http.middlewares.cors-allow.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS
- traefik.http.middlewares.cors-allow.headers.accessControlMaxAge=86400
# ---- no-cache ----
- traefik.http.middlewares.no-cache.headers.customResponseHeaders.Cache-Control=no-store
# ---- 基础链:所有路由都挂 ----
- traefik.http.middlewares.web-default.chain.middlewares=secure-headers@docker,compress@docker

入口层:cloudflared 与 frpc#

Cloudflare 隧道#

services:
cloudflared:
container_name: cloudflare-tunnel
image: cloudflare/cloudflared:latest
restart: unless-stopped
command:
- tunnel
- --no-autoupdate
- --protocol
- http2 # UDP 常被运营商干扰时改用 http2,网络好可去掉两行用默认 quic
- --metrics
- 127.0.0.1:2000
- run
environment:
- TZ=Asia/Shanghai
- TUNNEL_TOKEN= # 必改
healthcheck:
# 探 metrics 的 ready 接口,不要用 --version:那只检查二进制存在,隧道断了照样 healthy
test: ["CMD", "cloudflared", "tunnel", "--metrics", "127.0.0.1:2000", "ready"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
mem_limit: 256m
networks:
- traefik-net
networks:
traefik-net:
external: true

Cloudflare 后台的 Public Hostname 回源地址统一写 http://coolify-proxy:8888,靠 traefik-net 的内置 DNS 解析容器名,不经宿主机端口。

frp 客户端#

配置文件由启动脚本写出,不依赖外部挂载,整个服务一个文件搞定:

services:
frpc:
container_name: frpc
image: snowdreamtech/frpc:0.71.0-alpine
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
- FRPS_ADDR= # 必改:VPS 公网 IP
- FRPS_PORT= # 必改:frps 的 bindPort
- FRP_TOKEN= # 必改:与 frps 一致
entrypoint:
- /bin/sh
- -c
command:
- |
set -e
cat > /tmp/frpc.toml << EOF
serverAddr = "$$FRPS_ADDR"
serverPort = $$FRPS_PORT
auth.method = "token"
auth.token = "$$FRP_TOKEN"
transport.tls.enable = true
log.level = "info"
log.maxDays = 3
webServer.addr = "127.0.0.1"
webServer.port = 7400
[[proxies]]
name = "traefik-https"
type = "tcp"
localIP = "coolify-proxy"
localPort = 443
remotePort = 8443
EOF
echo "config written, connecting to $$FRPS_ADDR:$$FRPS_PORT"
exec frpc -c /tmp/frpc.toml
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7400/healthz >/dev/null 2>&1 || nc -z 127.0.0.1 7400 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
mem_limit: 128m
networks:
- traefik-net
labels:
- traefik.enable=false
networks:
traefik-net:
external: true

三个要点:

  • type = "tcp" 是原样透传,TLS 在家里的 Traefik 终止,VPS 上零证书、看不到明文
  • 脚本里的 $$ 是 compose 转义,实际执行为单个 $
  • 必须用 8443 而非 443:国内 VPS 上未备案域名访问 80/443 会被接入层拦截,非标端口不在拦截范围

认证层:tinyauth#

Traefik 的 ForwardAuth 后端,给公网入口套一层统一登录页。

services:
tinyauth:
container_name: tinyauth
image: ghcr.io/steveiliop56/tinyauth:latest
pull_policy: always
user: 0:0
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
- TINYAUTH_APPURL=https:// # 必改:登录页地址
- TINYAUTH_AUTH_USERS= # 必改:用户名:bcrypt 哈希,哈希里的 $ 双写成 $$
# CIDR 要对齐:192.168.0.0/12 是错的,实际覆盖 192.160~192.175
# 172.16/12 是 Docker 容器网段,Traefik 从容器网络过来,必须包含
- TINYAUTH_AUTH_TRUSTEDPROXIES=192.168.0.0/16,172.16.0.0/12
- TINYAUTH_AUTH_SESSIONEXPIRY=604800
- TINYAUTH_UI_TITLE= # 必改
- TINYAUTH_UI_WARNINGSENABLED=false
volumes:
- ./data:/data
healthcheck:
test: ["CMD", "tinyauth", "healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
networks:
- traefik-net
labels:
- traefik.enable=true
- traefik.docker.network=traefik-net
- traefik.http.services.tinyauth.loadbalancer.server.port=3000
# ---- 全局中间件,其他项目以 tinyauth@docker 引用 ----
# 地址里的 tinyauth 是服务名(网络别名),不是容器名,见坑五
- traefik.http.middlewares.tinyauth.forwardauth.address=http://tinyauth:3000/api/auth/traefik
- traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders=authorization
- traefik.http.middlewares.tinyauth.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.tinyauth.forwardauth.maxResponseBodySize=8192
# ---- 登录页本身不套 tinyauth,否则死循环 ----
- traefik.http.routers.tinyauth.rule=Host(``) # 必改
- traefik.http.routers.tinyauth.entrypoints=web-cf
- traefik.http.routers.tinyauth.middlewares=web-default@docker
- traefik.http.routers.tinyauth.service=tinyauth
- traefik.http.routers.tinyauth-lan.rule=Host(``) # 必改
- traefik.http.routers.tinyauth-lan.entrypoints=websecure
- traefik.http.routers.tinyauth-lan.tls.certresolver=cloudflare
- traefik.http.routers.tinyauth-lan.middlewares=web-default@docker
- traefik.http.routers.tinyauth-lan.service=tinyauth
networks:
traefik-net:
external: true

登录页最好走 Cloudflare 隧道而非 frp——因为 frp 那条带 8443 端口,而 ForwardAuth 返回的跳转地址 X-Tinyauth-Location 指向登录页域名,登录页必须在任何入口下都能到达。

session cookie 按主域名下发,端口不影响作用域,所以在隧道上登录完跳回 frp 端口的页面,cookie 依然有效。

业务服务的标准写法#

一个典型服务,两条路由指向同一个 service:

services:
myapp:
container_name: myapp
image: ghcr.io/example/myapp:latest
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
volumes:
- ./data:/data
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/health >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
networks:
- traefik-net
labels:
- traefik.enable=true
# 容器同时在多个网络时,必须指定 Traefik 该用哪个,否则可能路由到错误的网卡
- traefik.docker.network=traefik-net
- traefik.http.services.myapp.loadbalancer.server.port=8080
# ---- 内网直连:灰云 A 记录指 NAS ----
- traefik.http.routers.myapp-lan.rule=Host(``) # 必改
- traefik.http.routers.myapp-lan.entrypoints=websecure
- traefik.http.routers.myapp-lan.tls.certresolver=cloudflare
- traefik.http.routers.myapp-lan.middlewares=web-default@docker,internal-only@docker
- traefik.http.routers.myapp-lan.service=myapp
# ---- 公网隧道:橙云 CNAME 指隧道 ----
- traefik.http.routers.myapp-cf.rule=Host(``) # 必改
- traefik.http.routers.myapp-cf.entrypoints=web-cf
- traefik.http.routers.myapp-cf.middlewares=web-default@docker,tinyauth@docker
- traefik.http.routers.myapp-cf.service=myapp
networks:
traefik-net:
external: true

三个容易写错的地方:

  1. Host 规则必须用反引号,不是双引号。Host("x.com") 在 Traefik v3 里解析失败,路由直接不生效
  2. tls=true 和 tls.certresolver 不要同时写,后者已隐含前者
  3. 引用全局中间件要带 @docker 后缀,本项目内定义的中间件同理

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

自建 Traefik 网关:HTTPS 打不开的完整排查路径
https://blog.always1ov.com/posts/traefik-https-troubleshooting/
作者
薯饼
发布于
2026-09-06
许可协议
CC BY-NC-SA 4.0