ipWhiteList with excludedIPs setting will result in empty IP address when there is 1 IP address in X-Forwarded-For Header

Description

Hey guys, I noticed that when there is 1 IP address in the X-Forwarded-For header and I am using the ipStrategy.excludedIPs or ipStrategy.depth=1 setting, it will always return an empty IP address. This results in me getting a forbidden because the empty IP address is not in the IP whitelist source range.

Current Behavior

Setup

My setup is fairly simple, I am using traefik to forward my requests to the containious/whoami docker container. This docker container will show the request headers that it received from traefik.

In my ipWhitelist middleware I put "0.0.0.0/0 to white list all IPv4 addresses and 0000:0000::/0 to whitelist all IPv6 addresses.

[Issue] Accessing Traefik directly - 1 IP address in X-Forwarded-For header

When I access traefik directly (meaning no tunnels in between and I am inside the same network) I only have 1 IP address in the X-Forwarded-For header.

When I have 1 IP in the X-Forwarded-For header and I set the excludedIPs or the depth=1 setting, I get a 403 forbidden response. I am expecting a 200 response instead.

Headers

X-Forwarded-For: 192.168.1.123

Middleware Setting

    known-ips-internal:
      ipWhiteList:
        sourceRange:
          - "0.0.0.0/0"  # Whitelisting ipv4 for testing
          - "0000:0000::/0"  # Whitelisting all ipv6 for testing
        ipStrategy:
          excludedIPs:
            - 172.16.0.0/12
          # depth: 1  # this setting has the same behavior if enabled

Logs

time="2023-02-09T22:32:04-08:00" level=debug msg="Rejecting IP : empty IP address" middlewareName=known-ips-internal@file middlewareType=IPWhiteLister

[Behaving as expected] Accessing Traefik via cloudflare tunnel - 2 IP addresses in X-Forwarded-For header

When I accessing via the cloudflare tunnel I have 2 IPs in the X-Forwarded-For header. It will look like this: X-Forwarded-For: <public IP>, <cloudflared container IP>. When I use the excludedIPs setting it will select the correct IP address which is the public IP address.

Headers

X-Forwarded-For: 111.222.333.444, 172.22.0.123

Middleware Setting

    known-ips-internal:
      ipWhiteList:
        sourceRange:
          - "0.0.0.0/0"  # Whitelisting ipv4 for testing
          - "0000:0000::/0"  # Whitelisting all ipv6 for testing
        ipStrategy:
          excludedIPs:
            - 172.16.0.0/12

Logs

time="2023-02-09T22:27:15-08:00" level=debug msg="Accepting IP 111.222.333.444" middlewareName=known-ips-internal@file middlewareType=IPWhiteLister

Expected Behavior

For the scenario I listed above when I have 1 IP address in the X-Forwarded-For header and I am using a different excludedIPs, I expect it to use that IP address to evaluate for the IP whitelist.

i'm having the same issue,

i have an nginx RP that on the host and traefik as a docker container whith middleware configured to allow 192.168.0.0/16 and exludedIp of 172.20.0.1

Nginx forward the requests to traefik (and add X-Forwarded-For and X-Real-IP headers)
but the middleware reject the request

even tough there is already the header in the request

tcpdump on docker interface :

 172.20.0.1.55706 > 172.20.0.6.http: Flags [P.], cksum 0x6421 (incorrect -> 0x09bc), seq 1:3020, ack 1, win 502, options [nop,nop,TS val 1888465225 ecr 593107050], length 3019: HTTP, length: 3019
        GET /jenkins HTTP/1.0
        Host: *******
        X-Real-IP: 192.168.0.2
        X-Forwarded-For: 192.168.0.2
        X-Forwarded-Proto: http
        X-Forwarded-Login: *********
        X-Forwarded-Name: ********
        X-Forwarded-User: **********
        Connection: close

if i disable the ipStrategy i get the docker ip as remoteAddr (172.20.0.1) which is not whitelisted


i saw also this on the middleware doc

As a middleware, whitelisting happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to X-Forwarded-For during the last stages of proxying, i.e. after it has already passed through whitelisting. Therefore, during whitelisting, as the previous network hop is not yet present in X-Forwarded-For, it cannot be matched against sourceRange.

not sure if i understand the bold part

Hej,

