Private address on ClientHost after external request

Issue

I am running Traefik on Docker as the main entrypoint for our web applicactions on some self-managed servers, but on some of them I've noticed that the ClientHost that is registered on logs comes from an private address (e.g. 172.17.3.1) when a request coming from outside the network is made. This issue happens at seemly random intervals, with random clients, but only on some of the servers (it seems to occur on our OVH servers, but not on our Azure ones). As there is no other header field to which we can depend to know the client's IP address (Traefik is the entrypoint to our network), we are unable to rely on the IP that downstream services see.

Context

Traefik and all downstream services are running on a single server, all running on Docker containers and connected by Docker networks, with Traefik as the entrypoint for external connections (via ports 80 and 443) via an external Docker network. When the issue arises, the registered private IP always has the same private IP as the Docker network gateway used by the container. There is no additional reverse-proxy or API Gateway upstream to Traefik (unless Docker is doing something which I am not aware of?).

What I've tried

The same complete setup was deployed to two servers, one on OVH and one on Azure, on separate VMs (the former is way older than the latter). On both of them, I've completely removed Docker components and did the same deployment steps on both of them (via scripting), but the issue persisted on on the former, but not on the latter (at least from my observations).

What version of Traefik are you using?

Version: 3.3.7
Codename: saintnectaire
Go version: go1.23.8
Built: 2025-05-05T08:44:28Z
OS/Arch: linux/amd64

What is your environment & configuration?

# traefik.yaml

entryPoints:
  http:
    address: :80
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "172.17.0.0/16"
        - "172.18.0.0/16"
        - "172.22.0.0/16"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
          permanent: true

  https:
    address: :443
    forwardedHeaders:
      trustedIPs:
        - "172.17.0.0/16"
        - "172.18.0.0/16"
        - "172.22.0.0/16"
    http:
      tls:
        options: default
        certResolver: default

global:
  sendAnonymousUsage: false

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: gfe
  file:
    filename: /etc/traefik/dynamic_conf.yaml


certificatesResolvers:
  default:
    acme:
      email: [redacted]
      storage: /etc/traefik/acme.json
      tlsChallenge: {}


ping: {}


api:
  basePath: /traefik
  dashboard: true
  insecure: false

log:
  level: INFO

accessLog:
  format: json
  fields:
    defaultMode: keep
    headers:
      defaultMode: keep
      names:
        Authorization: redact


metrics:
  prometheus:
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true
    entryPoint: metrics
# dynamic_conf.yaml

tls:
  options:
    default:
      minVersion: VersionTLS12
      sniStrict: true

providers:
  docker:
    network: gfe
# docker-compose.yaml

services:
  traefik:
    image: traefik:3.3
    container_name: traefik
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./config/traefik:/etc/traefik
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - gfe
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik-dashboard.entrypoints=https
      - traefik.http.routers.traefik-dashboard.tls.certresolver=internal
      - traefik.http.routers.traefik-dashboard.rule=[redacted]
      - traefik.http.routers.traefik-dashboard.service=api@internal
      - traefik.http.routers.traefik-dashboard.middlewares=auth
      - traefik.http.middlewares.auth.basicauth.users=[redacted]


networks:
  gfe:
    external: true

The gfe network, besides the entrypoint, it the first point of contact of downstream services accessible from outside the server. It is a bridge network.

If applicable, please paste the log output in DEBUG level

{
    "ClientAddr": "172.17.3.1:46898",
    "ClientHost": "172.17.3.1",
    "ClientPort": "46898",
    "ClientUsername": "-",
    "DownstreamContentSize": 462,
    "DownstreamStatus": 200,
    "Duration": 7403918,
    "OriginContentSize": 462,
    "OriginDuration": 6390752,
    "OriginStatus": 200,
    "Overhead": 1013166,
    "RequestAddr": "[redacted]",
    "RequestContentSize": 0,
    "RequestCount": 4004,
    "RequestHost": "[redacted]",
    "RequestMethod": "GET",
    "RequestPath": "/",
    "RequestPort": "-",
    "RequestProtocol": "HTTP/1.1",
    "RequestScheme": "https",
    "RetryAttempts": 0,
    "RouterName": "[redacted]",
    "ServiceAddr": "172.17.3.5:80",
    "ServiceName": "[redacted]",
    "ServiceURL": "http://172.17.3.5:80",
    "StartLocal": "2025-09-22T05:51:43.380363985Z",
    "StartUTC": "2025-09-22T05:51:43.380363985Z",
    "TLSCipher": "TLS_AES_128_GCM_SHA256",
    "TLSVersion": "1.3",
    "downstream_Content-Encoding": "gzip",
    "downstream_Content-Length": "462",
    "downstream_Content-Type": "text/html; charset=utf-8",
    "downstream_Date": "Mon, 22 Sep 2025 05:51:43 GMT",
    "downstream_Etag": "\"dcv4h224uccgma-gzip\"",
    "downstream_Last-Modified": "Wed, 17 Sep 2025 13:50:31 GMT",
    "downstream_Vary": "Accept-Encoding",
    "entryPointName": "https",
    "level": "info",
    "msg": "",
    "origin_Content-Encoding": "gzip",
    "origin_Content-Length": "462",
    "origin_Content-Type": "text/html; charset=utf-8",
    "origin_Date": "Mon, 22 Sep 2025 05:51:43 GMT",
    "origin_Etag": "\"dcv4h224uccgma-gzip\"",
    "origin_Last-Modified": "Wed, 17 Sep 2025 13:50:31 GMT",
    "origin_Vary": "Accept-Encoding",
    "request_Accept": "*/*",
    "request_Accept-Encoding": "gzip",
    "request_User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "request_X-Forwarded-Host": "[redacted]",
    "request_X-Forwarded-Port": "443",
    "request_X-Forwarded-Proto": "https",
    "request_X-Forwarded-Server": "5a2569af892f",
    "request_X-Real-Ip": "172.17.3.1",
    "time": "2025-09-22T05:51:43Z"
}

Use the headers supplied by Traefik:

X-Forwarded-For: 12.34.56.78
X-Forwarded-Host: example.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: server51
X-Real-Ip: 12.34.56.78

If the real IP is not there, then you probably have a routing layer in between (load balancer?) and you need to ensure you add its IP to the trusted IPs for the headers to be carried over.

Docker would only create its own ingress network with IPs if you are running Docker Swarm, but this can be circumvented:

services:
  traefik:
    image: traefik:v3.5
    ports:
      # listen on host ports without ingress network
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host

Ok, I think I found out the issue. After some additional search on the Traefik forum, I found an existing answer with a scenario similar to mine, which than made me realize that one the differences between my two servers is that one also has support for IPv6 connections. So I tried enabling IPv6 support to the `gfe` network, and it seemed to work (at least for now).

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.