Traefik introduced an important entrypoint hardening option called underscoreHeadersStrategy in the v3.6.20 and v3.7.6 releases.

It controls how Traefik handles inbound HTTP headers whose names contain underscores before a request reaches routing and middleware processing.

Traefik Migration Documentation - Traefik
Learn the steps needed to migrate to new Traefik Proxy v3 versions. Read the technical documentation.

The option supports three modes:

  • keep is the default and forwards underscore-containing headers unchanged.
  • delete silently removes every request header containing an underscore.
  • reject returns 400 Bad Request when a request contains such a header.

At first glance, this may appear to be a minor normalization detail. In practice, it closes a dangerous header spoofing opportunity at the reverse-proxy boundary.

Why underscore headers are risky

Underscores are valid in HTTP header names. However, Go canonicalizes header names around dashes, not underscores. As a result, X-Auth-User and X_Auth_User remain separate header keys inside Go’s header map.

This matters when Traefik middleware manages a trusted identity header. For example, a ForwardAuth middleware may set or remove X-Auth-User before a request is forwarded to an application.

A malicious client could instead provide:

X_Auth_User: administrator

The middleware sees and controls the dash-based X-Auth-User header, while the underscore variant X_Auth_User can remain untouched.

Many backend environments normalize both forms to the same variable name. CGI, WSGI, PHP, and some NGINX or application-server configurations may expose both headers as HTTP_X_AUTH_USER. The backend can therefore read an attacker-controlled identity value even though Traefik correctly handled the dash-based header.

CVE-2026-33433 demonstrated how header canonicalization differences can lead to identity spoofing in proxy-based authentication flows.

The vulnerability affected BasicAuth and DigestAuth configurations using non-canonical headerField values, allowing an authenticated attacker to inject a competing header representation that the backend could prioritize.

BasicAuth/DigestAuth Identity Spoofing via Non-Canonical headerField
## Summary There is a potential vulnerability in Traefik’s Basic and Digest authentication middlewares when `headerField` is configured with a non-canonical HTTP header name. An authenticated…

The newly introduced field underscoreHeadersStrategy addresses a related defensive gap. Even where a middleware deletes or overwrites X-Auth-User, Go’s Header.Del("X-Auth-User") does not treat X_Auth_User as the same key. Without additional filtering, the underscore variant can survive and reach a backend that may treat the two forms as equivalent.

CVE-2024-45410 demonstrated how dangerous it can be when a backend application trusts headers added by a reverse proxy.

In affected Traefik versions, an HTTP/1.1 client could abuse the Connection header to mark selected forwarding headers, such as X-Forwarded-Host, as hop-by-hop. Traefik would then remove the header before the request reached the backend. The issue affected Traefik versions up to v2.11.8 and v3.1.2 and was fixed in v2.11.9 and v3.1.3.

HTTP client can remove the X-Forwarded headers
### Impact There is a vulnerability in Traefik that allows the client to remove the X-Forwarded headers (except the header X-Forwarded-For). ### Patches - https://github.com/traefik/traefi…

The attack chain relies on two separate behaviors:

  1. A crafted Connection header causes Traefik to remove the trusted dash-form header, such as X-Forwarded-Host. This was a CVE vulnerability but is nowadays fixed.
  2. An attacker-controlled underscore variant, such as X_Forwarded_Host, remains in the request.
  3. Python-based environments including WSGI applications may normalize dashes and underscores to the same variable name.
  4. Once the trusted header is missing, the backend can read the attacker-controlled underscore variant instead.

The Shadow Corp CTF challenge provides a practical local demonstration. A Flask backend uses X-Forwarded-Host to determine whether a request should be treated as originating from a trusted source. As Flask and other Python-based servers commonly receive headers through WSGI-style environment variables, dash and underscore variants can be normalized to the same value. An attacker can therefore remove the trusted X-Forwarded-Host header through the Connection header issue and supply an attacker-controlled X_Forwarded_Host value that the backend interprets as the trusted header.

GitHub - jphetphoumy/traefik-CVE-2024-45410-poc: A proof of concept of traefik CVE to understand the impact
A proof of concept of traefik CVE to understand the impact - jphetphoumy/traefik-CVE-2024-45410-poc

This is where underscoreHeadersStrategy provides valuable defense in depth. Setting the strategy to delete removes X_Forwarded_Host before it reaches the backend, preventing it from acting as a fallback or alias for the removed X-Forwarded-Host header. Using reject is even stricter and denies requests that contain underscore-based headers altogether.

For entrypoints that expose applications relying on trusted proxy headers, use delete unless underscore-based request headers are explicitly required:

entryPoints:
  websecure:
    address: ":443"
    http:
      underscoreHeadersStrategy: delete

Using delete provides a practical compatibility-friendly safeguard. Suspicious alias headers are removed, while the request can continue normally. Use reject where strict request validation is preferred and clients should be notified immediately with a 400 Bad Request response.

The default keep mode preserves existing behavior, but it is not recommended for entry points that front CGI, WSGI, PHP, or other backends that normalize underscores and dashes identically. Treat proxy-managed identity and authorization headers as a trust boundary, and remove ambiguous header aliases before they reach the application.