for people (like me) not understanding the issue. See the Github Issue, where it is explained in more detail:

As described in the documentation, when no IP strategy is configured the remote address is used. This is why everything is working when the client is sending the request directly to Traefik.
When the excludedIPs option is configured, the middleware uses the X-Forwarded-For to get the client IP. As there is no hop between the client and Traefik, which will likely add the X-Forwarded-For header, Traefik will receive an empty one and return a 403.
When proxying the request to the backend, Traefik will add the X-Forwarded-For to the request, which will contain the remote address, the client IP in your example. This is why you have the X-Forwarded-For header with only one IP in the whoami response.
To be able to do what you want, you will have to define two routers, which can use different IP allow list middleware configuration in function of the X-Forwarded-For header presence.

I've solved this using a custom middleware plugin.

It automatically fetches the public CIDR IP ranges of Cloudflare/CloudFront and adjusts the X-Forwarded-For header accordingly to hold the real IP address of site visitors based on the trusted edge headers (CF-Connecting-IP and Cloudfront-Viewer-Address).

Traefik would typically still throw an error, as:

As a middleware, whitelisting happens before the actual proxying to the backend takes place. In addition, the previous network hop only gets appended to X-Forwarded-For during the last stages of proxying, i.e. after it has already passed through whitelisting. Therefore, during whitelisting, as the previous network hop is not yet present in X-Forwarded-For, it cannot be matched against sourceRange.

The middleware plugin solves this problem by appending the correct IP address to the X-Forwarded-For (XFF) header. Also and especially for requests directly hitting Traefik (from local LAN for example). Usually, for such direct requests, XFF would be empty as there is no hop in between. So an IPAllowList middleware could not validate against any IP. The plugin fixes that. Finally, Traefik itself will always append the previous hop to XFF again (cannot be prevented). For local LAN requests, this leads to two duplicate LAN IPs reported in XFF (not an issue though). So one can just use the IPAllowList middleware and an ipStrategy with depth 1 and whitelisting will work for both private and public IP addresses due to the trick.

Just enable the plugin at Traefik's static conf:

experimental:
  plugins:
    traefikwarp:
      moduleName: github.com/l4rm4nd/traefik-warp
      version: v1.1.4

and define the middlewares:

http:
  middlewares:

    specific-ipwhitelist:
      IPAllowList:
        sourceRange:
          - 145.144.64.64/32 # example public ip to whitelist
          - 127.0.0.1/32 # localhost
          - 10.0.0.0/8 # private class A
          - 172.16.0.0/12 # private class B
          - 192.168.0.0/16 # private class C
        ipStrategy:
          depth: 1

    cf-warp:
      plugin:
        traefikwarp:
          provider: cloudflare
          autoRefresh: true
          refreshInterval: 24h
          debug: false

For this to work, you just have to apply the cf-warp middleware before the specific-ipwhitelist.

Here an example of the infamous whoami container:

services:

  whoami:
    image: traefik/whoami
    container_name: whoami
    hostname: whoami
    restart: unless-stopped
    expose:
      - 80
    environment:
      - WHOAMI_NAME=whoami
      - WHOAMI_PORT_NUMBER=80
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
      - traefik.http.services.whoami.loadbalancer.server.port=80
      - traefik.http.routers.whoami.middlewares=cf-warp@file,specific-ipwhitelist@file

networks:
  proxy:
    external: true

Here the results for XFF header based on network flow:

  • Cloudflare --> Traefik --> Whoami
    • X-Forwarded-For: 145.144.64.64, 172.69.224.81
    • Result: specific-ipwhitelist validates against 145.144.64.64. Access granted.
  • Local LAN --> Traefik --> Whoami
    • X-Forwarded-For: 192.168.178.50, 192.168.178.50
    • Result: specific-ipwhitelist validates against 192.168.178.50. Access granted.

If one removes the cf-warp middleware, local LAN requests stop working and you'll receive the typical 401 Forbidden message. This happens, as Traefik cannot obtain any IP address for IPAllowList validation. Container logs will throw this error:

2025-09-29T00:39:49+02:00 DBG github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist/ip_allowlist.go:78 > Rejecting IP : empty IP address middlewareName=specific-ipwhitelist@file middlewareType=IPAllowLister

Enabling and using the middleware solves this culprit.