Skip to content

k8stooling/traefik-lowercase-response-headers

Repository files navigation

Traefik Header Case Enforcement

Build Status

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.

What it does

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 as Content-Type, Content-Length, and Date.

You can use either mode alone or both together:

  • explicit entries in headers win for matching headers
  • toLower applies to all other headers
  • by default, both request and response headers are modified
  • set modifyRequest: false to skip request headers, or modifyResponse: false to skip response headers
  • request headers have no managed header restrictions

Examples:

  • X-Powered-By -> x-powered-by when listed as x-powered-by in headers
  • managed response headers such as Content-Type, Content-Length, and Date stay canonical unless unsafeManagedHeaders: true
  • request headers can be modified independently of response headers

Configuration

The plugin configuration schema is:

toLower: true
headers:
  - x-powered-by
  - x-trace-id
modifyRequest: true
modifyResponse: true
unsafeManagedHeaders: false

Fields:

  • 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.

Remote plugin usage

Static configuration:

experimental:
  plugins:
    lowercaseResponseHeaders:
      moduleName: github.com/k8stooling/traefik-lowercase-response-headers
      version: v0.1.0

Dynamic configuration:

http:
  middlewares:
    lowercase-response-headers:
      plugin:
        lowercaseResponseHeaders:
          toLower: true
          headers:
            - x-powered-by
            - x-trace-id
          modifyRequest: true
          modifyResponse: true
          unsafeManagedHeaders: false

Example for request header modification only:

http:
  middlewares:
    lowercase-request-headers:
      plugin:
        lowercaseResponseHeaders:
          toLower: true
          modifyRequest: true
          modifyResponse: false

Example for both request and response:

http:
  middlewares:
    lowercase-all-headers:
      plugin:
        lowercaseResponseHeaders:
          toLower: true
          modifyRequest: true
          modifyResponse: true

Example 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: true

Result of the mixed example:

  • X-Trace-Id → stays X-Trace-Id (enforced from headers list)
  • X-Request-ID → stays X-Request-ID (enforced from headers list)
  • Authorization → becomes authorization (lowercased)
  • X-Custom-Header → becomes x-custom-header (lowercased)

Attach the middleware to any router as usual.

Local plugin usage

For local development, Traefik must load the repository as a local plugin.

Static configuration:

experimental:
  localPlugins:
    lowercaseResponseHeaders:
      moduleName: github.com/k8stooling/traefik-lowercase-response-headers

The 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.

Local test harness

The local harness starts:

  • go-httpbin on host port 8080
  • Traefik on host port 29080
  • Traefik dashboard on host port 29081

It exposes three routes:

  • /plain/anything: forwards to httpbin without the plugin
  • /lower/anything: forwards to httpbin with the plugin enabled
  • /headers/anything: rewrites only Access-Control-Allow-Origin and X-Selected-Header

Start it:

./hack/local-test.sh up

Stop it:

./hack/local-test.sh down

Check container state:

./hack/local-test.sh status

Tail Traefik logs:

./hack/local-test.sh logs

Run both sample requests:

./hack/local-test.sh curl

Manual 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:

  • /plain returns normal Go canonical header casing
  • /lower returns lowercased response header names
  • /headers only rewrites access-control-allow-origin and x-selected-header

Implementation notes

  • 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, and Date are intentionally left canonical because Go or Traefik may synthesize those after middleware processing, which otherwise causes duplicates.
  • unsafeManagedHeaders: true disables that protection and rewrites managed headers too.

Development

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

Repository files

Limitations

  • 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.

About

A traefik plugin to convert response headers to lowercase

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors