Skip to content
Alexander Zinchenko edited this page Jun 21, 2026 · 1 revision

By default the container only routes its own traffic (and that of containers sharing its network namespace) through the VPN. Gateway mode lets the container act as a router for other containers that keep their own network namespace — for example a VPN server (ocserv, WireGuard, SoftEther…) whose clients should exit through NordVPN.

This is the opposite of NETWORK: NETWORK lets traffic bypass the tunnel to reach your LAN, while FORWARD_FROM lets downstream traffic route out through the tunnel.

What FORWARD_FROM Does

When FORWARD_FROM is set, the init-firewall script, for each CIDR:

  1. Adds FORWARD -s <cidr> -o tun0 -j ACCEPT — lets that subnet's traffic leave through the tunnel.
  2. Adds the matching tun0 → <cidr> ESTABLISHED,RELATED return rule.

The existing POSTROUTING -o tun0 -j MASQUERADE rule then NATs the forwarded packets onto the tunnel, so no extra NAT is needed.

Only the FORWARD chain is opened — INPUT/OUTPUT stay locked by the kill switch, and the rules reference tun0. If the tunnel drops, they cannot match, so downstream traffic is dropped rather than leaked.

Requirements

  • Enable IPv4 forwarding on the container: --sysctl net.ipv4.ip_forward=1.
  • Downstream traffic must arrive already SNATed into one of the FORWARD_FROM CIDRs (i.e. masqueraded to the downstream container's address on the shared docker network). That way the gateway needs no return route to the downstream client subnet — replies come back to an address it already knows.

Multiple Subnets

FORWARD_FROM is semicolon- or comma-separated, just like NETWORK:

-e FORWARD_FROM="172.28.0.0/24;10.30.0.0/24;192.168.50.0/24"

⚠️ Downstream container: use policy routing, not a default gateway

It is tempting to point the downstream container's default route at this container. Don't — it breaks the downstream container's own published ports. An inbound connection is DNATed to the downstream container, but its reply would then follow the default route into the tunnel and exit with the wrong source IP, so the client drops it.

Instead, route only the client subnet out through the gateway with a policy rule, keeping the default route on the docker bridge:

# on the downstream (e.g. ocserv) container; 172.28.0.2 = this gateway container
ip route replace default via 172.28.0.2 table 100
ip rule add from 10.20.0.0/24 lookup 100 priority 1000

The routing decision happens before the downstream container's own SNAT, so the from 10.20.0.0/24 match works; client traffic goes to the gateway, while the listener's replies and the container's own traffic stay on the bridge.

Docker Compose Example

Routing an ocserv VPN server's clients out through NordVPN:

networks:
  vpnnet:
    ipam:
      config:
        - subnet: 172.28.0.0/24

services:
  vpn:
    image: azinchen/nordvpn:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      - net.ipv4.ip_forward=1
    environment:
      - USER=service_username
      - PASS=service_password
      - COUNTRY=Netherlands
      - FORWARD_FROM=172.28.0.0/24        # the docker net ocserv SNATs into
    networks:
      vpnnet:
        ipv4_address: 172.28.0.2          # static, so ocserv can route to it
    restart: unless-stopped

  ocserv:
    image: azinchen/ocserv:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      - net.ipv4.ip_forward=1
    environment:
      - VPN_SUBNET=10.20.0.0/24
    ports:
      - "443:443/tcp"                     # published normally on ocserv itself
      - "443:443/udp"
    networks:
      vpnnet:
        ipv4_address: 172.28.0.3
    depends_on:
      - vpn
    restart: unless-stopped

ocserv masquerades its 10.20.0.0/24 clients to 172.28.0.3 (inside 172.28.0.0/24), policy-routes them to 172.28.0.2, and NordVPN forwards them out the tunnel.

Security Notes

  • Keep FORWARD_FROM as narrow as possible — every listed CIDR may route out through the tunnel.
  • Forwarding is gated on tun0, so the kill switch still applies: no tunnel, no forwarding.
  • This does not open any inbound ports on the gateway; publish the downstream service's ports on the downstream container.

Clone this wiki locally