A Traefik middleware plugin to automatically obtain the real visitor's IP address if Traefik is run behind a Content Delivery Network (CDN) like Cloudflare or CloudFront. Fully automated by fetching and regularly updating the official CDN CIDR IP addresses from official HTTP endpoints.
[!CAUTION] This plugin will not help logging the visitor's real IP address in Traefik's access log.
For such cases, use
forwardedHeaders.trustedIPsat your entrypoints. Example here.
🔒 Trust-gated by edge IP
📥 Provider headers supported
CF-Connecting-IP, CF-Visitor (scheme)Cloudfront-Viewer-Address (IP:port / [IPv6]:port)📤 Standard proxy headers emitted
X-Real-IP to the visitor IPX-Forwarded-For (preserves chain, only when trusted)X-Forwarded-Proto to http/https🧹 Header hygiene
X-Forwarded-For, X-Real-IP, X-Forwarded-Proto, Forwarded) before setting trusted values.🏷️ Neutral telemetry
X-Warp-Trusted = yes|no and X-Warp-Provider = cloudflare|cloudfront|unknown for downstream logging/metrics.🔁 Auto CIDR refresh (enabled per default)
TraefikWarp automatically fetches the latest Cloudflare and AWS CloudFront IPv4/IPv6 CIDR ranges from their official endpoints and builds an in-memory allowlist. On every middleware request, it validates the remote socket IP against this allowlist. Only when it matches, the middleware trusts the specific provider's headers to resolve the visitor’s real IP address. It then normalizes X-Forwarded-Proto to http or https and sets X-Forwarded-For, X-Real-IP, X-Warp-Trusted, and X-Warp-Provider. The resolved address is then propagated to backend services and recorded in the backend service's access logs. CDN CIDR IP addresses are regularly refreshed (default every 12h). If the ranges cannot be fetched, the middleware stays safe as no public ranges are trusted per default. You may extend the allowlist of trusted IPs by using trustIp.
The custom HTTP headers X-Warp-Trusted and X-Warp-Provider are forwarded to your backends to document TraefikWarp’s decision. X-Warp-Trusted is yes when the socket IP matched the allowlist (so provider headers were trusted) and no otherwise. X-Warp-Provider identifies, which provider's network the socket IP matched - e.g. cloudflare, cloudfront or unknown. These headers are informational for logging, metrics, and policy decisions. They don’t affect how TraefikWarp validates or rewrites request headers.
| Setting | Type | Required | Allowed values | Description |
|---|---|---|---|---|
provider | string | yes | auto, cloudfront, cloudflare | Selects which edge network to trust. auto = decide by the socket IP. |
trustip | map | no | per-provider CIDR list | Extends the built-in allowlists. Keys: cloudflare, cloudfront. |
autoRefresh | bool | no | true / false | Periodically refresh Cloudflare/CloudFront CIDR ranges. Default: true. |
refreshInterval | string | no | Go duration (e.g. 5m, 1h, 12h) | Interval for auto refresh, used only when autoRefresh is true. Default: 12h. |
debug | bool | no | true / false | Emit Traefik-style logs from the plugin (e.g., CIDR loads/refresh). Default: false. |
Note:
trustIpextends (does not replace) the official ranges. Avoid0.0.0.0/0or::/0.
experimental:plugins:traefikwarp:moduleName: github.com/l4rm4nd/traefik-warpversion: v1.1.5 # <-- ensure using a latest release
http:middlewares:warp-auto:plugin:traefikwarp:provider: autoautoRefresh: truerefreshInterval: 24hdebug: false# trustIp: # optional: extend allow-lists# cloudflare:# - "198.51.100.0/24"# cloudfront:# - "203.0.113.0/24"warp-cloudfront:plugin:traefikwarp:provider: cloudfrontwarp-cloudflare:plugin:traefikwarp:provider: cloudflarerouters:my-router-auto:rule: PathPrefix(`/`) && (Host(`cloudfront.example.com`) || Host(`cloudflare.example.com`))entryPoints: [web] # or your entrypoint name(s)service: svc-whoamimiddlewares:- warp-automy-router-cloudfront:rule: PathPrefix(`/`) && Host(`cloudfront.example.com`)entryPoints: [web]service: svc-whoamimiddlewares:- warp-cloudfrontmy-router-cloudflare:rule: PathPrefix(`/`) && Host(`cloudflare.example.com`)entryPoints: [web]service: svc-whoamimiddlewares:- warp-cloudflareservices:svc-whoami:loadBalancer:servers:- url:http://10.10.10.10:5000
You can also enable this plugin during development as follows:
Clone this repository:
cd /tmpgit clone https://github.com/l4rm4nd/traefik-warp
Then mount the plugin dir as docker bind mount volume in Traefik's compose:
volumes:- /tmp/traefik-warp:/plugins-local/src/github.com/l4rm4nd/traefik-warp:ro
Enable the local plugin in Traefik's static config:
experimental:localPlugins:traefikwarp:moduleName: github.com/l4rm4nd/traefik-warp
Finally, define the middleware in Traefik's dynamic config:
http:middlewares:warp-auto:plugin:traefikwarp:provider: autoautoRefresh: truerefreshInterval: 1mdebug: true
And test it using a whoami container:
services:whoami:image: traefik/whoamicontainer_name: whoamihostname: whoamirestart: unless-stoppedexpose:- 80environment:- WHOAMI_NAME=whoami- WHOAMI_PORT_NUMBER=80networks:- proxy # change to your traefik networklabels:- traefik.enable=true- traefik.docker.network=proxy # change to your traefik network- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)- traefik.http.services.whoami.loadbalancer.server.port=80- traefik.http.routers.whoami.middlewares=warp-auto@file # change to correct middleware name
The plugin will emit debug messages if you have enabled debug:
2025-09-27T03:59:58+02:00 INF warp: CIDRs loaded cf=22 cfn=194 middleware=warp-auto@file module=github.com/l4rm4nd/traefik-warp plugin=plugin-traefikwarp2025-09-27T04:01:04+02:00 INF warp: refreshed CIDRs cf=22 cfn=194 module=github.com/l4rm4nd/traefik-warp plugin=plugin-traefikwarp
Original code and idea from https://github.com/kyaxcorp/traefikdisolver