Traefik RedirectScheme 中间件完整指南:将 HTTP 请求安全重定向到 HTTPS 与自定义端口
技术导读:本文系统讲解 Traefik 官方 HTTP 中间件
RedirectScheme——当请求使用的 scheme(协议,如 http/https)与目标配置不一致时,向客户端返回重定向响应。文中将以当前仓库 Traefik v3 为对象,覆盖多种配置源(结构化 YAML/TOML、Docker/Swarm Labels、Marathon 等 Tags、Kubernetes CRD Middleware)的完整写法、scheme/permanent/port三个参数的精确语义,并结合 redirect_scheme.go 与 redirect_scheme_test.go 深入解析其如何依赖X-Forwarded-Proto判定 scheme、如何生成 301/302/307/308 响应以及默认端口自动归一化等底层原理。读完本文,你可以独立完成"全站 HTTP→HTTPS 强制跳转""多端口重定向"等生产级配置,并能准确预判在不同反向代理场景下的行为。
一、RedirectScheme 是什么
RedirectScheme 是 Traefik HTTP 路由链中的一种重定向类中间件。它的核心职责非常简单:当客户端请求所使用的 scheme(协议)与配置中期望的 scheme 不同时,把请求重定向到期望的 scheme。
也就是说,它并不会把所有请求一律跳转,而是先做"协议是否一致"的判断:
- 请求已是目标 scheme(例如配置为
https且请求本身就是 HTTPS),则直接放行到下一个处理器; - 请求 scheme 与目标不一致(例如配置为
https但请求是 HTTP),则返回 3xx 重定向,并把Location指向 scheme 与(可选的)端口重写后的同路径 URL。
最典型的生产场景是全站强制 HTTPS:将监听在 :80 入口点的 HTTP 请求统一重定向到 HTTPS 入口点,从而避免明文传输。此外,它也能在多个协议端口共存的场景下(如 http:8080 与 https:8443)把流量引导到正确的端点。
从源码结构看,重定向中间件家族共包含两类成员,均位于 pkg/middlewares/redirect 目录:
| 中间件 | 依据 | 实现文件 |
|---|---|---|
RedirectScheme |
依据 scheme / 端口 | redirect_scheme.go |
RedirectRegex |
依据 正则表达式 | redirect_regex.go |
其中 RedirectScheme 在服务启动装配时由 pkg/server/middleware/middlewares.go#L322 调用 redirect.NewRedirectScheme(ctx, next, *config.RedirectScheme, middlewareName) 创建,属于动态配置中声明即生效的中间件。
二、部署前置条件:反向代理链中的信任关系
文档(见 redirectscheme.md)在正文开头就给出了一个重要的前置警告:
当 Traefik 前面还有至少一个反向代理时,这个"最后一跳"的反向代理必须被 Traefik 视为**可信(trusted)**来源。
原因在于 RedirectScheme 判断客户端实际使用的协议,依赖的是从上游转发的 X-Forwarded 系列请求头(详见下文"scheme 判定原理")。如果中间的代理不被信任,Traefik 会清理掉这最后一跳传入的 X-Forwarded-Proto 等转发头(以防止伪造),此时 RedirectScheme 便无从得知真实协议,重定向行为就会失效或产生错误结果。
如何把前置代理加入可信来源,请参阅入口点配置文档中的可信代理(Forwarded Headers / trusted IPs)相关小节:entrypoints.md 配置选项。
三、Configuration Examples:五种配置源完整示例
原文档给出了四段直接可用的配置示例(YAML、TOML、Labels、Tags),外加 Kubernetes CRD 写法,此处完整保留并补充说明每一种写法的适用 Provider。
1. 结构化配置(YAML)
适用于以静态文件、Docker/K8s 等 Provider 注入的动态配置:
# Redirect to https
http:
middlewares:
test-redirectscheme:
redirectScheme:
scheme: https
permanent: true
2. 结构化配置(TOML)
# Redirect to https
[http.middlewares]
[http.middlewares.test-redirectscheme.redirectScheme]
scheme = "https"
permanent = true
3. Docker / Swarm 容器标签(Labels)
Labels 写法适用于 Docker、Docker Swarm Provider,在容器或服务上以 traefik.http.middlewares.<name>.<option>=<value> 形式声明:
# Redirect to https
labels:
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.permanent=true"
4. Marathon 等 Tags
Tags 写法适用于 Marathon、Rancher、Consul Catalog 等以键值 Tags 提供元数据的 Provider,键名规则与 Labels 相同:
// Redirect to https
{
// ...
"Tags": [
"traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https",
"traefik.http.middlewares.test-redirectscheme.redirectscheme.permanent=true"
]
}
5. Kubernetes CRD Middleware
在 Kubernetes 中推荐通过自定义资源 Middleware(traefik.io/v1alpha1)声明,再用注解挂载到 IngressRoute:
# Redirect to https
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-redirectscheme
spec:
redirectScheme:
scheme: https
permanent: true
注意 K8s Labels 写法中 key 的大小写:容器标签场景下中间件名称段统一使用小写
redirectscheme,而 YAML/TOML/CRD 中字段使用驼峰redirectScheme。从源码看,Labels/Tags 这类键值对最终会通过 pkg/config/label 的解析器映射到结构化字段,因此键名遵循 Provider 的标签命名约定(traefik.http.middlewares.*),而 CRD 的spec.redirectScheme则由 pkg/provider/kubernetes/crd/kubernetes.go#L323 直接拷贝到动态配置结构体中。
6. 把中间件挂载到路由器上使用
声明中间件本身并不会生效,必须通过路由器的 middlewares 引用它才会进入请求处理链。以最常用的容器 Labels 为例,完整的"中间件 + 路由器 + HTTPS 入口点强制跳转"组合如下:
labels:
# 1) 声明中间件
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
# 2) 在路由器上引用(example.com 的 HTTP 请求将被跳转到 https://example.com/...)
- "traefik.http.routers.my-router.rule=Host(`example.com`)"
- "traefik.http.routers.my-router.entrypoints=web"
- "traefik.http.routers.my-router.middlewares=redirect-to-https"
对 Kubernetes 的 IngressRoute 场景,则在 routes[].middlewares 中按名称引用已创建的 Middleware 资源。
四、Configuration Options:参数详解
原文档的参数表如下,这是该中间件全部对外暴露的配置项:
| Field | Description | Default | Required |
|---|---|---|---|
scheme |
新 URL 使用的协议(scheme) | "" |
Yes |
permanent |
是否启用永久重定向(对应 301/308) | false |
No |
port |
新 URL 使用的端口。注意必须传字符串,而不是数值 | "" |
No |
三个参数的精确语义与取值约束说明如下。
scheme(必填)
- 目标协议,决定请求应被重定向到
http、https(源码常量定义于 redirect.go),测试用例还覆盖了wss作为目标的场景。 - 必填:构造函数中若
len(conf.Scheme) == 0,直接返回错误"you must provide a target scheme",中间件创建失败(见 redirect_scheme.go#L32-L34)。
permanent(可选,默认 false)
控制重定向的"永久性",与返回状态码的映射关系如下(详见下文源码分析):
| permanent | 请求方法 | 返回状态码 |
|---|---|---|
false(临时) |
GET | 302 Found |
false(临时) |
其他方法(如 POST) | 307 Temporary Redirect |
true(永久) |
GET | 301 Moved Permanently |
true(永久) |
其他方法(如 POST) | 308 Permanent Redirect |
状态码会因请求方法不同而变化,这一点很容易被忽略:permanent 对 GET 请求给出 301/302,对非 GET 请求则分别给出 308/307,以避免 301/302 被某些客户端改写为 GET 而破坏 POST 语义。
port(可选,默认 "")
- 重定向目标 URL 的端口。文档特别强调:必须写成字符串(如
"8443"),而不是数值(如8443),这与动态配置字段声明为string类型一致。 - 若不设置,则目标 URL 保留原请求 Host 中的端口,并遵循默认端口归一化规则(当 http 配 80、https 配 443 时不携带端口,见下文)。
- 该端口还可用于"改端口"跳转,例如把
http://foo:8080永久重定向到https://foo:8443。
值得说明的内部细节:结构体 RedirectScheme 实际上还有第四个字段
ForcePermanentRedirect,但它在 YAML/TOML/Labels 等所有外部配置载体上均被标记为忽略(json/toml/yaml/label/file/kv 全部为"-"),仅作为内部字段供 Kubernetes ingress-nginx Provider 使用——当 Ingress 上开启 SSL 重定向注解时,translator.go#L396-L402 会构造RedirectScheme{Scheme: "https", ForcePermanentRedirect: true},强制所有方法一律返回 308(详见第五节状态码逻辑)。
五、源码级原理:请求如何被判定并重定向
1. 重定向目标 URL 的组装
NewRedirectScheme(redirect_scheme.go#L27-L57)在创建中间件时就固定了两件事:
- 目标替换模板为
conf.Scheme + "://" + ${2} + port + ${4}。这里的${2}与${4}引用的是正则捕获组; - 用于匹配原请求 URL 的正则
uriPattern:
即依次捕获:协议前缀(^(https?:\/\/)?(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$$1)、Host(支持 IPv6 字面量[...]与普通域名/IP,$2)、端口($3)、以及其余路径部分($4)。
其中端口归一化规则在此完成(redirect_scheme.go#L36-L39):
port := ""
if len(conf.Port) > 0 && !(conf.Scheme == schemeHTTP && conf.Port == "80" || conf.Scheme == schemeHTTPS && conf.Port == "443") {
port = ":" + conf.Port
}
含义是:scheme 为 http 且 port 为 80,或 scheme 为 https 且 port 为 443 时,该端口属于协议默认端口,会被省略、不写进目标 URL(即 http→80、https→443 都被视为"无端口")。例如配置 {scheme: http, port: "80"},访问 http://foo:80 时新旧 URL 完全一致,中间件不会触发重定向——测试用例 to HTTP 80 与 to HTTPS 443 断言返回 200 OK 正是这一逻辑的验证。
2. 当前请求 scheme 的判定:X-Forwarded-Proto 是关键
中间件必须重建"客户端眼中看到的原始 URL",再与新目标比较。clientRequestURL(redirect_scheme.go#L59-L107)按照如下优先级确定 scheme:
- 从
req.RequestURI中解析显式携带的协议前缀; - 若
req.TLS != nil(Traefik 本地已终止 TLS),判定为https; - 若请求头存在
X-Forwarded-Proto,则以它为准,并做 WebSocket 语义归并(见下); - 最终还会把默认端口从 URL 中剥离(http:80 / https:443 不展示)。
关于第 3 步有一个很关键的实现细节。由于前一跳代理可能把连接升级场景(WebSocket)的协议写成 ws/wss,而本中间件只在 HTTP(S) 语境中使用,因此 redirect_scheme.go#L87-L101 会做如下转换:
X-Forwarded-Proto为http或ws→ 按http处理;- 为
https或wss→ 按https处理; - 其他未知值 → 记录 Debug 日志
Invalid X-Forwarded-Proto并忽略,回落到原有判定。
这解释了第二节信任警告的必要性:如果 Traefik 不信任其前置代理并清除了 X-Forwarded-Proto,那么真实协议将无法被感知。测试用例 HTTP to HTTPS, with X-Forwarded-Proto to HTTPS 与 ...to wss 均断言不重定向、返回 200——因为携带的转发头已表明请求"实质上已是 https",无需再跳转,这能有效避免在代理链中形成无限重定向循环。
3. 状态码决策与重定向执行
真正的重定向执行位于通用实现 redirect.go。ServeHTTP 的处理流程为:
- 通过
rawURL(req)(即上面的clientRequestURL)重建原始 URL; - 正则不匹配 → 直接放行到 next handler;
- 用
replacement模板做正则替换得到newURL; - 若
newURL != oldURL→ 交给moveHandler写Location头并返回对应状态码; - 若替换后与原来一致 → 原地把
req.URL替换为解析后的新 URL 并继续向后传递(这正是"已是目标 scheme 则放行"的落点)。
状态码决策逻辑见 moveHandler.ServeHTTP(redirect.go#L92-L115),其规则与第四节参数表中的映射完全对应:
status := http.StatusFound // 302
if req.Method != http.MethodGet {
status = http.StatusTemporaryRedirect // 307
}
if m.permanent {
status = http.StatusMovedPermanently // 301
if req.Method != http.MethodGet {
status = http.StatusPermanentRedirect // 308
}
}
if m.statusCode != nil { // ForcePermanentRedirect 时强制 308
status = *m.statusCode
}
当 ForcePermanentRedirect=true(即 ingress-nginx 的 SSL 重定向场景)时,permanentRedirectCode 被固定为 308 Permanent Redirect,无论 GET 还是其他方法都返回 308,以严格保留请求方法与请求体语义。测试用例 HTTP to HTTPS with explicit 308 status code 与 ...for GET request 验证了这一行为。
六、行为边界与典型场景验证(测试用例归纳)
redirect_scheme_test.go 用约 30 组表格化用例固化了中间件行为,以下行为边界均有测试背书,可在实际排障时作为"预期行为清单":
| 场景 | 配置 | 期望行为 |
|---|---|---|
缺省 scheme |
{} |
创建中间件报错,handler 为 nil |
| HTTP→HTTPS | scheme: https |
302,Location: https://foo |
| 已是 HTTPS | scheme: https,请求带 X-Forwarded-Proto: https |
不重定向,200 |
代理头为 ws/未知值 |
scheme: https,X-Forwarded-Proto: ws/bar |
判定为 http → 302 跳 https |
代理头为 wss |
scheme: https,X-Forwarded-Proto: wss |
判定为 https → 不重定向 |
| 改端口跳转 | scheme: https, port: "8443" |
http://foo:8000 → https://foo:8443(302) |
| 永久重定向 | scheme: https, port: "8443", permanent: true |
http://foo → https://foo:8443(301) |
| 默认端口归一 | scheme: http, port: "80"(或 https/443) |
与现 URL 相同 → 不重定向,200 |
| IPv6 支持 | scheme: https |
http://[::1]:80 → https://[::1](保留方括号与地址) |
| WebSocket 目标 | scheme: wss, port: "9443" |
http://foo → wss://foo:9443(302) |
几点从用例中可以总结出的生产经验:
- 不要担心"多一跳代理后死循环":只要前置代理正确设置
X-Forwarded-Proto: https,即便用户通过 http URL 进入,只要 Traefik 接收到的已是 TLS 连接且转发头为 https,中间件也会放行;但反过来,若信任配置缺失导致转发头被清理,就会退化为不断跳转。 - 端口跳转时会改写原端口:
https://foo:8000在scheme: https(不带 port)的配置下会被归一为https://foo,即目标 URL 的端口不是"自动沿用原端口",而是按配置(或默认端口剥离)重算。 - URL 中其他部分(路径、查询串)保持不变:重定向只作用于 scheme/host/port 前缀,正则
$4捕获的路径部分被原样拼接回目标 URL。 - IPv6 地址能够正确处理:正则中的
\[[\w:.]+\]分支专门处理[::1]这类字面量,跳转后不会丢失方括号。
七、与本仓库其他能力的关系与延伸阅读
- 若你需要比 scheme 更复杂的重写规则(例如同时改写路径、host),应使用同一家族的 RedirectRegex 中间件,其实现位于 redirect_regex.go。
- RedirectScheme 的响应不会被再次套娃处理:它属于服务端返回的 3xx 响应,若你希望搭配"错误页/自定义响应体",可结合 customerrors 等机制,但最典型的组合仍是"HTTPS 入口点只挂 RedirectScheme,真实服务放在 HTTPS 路由上"。
- 想让 Traefik 自动签发证书配合本中间件实现"零人工配置"的 HTTPS,可结合 Let's Encrypt 配置 与 HTTPS 路由相关文档使用。
- 关于中间件在请求链中的顺序编排、以及
chain中间件把多个中间件打包复用,可阅读 HTTP 中间件 overview。
综上,RedirectScheme 虽是一个"小而专"的中间件,但其对 X-Forwarded-Proto 的依赖、对 301/302/307/308 的方法感知选择、对默认端口的归一化处理,共同决定了它在反向代理拓扑与 Kubernetes 场景下的精确行为。把握上述源码级细节,即可在生产中写出可靠且不会产生重定向风暴的强制 HTTPS 规则。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00