Note: Despite the repository name
traefik-lowercase-response-headers, this plugin now supports both request and response header modification. The name is kept for backward compatibility.
A Traefik middleware plugin that rewrites HTTP request and/or response header names before they are sent to the upstream or client.
It can enforce specific header casing or convert all headers to lowercase.
The plugin supports two modes:
toLower: true: convert all header names to lowercase.headers: [...]: rewrite only specific header names to the exact spellings you provide.modifyRequest: true: apply header case enforcement to request headers sent to upstream.modifyResponse: true: apply header case enforcement to response headers sent to client (default).unsafeManagedHeaders: true: allow rewriting managed response headers such asContent-Type,Content-Length, andDate.
You can use either mode alone or both together:
- explicit entries in
headerswin for matching headers toLowerapplies to all other headers- by default, both request and response headers are modified
- set
modifyRequest: falseto skip request headers, ormodifyResponse: falseto skip response headers - request headers have no managed header restrictions
Examples:
X-Powered-By->x-powered-bywhen listed asx-powered-byinheaders- managed response headers such as
Content-Type,Content-Length, andDatestay canonical unlessunsafeManagedHeaders: true - request headers can be modified independently of response headers
The plugin configuration schema is:
toLower: true
headers:
- x-powered-by
- x-trace-id
modifyRequest: true
modifyResponse: true
unsafeManagedHeaders: falseFields:
toLower: lowercase header names not explicitly mapped.headers: exact header names to force. Matching is case-insensitive, output spelling is exactly what you provide.modifyRequest: apply header case enforcement to request headers (default:true).modifyResponse: apply header case enforcement to response headers (default:true).unsafeManagedHeaders: opt in to rewriting managed response headers (Content-Type,Content-Length,Date). This may produce duplicate wire headers in HTTP/1.x. Only applies to response headers.
Note: By default, both request and response headers are modified (modifyRequest: true, modifyResponse: true). To modify only one direction, explicitly set the other to false.
Static configuration:
experimental:
plugins:
lowercaseResponseHeaders:
moduleName: github.com/k8stooling/traefik-lowercase-response-headers
version: v0.1.0Dynamic configuration:
http:
middlewares:
lowercase-response-headers:
plugin:
lowercaseResponseHeaders:
toLower: true
headers:
- x-powered-by
- x-trace-id
modifyRequest: true
modifyResponse: true
unsafeManagedHeaders: falseExample for request header modification only:
http:
middlewares:
lowercase-request-headers:
plugin:
lowercaseResponseHeaders:
toLower: true
modifyRequest: true
modifyResponse: falseExample for both request and response:
http:
middlewares:
lowercase-all-headers:
plugin:
lowercaseResponseHeaders:
toLower: true
modifyRequest: true
modifyResponse: trueExample combining toLower with specific headers (lowercase everything EXCEPT specified headers):
http:
middlewares:
mixed-case-enforcement:
plugin:
lowercaseResponseHeaders:
toLower: true # Lowercase everything...
headers: # ...EXCEPT these headers with enforced casing
- X-Trace-Id
- X-Request-ID
modifyRequest: true
modifyResponse: trueResult of the mixed example:
X-Trace-Id→ staysX-Trace-Id(enforced from headers list)X-Request-ID→ staysX-Request-ID(enforced from headers list)Authorization→ becomesauthorization(lowercased)X-Custom-Header→ becomesx-custom-header(lowercased)
Attach the middleware to any router as usual.
For local development, Traefik must load the repository as a local plugin.
Static configuration:
experimental:
localPlugins:
lowercaseResponseHeaders:
moduleName: github.com/k8stooling/traefik-lowercase-response-headersThe plugin source must be mounted or placed at:
./plugins-local/src/github.com/k8stooling/traefik-lowercase-response-headers
This repository includes a ready-to-run local harness under hack/local-test.sh and hack/traefik-local/traefik.yml.
The local harness starts:
go-httpbinon host port8080- Traefik on host port
29080 - Traefik dashboard on host port
29081
It exposes three routes:
/plain/anything: forwards tohttpbinwithout the plugin/lower/anything: forwards tohttpbinwith the plugin enabled/headers/anything: rewrites onlyAccess-Control-Allow-OriginandX-Selected-Header
Start it:
./hack/local-test.sh upStop it:
./hack/local-test.sh downCheck container state:
./hack/local-test.sh statusTail Traefik logs:
./hack/local-test.sh logsRun both sample requests:
./hack/local-test.sh curlManual checks:
curl -si http://127.0.0.1:29080/plain/anything | sed -n '1,20p'
curl -si http://127.0.0.1:29080/lower/anything | sed -n '1,20p'
curl -si http://127.0.0.1:29080/headers/anything | sed -n '1,20p'Expected result:
/plainreturns normal Go canonical header casing/lowerreturns lowercased response header names/headersonly rewritesaccess-control-allow-originandx-selected-header
- The plugin rewrites response headers in a wrapped
http.ResponseWriter. - If no configuration is active, it takes the fast path and passes through without wrapping.
- Header rewrite rules are built once during plugin construction.
- It does not attempt to support WebSocket hijacking.
- Managed headers such as
Content-Type,Content-Length, andDateare intentionally left canonical because Go or Traefik may synthesize those after middleware processing, which otherwise causes duplicates. unsafeManagedHeaders: truedisables that protection and rewrites managed headers too.
Run tests:
go test ./...The current tests verify:
- full lowercasing of downstream response headers
- explicit mapping of selected response header names
- request headers are not modified
- plugin.go: plugin implementation
- plugin_test.go: unit tests
- .traefik.yml: Traefik plugin catalog manifest
- hack/local-test.sh: local Podman test harness
- Response header names in Go are normally canonicalized by standard HTTP server code paths; this plugin exists for environments where downstream client-facing casing still matters.
- HTTP intermediaries and clients may still normalize or reinterpret header names.
- This plugin focuses only on header name rewriting, not header value mutation.