Using NGINX reverse-proxy to make certain Docker-containers only reachable over WireGuard

So, I got the robbertkl/docker-ipv6nat container set up. Now the correct IPv6 addresses show up in the access log and I can reach the internet when connected through WireGuard, but I can’t directly connect to any of the services running inside of docker anymore when connected through WireGuard. So it’s like this:

Wireguard off
GET admin.example.com/ → 403, IPv6 logged in access.log
GET example.com/ → 200, IPv6 logged in access.log*
GET eff.org/ → 200

Wireguard on
GET admin.example.com/ → err_connection_timed_out, nothing logged in access.log
GET example.com/ → 200, IPv4 logged in access.log*
GET eff.org/ → 200

* the top-level example.com/ is proxied through Cloudflare and staysreachable in both scenarios

daemon.json:

{
  "userland-proxy": false,
  "ipv6": true,
  "fixed-cidr": "192.168.0.0/16",
  "fixed-cidr-v6": "fd00:8008:8008::/64",
  "default-address-pools": [
    {
      "base": "192.168.0.0/16",
      "size": 24
    },
    {
      "base": "fd00:dead:beef::/48",
      "size": 64
    }
  ]
}

docker-compose.yml:

services:
  natv6:
    container_name: natv6
    image: robbertkl/ipv6nat
    network_mode: "host"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "/lib/modules:/lib/modules:ro"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    restart: unless-stopped

  nginx:
    container_name: nginx
    image: nginx:mainline-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ...
    networks:
      - default
      - wireguard
      - ...
    restart: unless-stopped
  
  wireguard:
    image: linuxserver/wireguard
    container_name: wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - ...
    volumes:
      - "/home/ubuntu/docker/wireguard-data:/config"
      - "/lib/modules:/lib/modules"
    ports:
      - 51820:51820/udp
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped
    networks:
      - wireguard

networks:
  default:
    driver: "bridge"
    enable_ipv6: true
    driver_opts:
      com.docker.network.bridge.name: br_default
    ipam:
      config:
        - subnet: "fd00:8008:8008:1::/64"

  wireguard:
    name: wireguard
    driver: "bridge"
    driver_opts:
      com.docker.network.bridge.name: br_wireguard

nginx.conf:

...
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name  example.com;
    charset      utf-8;

    ...

    index index.html;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name admin.example.com;
    charset     utf-8;

    ... 

    allow 192.168.0.0/16;
    deny all;

    location / {
        proxy_pass       http://django:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /stats {
        proxy_pass       http://expressjs:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
...
1 Like