Host header only being considered from within the server host

Conventions:

  • Client: The host trying to access whoami service through traefik
  • Client IP: 10.0.0.80
  • Server: The host running traefik and whoami services
  • Server IP: 10.0.0.15

While following the initial Getting Started tutorial, I created a simple docker-compose.yml containing an almost identical configuration file except for the Host router rule of the whoami service, that has been replaced with whoami.tld, representing a custom domain to be used locally:

version: '3'
services:
  reverse-proxy:
    # The official v2 Traefik docker image
    image: traefik:v2.10
    # Enables the web UI and tells Traefik to listen to docker
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
  whoami:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.tld`)"

In theory this service should be accessible for any Clients in the same network as the Server by the curl command using the Host header, but in practice it is only possible to access the whoami service running the curl command from within the Server itself.

curl -H Host:whoami.tld http://10.0.0.15/

The above command returns 301 Moved Permanently for every Client (and opens the traefik dashboard if accessed from the browser), while in the Server it returns the expected output containing the data of the whoami service.

I tried creating an exclusive network for traefik in docker but no different results. Also performed a packet capture with tcpdump to trace the HTTP request and the results was different for each one of the hosts.

# Format:
# network_interface src > dst

# Packet flow when running curl from the *Server*
br-e31f1e726d9e 10.0.0.15.39076  > 172.29.0.3.80
veth651aab0     10.0.0.15.39076  > 172.29.0.3.80
veth651aab0     172.29.0.3.58268 > 172.29.0.2.80
vethba1f5e8     172.29.0.3.58268 > 172.29.0.2.80
vethba1f5e8     172.29.0.2.80    > 172.29.0.3.58268
veth651aab0     172.29.0.2.80    > 172.29.0.3.58268
veth651aab0     172.29.0.3.80    > 10.0.0.15.39076
br-e31f1e726d9e 172.29.0.3.80    > 10.0.0.15.39076

--

# Packet flow when running curl from the *Client*
eth0            10.0.0.80.45088  > 10.0.0.15.80
br-e31f1e726d9e 172.29.0.1.54682 > 172.29.0.3.8080
veth651aab0     172.29.0.1.54682 > 172.29.0.3.8080
veth651aab0     172.29.0.3.8080  > 172.29.0.1.54682
br-e31f1e726d9e 172.29.0.3.8080  > 172.29.0.1.54682
eth0            10.0.0.15.80     > 10.0.0.80.45088

Apparently the package is being directly redirected to port 8080 when the request comes from the eth0 network interface.

iptables rules (just to make sure if this could be the reason of this weird behavior)
Chain INPUT (policy ACCEPT)                                                                                                                       
target     prot opt source               destination                                                                                              
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80                                                                          
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80                                                                          
                                                                                                                                                  
Chain FORWARD (policy DROP)                                                                                                                       
target     prot opt source               destination                                                                                              
DOCKER-USER  all  --  0.0.0.0/0            0.0.0.0/0                                                                                              
DOCKER-ISOLATION-STAGE-1  all  --  0.0.0.0/0            0.0.0.0/0                                                                                 
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED                                                         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0                                                                                                
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0                                                                                                
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0                                                                                                
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED                                                         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0                                                                                                
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain DOCKER (4 references)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            172.25.0.2           tcp dpt:80
ACCEPT     tcp  --  0.0.0.0/0            172.25.0.3           tcp dpt:8080
ACCEPT     tcp  --  0.0.0.0/0            172.25.0.3           tcp dpt:80

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination         
DOCKER-ISOLATION-STAGE-2  all  --  0.0.0.0/0            0.0.0.0/0           
DOCKER-ISOLATION-STAGE-2  all  --  0.0.0.0/0            0.0.0.0/0           
DOCKER-ISOLATION-STAGE-2  all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Chain DOCKER-ISOLATION-STAGE-2 (3 references)
target     prot opt source               destination         
DROP       all  --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Chain DOCKER-USER (1 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

Also tried using an invalid Host header, and when running the curl command from a Client, the response is always the 301 Moved Permanently while running from the Server, the response is 404 page not found, which makes me believe that any requests are being redirected to the dashboard regardless of the Host header.

I spent over 30h trying to debug this, but could not figure why this is happening, if anyone could help with some suggestion I would really appreciate!

Check simple Traefik example.

It seems you opened port 80, but you didn’t declare an entrypoint.

I tried adding the entrypoint and still didn't work. I've just deployed the same exact docker-compose.yml file on another host with same docker version (Client and Server version 24.0.5) and same docker compose version (v2.20.2), and I was able to reach the whoami service from other hosts in the network.

Although this worked on the other host, I need it to work on this specific host, as this is my home server.

Your existing Traefik static config looks incomplete: