Traefik ErrorPages(errors)中间件技术指南:按状态码范围返回自定义错误页面
Traefik 的 errors(ErrorPages)中间件允许你根据配置的 HTTP 状态码区间,拦截后端返回的错误响应,改由指定的错误处理服务(error service)返回自定义错误页面。本文以 Traefik 官方参考文档为核心,结合本仓库源码、集成测试与 Fixture 配置,系统讲解该中间件的全部配置项、五种声明方式的完整示例、占位符变量的语义,以及它在内部实现中如何捕获状态码、重写状态码与转发请求头,帮助你直接落地可运行的自定义错误页方案。
中间件行为概述
errors 中间件的作用非常聚焦:当后端服务返回的状态码落在你配置的区间内时,不再把原始响应(通常是后端默认的错误页面)直接转发给客户端,而是改向另一个服务请求一个错误页面,并把该页面返回给客户端;如果状态码不在配置区间内,则原始响应被透传,不经过任何缓冲与改写。
该中间件是 Traefik 官方文档中列为内建参考的 HTTP 中间件之一,正式文档见 errorpages.md,其 Go 实现位于 custom_errors.go,对应动态配置结构体
ErrorPage定义于 middlewares.go。
典型应用场景:
- 为 5xx 系列错误提供统一的品牌化错误页面,而非服务商默认的调试页;
- 将不友好的状态码(如 418、502)重写为对访问者更友好的 404 或 500 展示;
- 通过
{status}、{originalStatus}、{url}占位符动态生成错误页 URL,按状态码返回不同页面。
配置示例:五种声明方式
errors 中间件在动态配置层有统一的字段结构,可在不同入口按对应语法声明。以下示例为「对 5XX 状态码返回自定义错误页面,但排除 502 与 504」的完整配置。
结构化配置(YAML)
# Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504
http:
middlewares:
test-errors:
errors:
status:
- "500"
- "501"
- "503"
- "505-599"
statusRewrites:
"418": 404
"502-504": 500
service: error-handler-service
query: "/{status}.html"
services:
# ... definition of the error-handler-service
结构化配置(TOML)
# Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504
[http.middlewares]
[http.middlewares.test-errors.errors]
status = ["500","501","503","505-599"]
service = "error-handler-service"
query = "/{status}.html"
[http.middlewares.test-errors.errors.statusRewrites]
"418" = 404
"502-504" = 500
[http.services]
# ... definition of the error-handler-service
Docker / Swarm Labels 声明
# Dynamic Custom Error Page for 5XX Status Code
labels:
- "traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599"
- "traefik.http.middlewares.test-errors.errors.statusRewrites.418=404"
- "traefik.http.middlewares.test-errors.errors.statusRewrites.502-504=500"
- "traefik.http.middlewares.test-errors.errors.service=error-handler-service"
- "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
Marathon Tags 声明
// Dynamic Custom Error Page for 5XX Status Code excluding 502 and 504
{
// ...
"Tags": [
"traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599",
"traefik.http.middlewares.test-errors.errors.statusRewrites.418=404",
"traefik.http.middlewares.test-errors.errors.statusRewrites.502-504=500",
"traefik.http.middlewares.test-errors.errors.service=error-handler-service",
"traefik.http.middlewares.test-errors.errors.query=/{status}.html"
]
}
注意:在 Labels 与 Tags 中,多个状态码与区间以逗号分隔写在同一字符串中(如 500,501,503,505-599)。
Kubernetes Middleware CRD 声明
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-errors
spec:
errors:
status:
- "500"
- "501"
- "503"
- "505-599"
statusRewrites:
"418": 404
"502-504": 500
query: /{status}.html
service:
name: error-handler-service
port: 80
在 Kubernetes 场景下,该 CRD 由 Router(如 IngressRoute)通过 middlewares 字段引用,中间件随后会把错误页请求转发给其 spec.errors.service 指向的 Kubernetes Service。
配置项总览
ErrorPage 中间件各字段的官方定义如下(来自 errorpages.md 的配置项表):
| 字段 | 描述 | 默认值 | 必填 |
|---|---|---|---|
status |
定义哪些状态码或状态码区间应触发错误页。状态码区间是闭区间(505-599 会对 505 到 599 之间、包含两端点的每个码生效)。可以只写单个状态码(500),可以写多个以逗号分隔的状态码(500,502),可以用短横线连接两个码表示区间(505-599),也可以组合使用(404,418,505-599) |
[] |
否 |
statusRewrites |
可选的状态码映射(将原状态码改写为另一个状态码)。详见后文 statusRewrites 小节 |
[] |
否 |
service |
负责提供错误页的服务(即「错误服务」)。详见后文 service 小节 |
"" |
否 |
query |
错误页的请求路径(由 service 提供服务)。详见后文 query 小节 |
"" |
否 |
errorRequestHeaders |
定义要转发给错误页服务的原始请求头列表。详见后文 errorRequestHeaders 小节 |
[] |
否 |
从配置结构体的源码注释(middlewares.go)可以进一步确认每个字段的语义,其中 Status 以字符串切片形式保存(以容纳 500-599 这类区间写法),StatusRewrites 以 map[string]int 保存(区间写法作为 key,目标状态码作为 value)。
status:触发范围怎么写
status 决定「哪些响应需要被错误页接管」,支持四种写法并可自由混用:
- 单个状态码:
"500"; - 逗号分隔的多个状态码:
"500,502"; - 闭区间:
"505-599"(两端都包含,505、599都会触发); - 组合写法:
"404,418,505-599"。
区间最终会在运行时被解析为 [low, high] 的整数对。在 http_code_range.go 的 NewHTTPCodeRanges 实现中,每个以短横线分隔的块会被拆成两个数字,没有短横线的单个码会被自动补全为同值的区间(如 503 → [503, 503]),随后由 Contains 通过「状态码大于等于 low 且小于等于 high」来判断是否命中。这段代码同样复用于 statusRewrites 的区间解析——所以映射的区间语法与 status 完全一致。
service 与 HostHeader 的协作语义
service 字段指定提供错误页的后端服务。在错误触发后,中间件会构造一个指向该服务的新请求来抓取错误页。
默认行为:客户端请求的 Host 头会被转发给所配置的错误服务。
若希望转发的是错误服务 URL 对应的 Host 值,则需要在错误服务的负载均衡器上把 passHostHeader 选项设置为 false。该选项的完整说明见 service.md。
!> Kubernetes 注意事项:在 Kubernetes 中(例如 IngressRoute 引用时)指定 service,需要引用 Kubernetes Service 资源的 name、namespace 与 port。例如 my-service.my-namespace@kubernetescrd(或 my-service.my-namespace@kubernetescrd:80),可以确保请求正确到达目标服务与端口。
statusRewrites:改写触发状态码
statusRewrites 是可选的状态码映射,用于把「中间件实际看到的状态码」改写为另一个状态码后再返回给客户端。
例如后端返回 418 时,你可能希望它对外表现为 404;又如把 502-504 整体改写为 500。它同样支持单个状态码、逗号分隔的多个码、以及闭区间,区间语法与 status 选项完全一致。
statusRewrites:
"418": 404 # 418 → 404
"502-504": 500 # 502/503/504 → 500
从源码实现看(custom_errors.go),ServeHTTP 在捕获到命中 status 的状态码后,会先按配置顺序检查 statusRewrites,一旦命中区间即改写并 break(即先匹配到谁用谁)。改写后的码用于生成 query 中的 {status} 值;改写前的原始码则作为 {originalStatus} 保留。
query:错误页 URL 的占位符变量
query 定义向错误服务请求的路径(最终 URL 为 http://<req.Host> + query 形式)。query 中允许插入三类变量,中间件会在请求前完成替换:
| 变量 | 替换值 |
|---|---|
{status} |
响应状态码(若被 statusRewrites 改写,则为改写后的码) |
{originalStatus} |
原始响应状态码(仅当被 statusRewrites 改写后才与原码不同) |
{url} |
经过 转义 处理的原始请求 URL |
例如 query: "/{status}.html",当捕获到 500 时,会向错误服务请求 /500.html;query: "/?status={status}&url={url}" 则会把转义后的原始 URL 作为查询参数一并带给错误页。
源码中的替换逻辑位于 custom_errors.go:三个占位符分别由 strconv.Itoa(code)、strconv.Itoa(originalCode) 与 url.QueryEscape(orig.String()) 填充,其中 orig 是依据原始请求的 scheme、host、path、raw query 与 fragment 重建的 URL。对应单测在 custom_errors_test.go 的 query replacement 与 full query replacement 用例中给出了明确的期望值,例如替换后的完整请求会形如 /?status=503&url=http%3A%2F%2Flocalhost%2Ftest%3Ffoo%3Dbar%26baz%3Dbuz。
errorRequestHeaders:控制跨服务边界的请求头
errorRequestHeaders 定义需要转发给错误页服务的原始请求头白名单:
- 不设置(默认):所有原始请求头都会被转发,其中包含
Authorization、Cookie等认证信息; - 设置为显式列表:仅转发列出的请求头,例如
errorRequestHeaders: ["X-Request-Id"]; - 设置为空列表(
errorRequestHeaders: []):不转发任何请求头。
如果你的错误页服务处于独立的信任域(trust domain),应使用该选项把跨服务边界的请求头收窄,避免认证材料外泄。这三种语义在源码中由 requestHeaders 字段是否为 nil 区分(custom_errors.go):nil 表示「全部转发」,走 utils.CopyHeaders 全量拷贝;否则仅遍历白名单、用 http.CanonicalHeaderKey 规范键名后拷贝命中的头。
源码透视:错误页是怎么被“抓”出来并替换的
errors 中间件的核心实现位于 pkg/middlewares/customerrors/custom_errors.go,整体流程如下:
-
构建期(
New):把config.Status与每个statusRewrites的 key 通过types.NewHTTPCodeRanges解析成整数区间(解析失败即返回错误),并通过serviceBuilder.BuildHTTP(ctx, config.Service)构建错误服务的后端处理器(custom_errors.go)。可见错误服务必须在配置期即可解析,若错误服务不可用,请求会直接放行给下一个处理器并记录错误日志。 -
运行时拦截(
ServeHTTP):请求先交给codeCatcher(一个包装后的http.ResponseWriter),再调用next.ServeHTTP让下游正常处理(custom_errors.go)。codeCatcher的作用是尽早判断响应码是否命中配置区间:命中时丢弃响应体内容(不写回客户端),等待后续替换;未命中时则把头部与数据直接透传给原始客户端,无缓冲(见其WriteHeader/Write实现,custom_errors.go)。它同时实现了http.Hijacker与http.Flusher,兼容 WebSocket、流式响应等场景。 -
命中后的替换:若
codeCatcher.isFilteredCode()为真,先执行statusRewrites改写逻辑;随后基于query模板生成错误页请求(newRequest构造 GET 请求,custom_errors.go),按errorRequestHeaders规则带上请求头后,把请求交给错误服务后端。默认模式下,错误服务的响应会包一层codeModifier(custom_errors.go),它强制以错误码(改写后的码)作为最终返回给客户端的响应码,即使错误服务自身返回了 200;同时错误服务返回的响应头与 1xx 信息头也会被透传。 -
附加的内部模式:配置结构体中还有一个仅对 IngressNGINX Provider 暴露、不对普通用户开放的内部字段
NginxHeaders(middlewares.go)。当携带该字段时,中间件会切换为 Nginx 风格:额外设置X-Code、X-Format、X-Original-Uri、X-Request-ID等请求头,并保留错误服务自身返回的状态码而不强制改写。对应行为在测试中均有覆盖(custom_errors_test.go 的 nginx headers 系列用例)。
集成测试与 Fixture:可直接参考的完整配置
仓库在 integration 层提供了一套可直接参考的端到端用例。其 TOML Fixture 展示了中间件与普通服务、错误服务的组合方式,例如 simple.toml:
[http.middlewares]
[http.middlewares.error.errors]
status = ["500-502", "503-599"]
service = "error"
query = "/50x.html"
[http.services]
[http.services.error.loadBalancer]
[[http.services.error.loadBalancer.servers]]
url = "http://{{.Server2}}:80"
而在 statusRewrites.toml 中,则同时验证了区间捕获与改写映射的组合:
[http.middlewares.error.errors]
status = ["500-502", "503-599", "418"]
service = "error"
query = "/50x.html"
[http.middlewares.error.errors.statusRewrites]
"418" = 400
"500-502" = 404
上述文件与 integration/error_pages_test.go 配合,在真实 Traefik 实例中验证了「命中区间→请求错误页→改写状态码→返回自定义页面」的完整链路,可作为生产配置的样板。
小结
errors 中间件是 Traefik 动态配置体系中实现「统一错误呈现」的专用组件。把握住三个要点即可灵活使用:
status决定“抓谁”:闭区间、多码、组合写法都可以,且与statusRewrites的区间语法一致;query决定“问谁要页面、要哪个页面”:配合{status}、{originalStatus}、{url}占位符,可实现按状态码、按原始请求动态取页;service+passHostHeader+errorRequestHeaders决定“跨边界时如何呈现与保密”:默认转发全部请求头,跨信任域时务必用白名单或空列表收窄。
配合官方文档 errorpages.md 与源码 custom_errors.go、单测 custom_errors_test.go,你可以在 Docker、Kubernetes、Marathon、TOML/YAML 文件等任意 Provider 上落地一致的自定义错误页体验。
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 StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00