docs/content/examples/tutorials/routed.md
This guide shows how to run wg-easy with a routed setup, so packets are forwarded instead of NATed.
In a routed design, each WireGuard client keeps its own IPv4/IPv6 address. That means you can identify clients by their real addresses instead of seeing everything as the WireGuard server’s IP.
To make use of our own IPv4/IPv6 addresses, run the container with the network_mode: host option.
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
network_mode: 'host'
volumes:
- ./config:/etc/wireguard
- /lib/modules:/lib/modules:ro
cap_add:
- NET_ADMIN
- SYS_MODULE
devices:
- /dev/net/tun:/dev/net/tun
restart: unless-stopped
Because we’re on the host network, remove any ports: and container sysctls: you might have had before.
With host networking, system sysctls must be set on the host. On your host, create /etc/sysctl.d/90-wireguard.conf:
net.ipv4.ip_forward=1
net.ipv4.conf.all.src_valid_mark=1
net.ipv6.conf.all.disable_ipv6=0
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
Apply and verify:
sysctl -p /etc/sysctl.d/90-wireguard.conf
sysctl -n net.ipv4.ip_forward # should print 1
Pick an IPv4 and IPv6 subnet for your clients and add static routes on your router, pointing to the WireGuard server's LAN addresses.
/// note | 2001:db8::/32
The documentation prefix 2001:db8::/32 (RFC 3849) used in this example is not meant for production use, replace it with your own ISP-assigned IPv6 prefix (GUA) or local prefix (ULA)
///
I want my WireGuard clients in 192.168.0.0/24 and 2001:db8:abc:0::/64.
192.168.0.0/242001:db8:abc:0::/64192.168.10.118 and 2001:db8:abc:10:216:3eff:fedb:949eOn your router:
192.168.0.0/24 → next hop 192.168.10.1182001:db8:abc:0::/64 → next hop 2001:db8:abc:10:216:3eff:fedb:949eDon't forget to create the necessary firewall rules to allow these subnets to travel across your LAN. Some routers or servers may require specific Outbound NAT rules for the chosen IPv4 and IPv6 subnets to allow traffic to traverse your LAN.
wg-easy configurationIn the Web UI → Admin → Interface, click Change CIDR and set the IPv4/IPv6 routed subnets you chose above. Save.
Then go to Admin → Hooks and add:
PostUp
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT
PostDown
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT
/// warning | Important: When using nftables use the following hooks instead.
PostUp
nft add chain ip filter WG_EASY; nft add rule ip filter DOCKER-USER jump WG_EASY; nft add rule ip filter WG_EASY iifname {{device}} accept; nft add rule ip filter WG_EASY oifname {{device}} accept; nft add chain ip6 filter WG_EASY; nft add rule ip6 filter DOCKER-USER jump WG_EASY; nft add rule ip6 filter WG_EASY iifname {{device}} accept; nft add rule ip6 filter WG_EASY oifname {{device}} accept;
PostDown
nft delete rule ip filter DOCKER-USER handle $(nft -a list chain ip filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip filter WG_EASY; nft delete chain ip filter WG_EASY; nft delete rule ip6 filter DOCKER-USER handle $(nft -a list chain ip6 filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip6 filter WG_EASY; nft delete chain ip6 filter WG_EASY
///