# Solving the Real IP Problem: A Unified ipAllowList Strategy for Traefik, Cloudflare, and Tailscale

**URL:** <https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376>\
**Category:** Networking\
**Tags:** cloudflare, tailscale, traefik, pangolin\
**Created:** [August 4, 2025, 6:14am UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376 "2025-08-04T06:14:15Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![hhf.technoloy](https://forum.hhf.technology/user_avatar/forum.hhf.technology/hhf.technoloy/32/68_2.png) [@hhf.technoloy](https://forum.hhf.technology/u/hhf.technoloy)\
**Post date:** [August 4, 2025, 6:14am UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376/1 "2025-08-04T06:14:15Z")

</div>

A detailed user guide based on a regular question for correctly configuring Traefik’s `ipAllowList` with Cloudflare and Tailscale.

The best solution is to use the **`traefik-get-real-ip`** plugin to standardize the client IP into the `X-Forwarded-For` header and then configure **`ipAllowList`** to read from that header. This creates a single, unified configuration that works for all your scenarios (Cloudflare, local access, and Tailscale).

Your core issue was correctly identifying that `ipAllowList` wasn’t using the `X-Real-Ip` header. The missing link is telling `ipAllowList` _how_ to find the IP you’ve extracted. The key is to have the plugin overwrite the `X-Forwarded-For` header and then instruct `ipAllowList` to use it with `ipStrategy`.

* * *

### ## The Core Problem: Finding the Real “From” Address

When you use proxies like Cloudflare or networks like Tailscale, your Traefik instance doesn’t see the original user’s IP address directly. It sees the IP of the last proxy that connected to it.

- **Cloudflare:** Traefik sees a Cloudflare IP. The real user IP is in a header like `CF-Connecting-IP`.

- **Tailscale:** Traefik sees the IP of the Tailscale client on the Docker network, not the user’s actual device IP within the Tailnet.

- **Local Network:** Traefik sees the user’s local IP address.

Your challenge is to make `ipAllowList` aware of the _true_ source IP, regardless of how the user is connecting.

* * *

### ## The Solution: A Two-Step Strategy

We’ll use a combination of a plugin and middleware configuration to solve this permanently.

1. **Step 1: Standardize the Client IP:** Use the `traefik-get-real-ip` plugin to inspect incoming requests. Based on the connection type (Cloudflare or direct), it will find the true client IP and place it into the `X-Forwarded-For` header.

2. **Step 2: Tell `ipAllowList` Where to Look:** Configure the `ipAllowList` middleware to use its `ipStrategy` to look at the `X-Forwarded-For` header that the plugin just prepared.

* * *

### ## Step 1: Correctly Configure the `traefik-get-real-ip` Plugin

You were on the right track with this plugin. The key is to configure it to overwrite `X-Forwarded-For` (`OverwriteXFF: true`). This makes it the single source of truth for the client IP.

Here is the corrected middleware configuration in your dynamic configuration file (e.g., `rules.yml`):

```auto
middlewares:
  realip:
    plugin:
      traefik-get-real-ip:
        # Rule 1: Handle traffic from Cloudflare
        Proxy:
          - proxyHeadername: "CF-Connecting-IP" # Check if the CF header exists
            realIP: "CF-Connecting-IP" # If so, the real IP is in this header
            OverwriteXFF: true # CRITICAL: Replace X-Forwarded-For with this IP

        # Rule 2: Handle all other traffic (Local, Tailscale, etc.)
          - proxyHeadername: "*" # A wildcard for any other request
            realIP: "RemoteAddr" # The real IP is the direct connecting IP
            OverwriteXFF: true # CRITICAL: Replace X-Forwarded-For with this IP

```

**What this does:**

- **If a request comes from Cloudflare** , the `CF-Connecting-IP` header will exist. The plugin grabs the user’s IP from it and writes it into the `X-Forwarded-For` header, overwriting anything that was there.

- **If a request comes directly (local network or Tailscale)**, the first rule is skipped. The second wildcard rule matches, takes the IP from the direct connection (`RemoteAddr`), and writes it into the `X-Forwarded-For` header.

* * *

### ## Step 2: Configure `ipAllowList` to Use the Correct IP

Now that the correct client IP is consistently in `X-Forwarded-For`, you can configure `ipAllowList` to read it. The secret is setting `ipstrategy.depth` to **`0`**.

In Traefik, `depth: 0` tells the middleware to use the **first IP** listed in the `X-Forwarded-For` header, which is exactly the one our plugin just placed there.

YAML

```auto
middlewares:
  local-only:
    ipallowlist:
      sourceRange:
        - "127.0.0.1/32" # Localhost
        - "192.168.1.0/24" # Your local network range
        - "YOUR_WAN_IP/32" # Your home's public IP for external access
        - "100.64.0.0/10" # Tailscale's CGNAT IP range for all your devices
      ipstrategy:
        depth: 0 # IMPORTANT: Use the IP our 'realip' plugin prepared

```

Why depth: 0?

Previous attempts using depth: 1 failed because it assumes a trusted proxy is always in front. By using the plugin to sanitize the IP and setting depth: 0, you create a system that doesn’t need to guess. It simply uses the IP you’ve designated as the “real” one.

* * *

### ## Putting It All Together: A Complete Example

Here is how your static (`traefik_config.yml`) and dynamic\_config (`rules.yml`) configurations would look.

#### **`traefik_config.yml` (Static Configuration)**

```auto
x-trusted-ips: &trustedIPs
        # Internal
        - 127.0.0.1/32
        - 192.168.0.0/16
        - 172.16.0.0/12
        - 192.168.0.1/24
        - 10.0.0.0/8
        - 192.168.1.229
        - 172.23.0.0/16 # Traefik
        # Cloudflare V4
        - 173.245.48.0/20
        - 103.21.244.0/22
        - 103.22.200.0/22
        - 103.31.4.0/22
        - 141.101.64.0/18
        - 108.162.192.0/18
        - 190.93.240.0/20
        - 188.114.96.0/20
        - 197.234.240.0/22
        - 198.41.128.0/17
        - 162.158.0.0/15
        - 104.16.0.0/13
        - 104.24.0.0/14
        - 172.64.0.0/13
        - 131.0.72.0/22
        # Cloudflare V6
        - 2400:cb00::/32
        - 2606:4700::/32
        - 2803:f800::/32
        - 2405:b500::/32
        - 2405:8100::/32
        - 2a06:98c0::/29
        - 2c0f:f248::/32

api:
 dashboard: true
 
ping:
  manualRouting: true 
  terminatingStatusCode: 503

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
    forwardedHeaders:
      trustedIPs: *trustedIPs 
  websecure:
    address: ":443"
    http:
      tls:
         options: default
         certResolver: letsencrypt
    forwardedHeaders:
      trustedIPs: *trustedIPs 
    proxyProtocol:
      trustedIPs: *trustedIPs 
  http3:
    address: ":443/udp"

experimental:
  plugins:
    traefik-get-real-ip:
      moduleName: "https://github.com/Paxxs/traefik-get-real-ip"
      version: "v1.0.3"

    badger:
      moduleName: "github.com/fosrl/badger"
      version: "v1.0.0"

# ... rest of your static config (providers, etc.)

```

#### **`rules.yml` (Dynamic Configuration)**

```auto
http:
  middlewares:
    # 1. The Real IP Plugin
    realip:
      plugin:
        traefik-get-real-ip:
          Proxy:
            - proxyHeadername: "CF-Connecting-IP"
              realIP: "CF-Connecting-IP"
              OverwriteXFF: true
            - proxyHeadername: "*"
              realIP: "RemoteAddr"
              OverwriteXFF: true

    # 2. The IP Allowlist Middleware
    local-only:
      ipallowlist:
        sourceRange:
          - "127.0.0.1/32"
          - "192.168.1.0/24" #<-- Your local LAN
          - "YOUR_WAN_IP/32" #<-- Your public IP
          - "100.64.0.0/10" #<-- Tailscale IPs
        ipstrategy:
          depth: 0

  routers:
    whoami:
      entryPoints:
        - "websecure"
      rule: "Host(`whoami.domain.com`)"
      middlewares:
        - realip # First, get the correct IP
        - local-only # Then, check it against the allowlist
      service: whoami
      tls: {}

# ... your services definition

```

This configuration is robust and solves all your use cases without conflict. You no longer need to switch configs or worry about which DNS server a device is using.  
[Plugin](https://plugins.traefik.io/plugins/62947309108ecc83915d7784/traefik-get-real-ip)

---

<div class="post-metadata">

**Author:** ![dennis](https://forum.hhf.technology/letter_avatar_proxy/v4/letter/d/e56c9b/32.png) [@dennis](https://forum.hhf.technology/u/dennis)\
**Post date:** [September 29, 2025, 1:41pm UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376/2 "2025-09-29T13:41:43Z")

</div>

Nice guide! The guide isn’t complete for docker scenarios, i.e. when you have traefik running in a bridged network (e.g. together with Pangolin). Only in host network mode you will see client IPs otherwise you will always see the dockers gateway IP address.

---

<div class="post-metadata">

**Author:** ![hhf.technoloy](https://forum.hhf.technology/user_avatar/forum.hhf.technology/hhf.technoloy/32/68_2.png) [@hhf.technoloy](https://forum.hhf.technology/u/hhf.technoloy)\
**Post date:** [September 29, 2025, 3:39pm UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376/3 "2025-09-29T15:39:53Z")

</div>

> [@dennis](#):
>
> Nice guide! The guide isn’t complete for docker scenarios, i.e. when you have traefik running in a bridged network (e.g. together with Pangolin). Only in host network mode you will see client IPs otherwise you will always see the dockers gateway IP address.

Thanks for pointing that out, You arere absolutely right — in a plain bridged network setup, traefik will only ever see the docker gateway IP unless you’re in host mode. That’s exactly why in the guide I leaned on the `traefik-get-real-ip` plugin + `ipStrategy` combo: it normalizes the client IP into `X-Forwarded-For` regardless of whether you’re behind Cloudflare, Pangolin, or just a docker bridge.

So even if you don’t want to run traefik in host mode, you can still recover the _real_ client IP by letting the plugin overwrite `XFF` and then telling `ipAllowList` to trust that header. That way the middleware logic works consistently across host, bridge, and overlay networks without having to compromise on isolation.

* * *

here’s a concise **Docker bridge mode**  
It shows how to recover the real client IP even when traefik is running in a bridged network (e.g alongside Pangolin), where you would otherwise only see the docker gateway IP:

```yaml
http:
  middlewares:
    # Step 1: Normalize the client IP
    realip:
      plugin:
        traefik-get-real-ip:
          Proxy:
            # Cloudflare traffic
            - proxyHeadername: "CF-Connecting-IP"
              realIP: "CF-Connecting-IP"
              OverwriteXFF: true
            # Everything else (local LAN, Pangolin, bridge)
            - proxyHeadername: "*"
              realIP: "RemoteAddr"
              OverwriteXFF: true

    # Step 2: Apply ipAllowList against the normalized IP
    local-only:
      ipAllowList:
        sourceRange:
          - "127.0.0.1/32" # localhost
          - "192.168.1.0/24" # your LAN
          - "100.64.0.0/10" # Tailscale CGNAT
          - "YOUR_WAN_IP/32" # your public IP
        ipStrategy:
          depth: 0 # trust the plugin’s XFF

  routers:
    whoami:
      entryPoints:
        - "websecure"
      rule: "Host(`whoami.example.com`)"
      middlewares:
        - realip
        - local-only
      service: whoami
      tls: {}

```

**Key points added here:**

- In bridge mode, traefik sees only the docker gateway IP by default.
- The `traefik-get-real-ip` plugin overwrites `X-Forwarded-For` with the true client IP, regardless of whether traffic comes via Cloudflare, Pangolin, or directly.
- Setting `ipStrategy.depth: 0` ensures `ipAllowList` uses the sanitized IP, not the gateway.

This way, you can keep traefik isolated in bridge mode without losing client IP visibility.

I hope this makes guide more clear. See you around 🙂

---

<div class="post-metadata">

**Author:** ![hhf.technoloy](https://forum.hhf.technology/user_avatar/forum.hhf.technology/hhf.technoloy/32/68_2.png) [@hhf.technoloy](https://forum.hhf.technology/u/hhf.technoloy)\
**Post date:** [March 30, 2026, 7:57pm UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376/4 "2026-03-30T19:57:29Z")

</div>



---

<div class="post-metadata">

**Author:** ![PANiCnz](https://forum.hhf.technology/letter_avatar_proxy/v4/letter/p/ccd318/32.png) [@PANiCnz](https://forum.hhf.technology/u/PANiCnz)\
**Post date:** [April 2, 2026, 1:47am UTC](https://forum.hhf.technology/t/solving-the-real-ip-problem-a-unified-ipallowlist-strategy-for-traefik-cloudflare-and-tailscale/3376/5 "2026-04-02T01:47:03Z")

</div>

Couple of rookie questions, is both the static and dynamic configuration required or is it one or the other? Also, where do I find rules.yml? Or I need to create it?
