In a couple of prior articles (here and here) we showcased the capabilities of our WireGuard Docker container with some real world examples. At the time, our WireGuard container only supported one active tunnel at a time so the second article resorted to using multiple WireGuard containers running on the same host and using the host's routing tables to do advanced routing between and through them.
In October 2023, our WireGuard container received a major update and started supporting multiple WireGuard tunnels at a time, which made it much more versatile than before. In this article we'll take advantage of this new capability and showcase a setup that involves a single container that acts as both a server and a client that tunnels peers through multiple redundant VPN connections while maintaining access to the LAN.
Many VPN providers have a limit on the number of devices (or tunnels). This setup will allow you to have an unlimited amount of devices tunneled through a single VPN connection while also supporting a fail-over backup connection!
DISCLAIMER: This article is not meant to be a step by step guide, but instead a showcase for what can be achieved with our WireGuard image. We do not officially provide support for routing whole or partial traffic through our WireGuard container (aka split tunneling) as it can be very complex and require specific customization to fit your network setup and your VPN provider's. But you can always seek community support on our Discord server's #other-support channel.
Tested on Ubuntu 23.04, Docker 24.0.5, Docker Compose 2.20.2, with Mullvad.
Requirements
- A working instance of our Wireguard container in server mode.
Initial WireGuard Server Configuration
Configure a standard WireGuard server according to the WireGuard documentation.
wireguard:
image: lscr.io/linuxserver/wireguard:latest
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- SERVERURL=wireguard.domain.com
- SERVERPORT=51820
- PEERS=1
- PEERDNS=auto
- INTERNAL_SUBNET=10.13.13.0
volumes:
- /path/to/appdata/config:/config
- /lib/modules:/lib/modules
ports:
- 51820:51820/udp
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
Start the container and validate that docker logs wireguard
contains no errors, and validate that the server is working properly by connecting a client to it.
VPN Client Tunnels Configuration
Copy the 2 WireGuard configs that you get from your VPN providers into files under /config/wg_confs/wg1.conf
and /config/wg_confs/wg2.conf
.
Example wg1.conf
Make the following changes:
- Add
Table = 55111
to distinguish rules for this interface. - Add
PostUp = ip rule add pref 10001 from 10.13.13.0/24 lookup 55111
to forward traffic from the wireguard server through the tunnel using table 55111 and priority 10001. - Add
PreDown = ip rule del from 10.13.13.0/24 lookup 55111
to remove the previous rule when the interface goes down. - Add
PersistentKeepalive = 25
to keep the tunnel alive. - Add
AllowedIPs =
and calculate the value using a Wireguard AllowedIPs Calculator.- Write
0.0.0.0/0
in theAllowed IPs
field. - Write your LAN subnet and Wireguard server subnet in the
Disallowed IPs
field, for example:192.168.0.0/24, 10.13.13.0/24
, make sure it doesn't include the VPN interface address (10.65.156.233
in the example below).
- Write
- Make sure you're using the
PrivateKey
,Address
,PublicKey
, andEndpoint
that you got from your VPN provider (below is just an example).
[Interface]
PrivateKey = ...
Address = 10.65.156.233/32
Table = 55111
PostUp = ip rule add pref 10001 from 10.13.13.0/24 lookup 55111
PreDown = ip rule del from 10.13.13.0/24 lookup 55111
[Peer]
PublicKey = ...
AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, 10.0.0.0/13, 10.8.0.0/14, 10.12.0.0/16, 10.13.0.0/21, 10.13.8.0/22, 10.13.12.0/24, 10.13.14.0/23, 10.13.16.0/20, 10.13.32.0/19, 10.13.64.0/18, 10.13.128.0/17, 10.14.0.0/15, 10.16.0.0/12, 10.32.0.0/11, 10.64.0.0/10, 10.128.0.0/9, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/2, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.168.1.0/24, 192.168.2.0/23, 192.168.4.0/22, 192.168.8.0/21, 192.168.16.0/20, 192.168.32.0/19, 192.168.64.0/18, 192.168.128.0/17, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/3
Endpoint = 169.150.217.215:51820
PersistentKeepalive = 25
Example wg2.conf
Make the following changes:
- Add
Table = 55112
to distinguish rules for this interface. - Add
PostUp = ip rule add pref 10002 from 10.13.13.0/24 lookup 55112
to forward traffic from the WireGuard server through the tunnel using table 55112 and priority 10002. - Add
PreDown = ip rule del from 10.13.13.0/24 lookup 55112
to remove the previous rule when the interface goes down. - Add
PersistentKeepalive = 25
to keep the tunnel alive. - Add
AllowedIPs =
and calculate the value using a Wireguard AllowedIPs Calculator (same as above). - Make sure you're using the
PrivateKey
,Address
,PublicKey
, andEndpoint
that you got from your VPN provider (below is just an example).
[Interface]
PrivateKey = ...
Address = 10.67.126.217/32
Table = 55112
PostUp = ip rule add pref 10002 from 10.13.13.0/24 lookup 55112
PreDown = ip rule del from 10.13.13.0/24 lookup 55112
[Peer]
PublicKey = ...
AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, 10.0.0.0/13, 10.8.0.0/14, 10.12.0.0/16, 10.13.0.0/21, 10.13.8.0/22, 10.13.12.0/24, 10.13.14.0/23, 10.13.16.0/20, 10.13.32.0/19, 10.13.64.0/18, 10.13.128.0/17, 10.14.0.0/15, 10.16.0.0/12, 10.32.0.0/11, 10.64.0.0/10, 10.128.0.0/9, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/2, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.168.1.0/24, 192.168.2.0/23, 192.168.4.0/22, 192.168.8.0/21, 192.168.16.0/20, 192.168.32.0/19, 192.168.64.0/18, 192.168.128.0/17, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/3
Endpoint = 169.150.217.232:51820
PersistentKeepalive = 25
Save the changes and restart the container with docker restart wireguard
, validate that docker logs wireguard
contains no errors.
Perform the following validations to check that the VPN tunnels works:
- Check that you have connectivity on wg1 by running
docker exec wireguard ping -c4 -I wg1 1.1.1.1
. - Check that you have connectivity on wg2 by running
docker exec wireguard ping -c4 -I wg2 1.1.1.1
. - Check the details of your VPN tunnel on wg1 by running
docker exec wireguard curl --interface wg1 -s https://am.i.mullvad.net/json
, you should get an IP that is different from your WAN IP. - Check the details of your VPN tunnel on wg2 by running
docker exec wireguard curl --interface wg2 -s https://am.i.mullvad.net/json
, you should get an IP that is different from your WAN IP.
Failover Script
Place the following fail-over script under /config/wg_failover.sh
, the defaults match the examples but you can read the comments explaining each parameter and modify them.
#!/bin/bash
# Ping targets to check connectivity, the default is cloudflare and google DNS addresses
TARGETS=("1.1.1.1" "1.0.0.1" "8.8.8.8" "8.8.4.4")
# How many failed pings are allowed before failover
FAILOVER_LIMIT=2
# The subnets that should be tunneled through wireguard
LOCAL_RANGES=("10.13.13.0/24")
# An array of tunnel details corresponding to the tunnel conf: <tunnel-name>;<table-number>;<rule-priority>
TUNNELS=("wg1;55111;10001" "wg2;55112;10002")
# Connectivity check interval in seconds
PING_INTERVAL=20
LOG_FILE="/config/wg_failover.log"
apply_rules () {
for LOCAL_RANGE in "${LOCAL_RANGES[@]}"
do
ip rule del from ${LOCAL_RANGE} lookup $1
ip rule add pref $2 from ${LOCAL_RANGE} lookup $1
done
}
FAILED=()
INDEX=0
while sleep $PING_INTERVAL
do
COUNTER=1
IFS=";" read -r -a TUNNEL <<< "${TUNNELS[INDEX]}"
TUNNEL_NAME="${TUNNEL[0]}"
TUNNEL_TABLE=${TUNNEL[1]}
TUNNEL_PRIORITY=${TUNNEL[2]}
wg-quick up "${TUNNEL_NAME}" > /dev/null 2>&1
for TARGET in "${TARGETS[@]}"
do
if ! ping -c1 -w10 -I "${TUNNEL_NAME}" "${TARGET}" > /dev/null 2>&1; then
(( COUNTER++ ))
fi
done
if [[ "$COUNTER" -gt "$FAILOVER_LIMIT" ]] && [[ ! "${FAILED[*]}" =~ "${TUNNEL_NAME}" ]]; then
echo "$(date +'%Y-%m-%d %T') - ${TUNNEL_NAME} failed ${COUNTER} pings" >> $LOG_FILE
apply_rules "${TUNNEL_TABLE}" "$(( TUNNEL_PRIORITY+1000 ))"
FAILED+=(${TUNNEL_NAME})
elif [[ "$COUNTER" -le "$FAILOVER_LIMIT" ]] && [[ "${FAILED[*]}" =~ "${TUNNEL_NAME}" ]]; then
echo "$(date +'%Y-%m-%d %T') - ${TUNNEL_NAME} restored" >> $LOG_FILE
apply_rules "${TUNNEL_TABLE}" "$(( TUNNEL_PRIORITY ))"
FAILED=( "${FAILED[@]/$TUNNEL_NAME}" )
elif [[ "$COUNTER" -gt "$FAILOVER_LIMIT" ]] && [[ "${FAILED[*]}" =~ "${TUNNEL_NAME}" ]]; then
wg-quick down "${TUNNEL_NAME}" > /dev/null 2>&1
fi
(( INDEX++ ))
if [[ $INDEX+1 > ${#TUNNELS[@]} ]]; then
INDEX=0
fi
done
WireGuard Server Configuration Changes
Edit /config/templates/server.conf
, replace the PostUp/PreDown rules with the rules listed below, these rules are required for the server to forward traffic to the VPN client tunnels and activate the fail-over script.
PostUp = iptables -I FORWARD -i %i -o wg1 -j ACCEPT
PostUp = iptables -I FORWARD -i %i -o wg2 -j ACCEPT
PostUp = iptables -I FORWARD -i %i -d 10.0.0.0/8 -j ACCEPT
PostUp = iptables -I FORWARD -i %i -d 172.16.0.0/12 -j ACCEPT
PostUp = iptables -I FORWARD -i %i -d 192.168.0.0/16 -j ACCEPT
PostUp = iptables -I FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -A FORWARD -j REJECT
PostUp = iptables -t nat -A POSTROUTING -o wg1 -j MASQUERADE
PostUp = iptables -t nat -A POSTROUTING -o wg2 -j MASQUERADE
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip rule add pref 1000 lookup main suppress_prefixlength 0
PostUp = /config/wg_failover.sh &
PreDown = ip rule del lookup main suppress_prefixlength 0
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -t nat -D POSTROUTING -o wg1 -j MASQUERADE
PreDown = iptables -t nat -D POSTROUTING -o wg2 -j MASQUERADE
PreDown = iptables -D FORWARD -j REJECT
PreDown = iptables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PreDown = iptables -D FORWARD -i %i -d 10.0.0.0/8 -j ACCEPT
PreDown = iptables -D FORWARD -i %i -d 172.16.0.0/12 -j ACCEPT
PreDown = iptables -D FORWARD -i %i -d 192.168.0.0/16 -j ACCEPT
PreDown = iptables -D FORWARD -i %i -o wg1 -j ACCEPT
PreDown = iptables -D FORWARD -i %i -o wg2 -j ACCEPT
Save the changes and delete /config/wg_confs/wg0.conf
so it would be generated again, restart the container with docker restart wireguard
, validate that docker logs wireguard
contains no errors.
Try navigating to https://am.i.mullvad.net/json
on one of your client devices and verify that the WireGuard server is working properly and that you're tunneled through one the VPN tunnels.