diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f616b6a41..58d6bc9285 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## [v2.11.44](https://github.com/traefik/traefik/tree/v2.11.44) (2026-04-29)
+[All Commits](https://github.com/traefik/traefik/compare/v2.11.43...v2.11.44)
+
+**Bug fixes:**
+- **[middleware]** Add errorRequestHeaders option to Errors middleware ([#13034](https://github.com/traefik/traefik/pull/13034) @gndz07)
+- **[acme]** Bump github.com/go-acme/lego to v4.35.2 ([#13052](https://github.com/traefik/traefik/pull/13052) @mmatur)
+
+**Misc:**
+- Make FLAGS Make variable usable ([#13009](https://github.com/traefik/traefik/pull/13009) @twz123)
+
## [v3.6.14](https://github.com/traefik/traefik/tree/v3.6.14) (2026-04-22)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.13...v3.6.14)
diff --git a/Makefile b/Makefile
index 7f361cfb25..283fce3300 100644
--- a/Makefile
+++ b/Makefile
@@ -58,7 +58,7 @@ generate:
#? binary: Build the binary
binary: generate-webui dist
@echo SHA: $(VERSION) $(CODENAME) $(DATE)
- CGO_ENABLED=0 GOGC=${GOGC} GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS[*]} -ldflags "-s -w \
+ CGO_ENABLED=0 GOGC=${GOGC} GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS} -ldflags "-s -w \
-X github.com/traefik/traefik/v3/pkg/version.Version=$(VERSION) \
-X github.com/traefik/traefik/v3/pkg/version.Codename=$(CODENAME) \
-X github.com/traefik/traefik/v3/pkg/version.BuildDate=$(DATE)" \
diff --git a/docs/content/migrate/v3.md b/docs/content/migrate/v3.md
index bf1ee1c8c8..ddc616a5d5 100644
--- a/docs/content/migrate/v3.md
+++ b/docs/content/migrate/v3.md
@@ -9,6 +9,15 @@ This guide provides detailed migration steps for upgrading between different Tra
---
+## v3.6.15
+
+In `v3.6.15`, a new `errorRequestHeaders` option has been added to the Errors middleware.
+
+By default, the behavior is unchanged: all original request headers are forwarded to the error page service.
+If the error page service is in a separate trust domain, consider using `errorRequestHeaders` to restrict which headers are forwarded.
+
+Please check out the [Error Pages](../reference/routing-configuration/http/middlewares/errorpages.md#errorRequestHeaders) middleware documentation for more details.
+
## v3.6.14
### Kubernetes CRD: Chain middleware and `allowCrossNamespace`
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index eb525ee3b4..d57ef4bc58 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -30,6 +30,7 @@
- "traefik.http.middlewares.middleware08.digestauth.removeheader=true"
- "traefik.http.middlewares.middleware08.digestauth.users=foobar, foobar"
- "traefik.http.middlewares.middleware08.digestauth.usersfile=foobar"
+- "traefik.http.middlewares.middleware09.errors.errorrequestheaders=foobar, foobar"
- "traefik.http.middlewares.middleware09.errors.query=foobar"
- "traefik.http.middlewares.middleware09.errors.service=foobar"
- "traefik.http.middlewares.middleware09.errors.status=foobar, foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index 4b51878b07..dad9c61eb1 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -193,6 +193,7 @@
status = ["foobar", "foobar"]
service = "foobar"
query = "foobar"
+ errorRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware09.errors.statusRewrites]
name0 = 42
name1 = 42
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index a5b70b3ec2..8810e1da17 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -208,6 +208,9 @@ http:
name1: 42
service: foobar
query: foobar
+ errorRequestHeaders:
+ - foobar
+ - foobar
Middleware10:
forwardAuth:
address: foobar
diff --git a/docs/content/reference/routing-configuration/http/middlewares/errorpages.md b/docs/content/reference/routing-configuration/http/middlewares/errorpages.md
index 9f95b0acd4..94a3b83887 100644
--- a/docs/content/reference/routing-configuration/http/middlewares/errorpages.md
+++ b/docs/content/reference/routing-configuration/http/middlewares/errorpages.md
@@ -129,3 +129,12 @@ The table below lists all the available variables and their associated values.
| `{status}` | The response status code. |
| `{originalStatus}` | The original response status code, if it has been modified by the `statusRewrites` option. |
| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL.|
+
+### `errorRequestHeaders`
+
+Defines the list of original request headers forwarded to the error page service.
+
+By default (`errorRequestHeaders` not set), all request headers — including authentication material such as `Authorization` and `Cookie` — are forwarded.
+If the error page service is in a separate trust domain, use this option to restrict which headers cross the service boundary.
+
+Set to an explicit list to forward only those headers, or set to an empty list (`errorRequestHeaders: []`) to forward no headers.
diff --git a/docs/content/reference/routing-configuration/other-providers/file.toml b/docs/content/reference/routing-configuration/other-providers/file.toml
index 8fffdd5929..e02b09a309 100644
--- a/docs/content/reference/routing-configuration/other-providers/file.toml
+++ b/docs/content/reference/routing-configuration/other-providers/file.toml
@@ -195,6 +195,7 @@
status = ["foobar", "foobar"]
service = "foobar"
query = "foobar"
+ errorRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware09.errors.statusRewrites]
name0 = 42
name1 = 42
diff --git a/docs/content/reference/routing-configuration/other-providers/file.yaml b/docs/content/reference/routing-configuration/other-providers/file.yaml
index 0f9c5f5318..ae91fd329a 100644
--- a/docs/content/reference/routing-configuration/other-providers/file.yaml
+++ b/docs/content/reference/routing-configuration/other-providers/file.yaml
@@ -214,6 +214,9 @@ http:
name1: 42
service: foobar
query: foobar
+ errorRequestHeaders:
+ - foobar
+ - foobar
Middleware10:
forwardAuth:
address: foobar
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index 037dbe393e..befe299542 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -236,6 +236,10 @@ type ErrorPage struct {
// The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
// The {url} variable can be used in order to insert the escaped request URL.
Query string `json:"query,omitempty" toml:"query,omitempty" yaml:"query,omitempty" export:"true"`
+ // ErrorRequestHeaders defines the list of request headers forwarded to the error page service.
+ // When nil (not set), all original request headers are forwarded.
+ // Set to an empty list to forward no headers, or list specific headers to forward only those.
+ ErrorRequestHeaders []string `json:"errorRequestHeaders,omitempty" toml:"errorRequestHeaders,omitempty" yaml:"errorRequestHeaders,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index a14c1e97cb..f25e40158e 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -321,6 +321,11 @@ func (in *ErrorPage) DeepCopyInto(out *ErrorPage) {
(*out)[key] = val
}
}
+ if in.ErrorRequestHeaders != nil {
+ in, out := &in.ErrorRequestHeaders, &out.ErrorRequestHeaders
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
return
}
diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go
index 193ad43ec6..3617ca8155 100644
--- a/pkg/middlewares/customerrors/custom_errors.go
+++ b/pkg/middlewares/customerrors/custom_errors.go
@@ -37,6 +37,7 @@ type customErrors struct {
backendHandler http.Handler
httpCodeRanges types.HTTPCodeRanges
backendQuery string
+ requestHeaders []string
statusRewrites []statusRewrite
}
@@ -79,6 +80,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
backendHandler: backend,
httpCodeRanges: httpCodeRanges,
backendQuery: config.Query,
+ requestHeaders: config.ErrorRequestHeaders,
statusRewrites: statusRewrites,
}, nil
}
@@ -145,7 +147,15 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}
- utils.CopyHeaders(pageReq.Header, req.Header)
+ if c.requestHeaders != nil {
+ for _, header := range c.requestHeaders {
+ if values := req.Header.Values(header); len(values) > 0 {
+ pageReq.Header[http.CanonicalHeaderKey(header)] = values
+ }
+ }
+ } else {
+ utils.CopyHeaders(pageReq.Header, req.Header)
+ }
c.backendHandler.ServeHTTP(newCodeModifier(rw, code),
pageReq.WithContext(req.Context()))
}
diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go
index ec8eff1536..bf18840199 100644
--- a/pkg/middlewares/customerrors/custom_errors_test.go
+++ b/pkg/middlewares/customerrors/custom_errors_test.go
@@ -23,6 +23,7 @@ func TestHandler(t *testing.T) {
backendCode int
backendErrorHandler http.HandlerFunc
validate func(t *testing.T, recorder *httptest.ResponseRecorder)
+ requestHeaders map[string]string
}{
{
desc: "no error",
@@ -154,6 +155,60 @@ func TestHandler(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "My 503 page.")
},
},
+ {
+ desc: "forward all headers by default",
+ errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}},
+ requestHeaders: map[string]string{
+ "X-Request-Id": "trace-abc",
+ "Authorization": "Bearer secret",
+ },
+ backendCode: http.StatusServiceUnavailable,
+ backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
+ fmt.Fprintln(w, r.Header.Get("Authorization"))
+ }),
+ validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
+ t.Helper()
+ assert.Contains(t, recorder.Body.String(), "trace-abc")
+ assert.Contains(t, recorder.Body.String(), "Bearer secret")
+ },
+ },
+ {
+ desc: "forward only allowlisted headers",
+ errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}, ErrorRequestHeaders: []string{"X-Request-Id"}},
+ requestHeaders: map[string]string{
+ "X-Request-Id": "trace-abc",
+ "Authorization": "Bearer secret",
+ },
+ backendCode: http.StatusServiceUnavailable,
+ backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
+ fmt.Fprintln(w, r.Header.Get("Authorization"))
+ }),
+ validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
+ t.Helper()
+ assert.Contains(t, recorder.Body.String(), "trace-abc")
+ assert.NotContains(t, recorder.Body.String(), "Bearer secret")
+ },
+ },
+ {
+ desc: "forward no headers",
+ errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}, ErrorRequestHeaders: []string{}},
+ requestHeaders: map[string]string{
+ "X-Request-Id": "trace-abc",
+ "Authorization": "Bearer secret",
+ },
+ backendCode: http.StatusServiceUnavailable,
+ backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
+ fmt.Fprintln(w, r.Header.Get("Authorization"))
+ }),
+ validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
+ t.Helper()
+ assert.NotContains(t, recorder.Body.String(), "trace-abc")
+ assert.NotContains(t, recorder.Body.String(), "Bearer secret")
+ },
+ },
}
for _, test := range testCases {
@@ -174,6 +229,9 @@ func TestHandler(t *testing.T) {
require.NoError(t, err)
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil)
+ for k, v := range test.requestHeaders {
+ req.Header.Set(k, v)
+ }
// Client like browser and curl will issue a relative HTTP request, which not have a host and scheme in the URL. But the http.NewRequest will set them automatically.
req.URL.Host = ""