Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 91 additions & 1 deletion readme-vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ opt_param_env_vars:
- {env_var: "PEERS", env_value: "1", desc: "Number of peers to create confs for. Required for server mode. Can also be a list of names: `myPC,myPhone,myTablet` (alphanumeric only)"}
- {env_var: "PEERDNS", env_value: "auto", desc: "DNS server set in peer/client configs (can be set as `8.8.8.8`). Used in server mode. Defaults to `auto`, which uses wireguard docker host's DNS via included CoreDNS forward."}
- {env_var: "INTERNAL_SUBNET", env_value: "10.13.13.0", desc: "Internal subnet for the wireguard and server and peers (only change if it clashes). Used in server mode."}
- {env_var: "IP6_SUBNET", env_value: "", desc: "IPv6 subnet for the WireGuard tunnel, enabling dual-stack (IPv4+IPv6) configuration. Must end with ':' (e.g., 2001:db8:b00b:420::). Accepts an optional CIDR prefix /64 through /112 (defaults to /64 if omitted). Server gets ::1/128, peers get sequential ::2/128, ::3/128, etc. Requires SYS_MODULE capability and a static IPv6 route on your router. See IPv6 GUA setup instructions in the application setup section below. Used in server mode."}
- {env_var: "ALLOWEDIPS", env_value: "0.0.0.0/0", desc: "The IPs/Ranges that the peers will be able to reach using the VPN connection. If not specified the default value is: '0.0.0.0/0, ::0/0' This will cause ALL traffic to route through the VPN, if you want split tunneling, set this to only the IPs you would like to use the tunnel AND the ip of the server's WG ip, such as 10.13.13.1."}
- {env_var: "PERSISTENTKEEPALIVE_PEERS", env_value: "", desc: "Set to `all` or a list of comma separated peers (ie. `1,4,laptop`) for the wireguard server to send keepalive packets to listed peers every 25 seconds. Useful if server is accessed via domain name and has dynamic IP. Used only in server mode."}
- {env_var: "LOG_CONFS", env_value: "true", desc: "Generated QR codes will be displayed in the docker log. Set to `false` to skip log output."}
Expand All @@ -60,7 +61,7 @@ app_setup_block: |

If the environment variable `PEERS` is set to a number or a list of strings separated by comma, the container will run in server mode and the necessary server and peer/client confs will be generated. The peer/client config qr codes will be output in the docker log if `LOG_CONFS` is set to `true`. They will also be saved in text and png format under `/config/peerX` in case `PEERS` is a variable and an integer or `/config/peer_X` in case a list of names was provided instead of an integer.

Variables `SERVERURL`, `SERVERPORT`, `INTERNAL_SUBNET`, `PEERDNS`, `INTERFACE`, `ALLOWEDIPS` and `PERSISTENTKEEPALIVE_PEERS` are optional variables used for server mode. Any changes to these environment variables will trigger regeneration of server and peer confs. Peer/client confs will be recreated with existing private/public keys. Delete the peer folders for the keys to be recreated along with the confs.
Variables `SERVERURL`, `SERVERPORT`, `INTERNAL_SUBNET`, `PEERDNS`, `INTERFACE`, `ALLOWEDIPS`, `PERSISTENTKEEPALIVE_PEERS` and `IP6_SUBNET` are optional variables used for server mode. Any changes to these environment variables will trigger regeneration of server and peer confs. Peer/client confs will be recreated with existing private/public keys. Delete the peer folders for the keys to be recreated along with the confs.

To add more peers/clients later on, you increment the `PEERS` environment variable or add more elements to the list and recreate the container.

Expand All @@ -76,6 +77,95 @@ app_setup_block: |

If you get IPv6 related errors in the log and connection cannot be established, edit the `AllowedIPs` line in your peer/client wg0.conf to include only `0.0.0.0/0` and not `::/0`; and restart the container.

## IPv6 GUA (Global Unicast Address) Support

WireGuard supports optional dual-stack (IPv4+IPv6) tunnel configuration via the `IP6_SUBNET` environment variable. When set, both the server and all peers receive an IPv6 GUA address in addition to their IPv4 address. IPv6 is disabled by default; existing IPv4-only configurations continue to work without modification.

`SYS_MODULE` capability **must** be added to `cap_add` when using IPv6. A static IPv6 route must also be configured on your router pointing `IP6_SUBNET` to your host — this is required for both host and bridge modes. Without the router route, IPv6 traffic will not reach your WireGuard peers.

### Host Mode

Enable IPv6 forwarding on the host by adding the following sysctls to your docker-compose:

```yaml
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
```

Configure a static IPv6 route on your router pointing `IP6_SUBNET` to the host device IP.

Example (host mode):

```yaml
services:
wireguard:
image: lsio/wireguard
container_name: wireguard
network_mode: host
cap_add:
- NET_ADMIN
- SYS_MODULE
volumes:
- /path/to/wireguard/config:/config
- /lib/modules:/lib/modules
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
environment:
- PEERS=3
- SERVERURL=wireguard.domain.com
- IP6_SUBNET=2001:db8:b00b:420::
- PEERDNS=8.8.8.8,2001:4860:4860::8888
restart: unless-stopped
```

### Bridge Mode

Create an external Docker network with IPv6 support:

```bash
docker network create --ipv6 --subnet 2001:db8:1::/64 wireguard_net
```

Add a static IPv6 route on the host pointing `IP6_SUBNET` to the Docker network gateway. **Note:** host routes added via `ip route` are ephemeral — you must make them persistent via your system's network configuration or they will be lost on reboot.

```bash
sudo ip -6 route add 2001:db8:b00b:420::/64 via <docker_network_gateway>
```

Configure a static IPv6 route on your router pointing `IP6_SUBNET` to the host device IP.

Example (bridge mode):

```yaml
services:
wireguard:
image: lsio/wireguard
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- 51820:51820/udp
volumes:
- /path/to/wireguard/config:/config
- /lib/modules:/lib/modules
networks:
wireguard_net:
environment:
- PEERS=3
- SERVERURL=wireguard.domain.com
- IP6_SUBNET=2001:db8:b00b:420::
- PEERDNS=8.8.8.8,2001:4860:4860::8888
restart: unless-stopped
networks:
wireguard_net:
external: true
```

## Road warriors, roaming and returning home

If you plan to use Wireguard both remotely and locally, say on your mobile phone, you will need to consider routing. Most firewalls will not route ports forwarded on your WAN interface correctly to the LAN out of the box. This means that when you return home, even though you can see the Wireguard server, the return packets will probably get lost.
Expand Down
2 changes: 1 addition & 1 deletion root/defaults/peer.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Interface]
Address = ${CLIENT_IP}
Address = ${CLIENT_IP}${CLIENT_IP6:+,${CLIENT_IP6}}
PrivateKey = $(cat /config/${PEER_ID}/privatekey-${PEER_ID})
ListenPort = 51820
DNS = ${PEERDNS}
Expand Down
2 changes: 1 addition & 1 deletion root/defaults/server.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Interface]
Address = ${INTERFACE}.1
Address = ${INTERFACE}.1${IP6_ADDR_SERVER:+,${IP6_ADDR_SERVER}}
ListenPort = 51820
PrivateKey = $(cat /config/server/privatekey-server)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
Expand Down
63 changes: 59 additions & 4 deletions root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ fi
if ! grep -q 'PresharedKey' /config/templates/peer.conf; then
sed -i 's|^Endpoint|PresharedKey = \$\(cat /config/\${PEER_ID}/presharedkey-\${PEER_ID}\)\nEndpoint|' /config/templates/peer.conf
fi
# add IPv6 GUA support to user templates (backwards compatibility)
if ! grep -q 'IP6_ADDR_SERVER' /config/templates/server.conf; then
sed -i 's|^Address = ${INTERFACE}\.1$|Address = ${INTERFACE}.1${IP6_ADDR_SERVER:+,${IP6_ADDR_SERVER}}|' /config/templates/server.conf
fi
if ! grep -q 'CLIENT_IP6' /config/templates/peer.conf; then
sed -i 's|^Address = ${CLIENT_IP}$|Address = ${CLIENT_IP}${CLIENT_IP6:+,${CLIENT_IP6}}|' /config/templates/peer.conf
fi

generate_confs () {
mkdir -p /config/server
Expand Down Expand Up @@ -50,10 +57,18 @@ DUDE"
wg genpsk > "/config/${PEER_ID}/presharedkey-${PEER_ID}"
fi
if [[ -f "/config/${PEER_ID}/${PEER_ID}.conf" ]]; then
CLIENT_IP=$(grep "Address" "/config/${PEER_ID}/${PEER_ID}.conf" | awk '{print $NF}')
CLIENT_IP=$(grep "Address" "/config/${PEER_ID}/${PEER_ID}.conf" | awk '{print $NF}' | cut -d, -f1)
if [[ -n "${ORIG_INTERFACE}" ]] && [[ "${INTERFACE}" != "${ORIG_INTERFACE}" ]]; then
CLIENT_IP="${CLIENT_IP//${ORIG_INTERFACE}/${INTERFACE}}"
fi
if [[ -n "${IP6_SUBNET}" ]]; then
CLIENT_IP6=$(grep "Address" "/config/${PEER_ID}/${PEER_ID}.conf" | awk '{print $NF}' | cut -d, -f2)
if [[ -n "${ORIG_IP6_SUBNET}" ]] && [[ "${IP6_SUBNET}" != "${ORIG_IP6_SUBNET}" ]]; then
CLIENT_IP6=""
fi
else
CLIENT_IP6=""
fi
else
for idx in {2..254}; do
PROPOSED_IP="${INTERFACE}.${idx}"
Expand All @@ -62,6 +77,16 @@ DUDE"
break
fi
done
CLIENT_IP6=""
fi
# IPv6 address conflict detection
if [[ -n "${IP6_SUBNET}" ]] && [[ -z "${CLIENT_IP6}" ]]; then
for idx in {2..254}; do
if ! grep -q -R "${IP6_SUBNET}${idx}" /config/peer*/*.conf 2>/dev/null; then
CLIENT_IP6="${IP6_SUBNET}${idx}/128"
break
fi
done
fi
if [[ -f "/config/${PEER_ID}/presharedkey-${PEER_ID}" ]]; then
# create peer conf with presharedkey
Expand Down Expand Up @@ -95,11 +120,11 @@ DUDE
if [[ -n "${!SERVER_ALLOWEDIPS}" ]]; then
echo "Adding ${!SERVER_ALLOWEDIPS} to wg0.conf's AllowedIPs for peer ${i}"
cat <<DUDE >> /config/wg_confs/wg0.conf
AllowedIPs = ${CLIENT_IP}/32,${!SERVER_ALLOWEDIPS}
AllowedIPs = ${CLIENT_IP}/32${CLIENT_IP6:+,${CLIENT_IP6}},${!SERVER_ALLOWEDIPS}
DUDE
else
cat <<DUDE >> /config/wg_confs/wg0.conf
AllowedIPs = ${CLIENT_IP}/32
AllowedIPs = ${CLIENT_IP}/32${CLIENT_IP6:+,${CLIENT_IP6}}
DUDE
fi
# add PersistentKeepalive if the peer is specified
Expand Down Expand Up @@ -133,6 +158,7 @@ ORIG_PEERS="$PEERS"
ORIG_INTERFACE="$INTERFACE"
ORIG_ALLOWEDIPS="$ALLOWEDIPS"
ORIG_PERSISTENTKEEPALIVE_PEERS="$PERSISTENTKEEPALIVE_PEERS"
ORIG_IP6_SUBNET="$IP6_SUBNET"
DUDE
}

Expand Down Expand Up @@ -160,6 +186,35 @@ if [[ -n "$PEERS" ]]; then
INTERFACE=$(echo "$INTERNAL_SUBNET" | awk 'BEGIN{FS=OFS="."} NF--')
ALLOWEDIPS=${ALLOWEDIPS:-0.0.0.0/0, ::/0}
echo "**** AllowedIPs for peers $ALLOWEDIPS ****"
if [[ -n "$IP6_SUBNET" ]]; then
# strip optional CIDR prefix and validate prefix length
if [[ "$IP6_SUBNET" =~ /([0-9]+)$ ]]; then
IP6_PREFIX_LEN="${BASH_REMATCH[1]}"
IP6_SUBNET="${IP6_SUBNET%/*}"
if [[ "$IP6_PREFIX_LEN" -lt 64 ]]; then
echo "**** ERROR: IP6_SUBNET prefix length /${IP6_PREFIX_LEN} is too small. Must be /64 or larger. IPv6 disabled. ****"
IP6_SUBNET=""
fi
fi
fi
if [[ -n "$IP6_SUBNET" ]]; then
# must end with colon (e.g. 2001:db8:b00b:420::)
if [[ ! "$IP6_SUBNET" =~ :$ ]]; then
echo "**** ERROR: IP6_SUBNET must end with ':' (e.g., 2001:db8:b00b:420::). IPv6 disabled. ****"
IP6_SUBNET=""
fi
fi
if [[ -n "$IP6_SUBNET" ]]; then
# basic format: only hex digits and colons
if [[ ! "$IP6_SUBNET" =~ ^[0-9a-fA-F:]+$ ]]; then
echo "**** ERROR: IP6_SUBNET contains invalid characters. IPv6 disabled. ****"
IP6_SUBNET=""
fi
fi
if [[ -n "$IP6_SUBNET" ]]; then
IP6_ADDR_SERVER="${IP6_SUBNET}1/128"
echo "**** IPv6 subnet is set to ${IP6_SUBNET}, server IPv6 will be ${IP6_ADDR_SERVER} ****"
fi
if [[ -z "$PEERDNS" ]] || [[ "$PEERDNS" = "auto" ]]; then
PEERDNS="${INTERFACE}.1"
echo "**** PEERDNS var is either not set or is set to \"auto\", setting peer DNS to ${INTERFACE}.1 to use wireguard docker host's DNS. ****"
Expand All @@ -175,7 +230,7 @@ if [[ -n "$PEERS" ]]; then
if [[ -f /config/.donoteditthisfile ]]; then
. /config/.donoteditthisfile
fi
if [[ "$SERVERURL" != "$ORIG_SERVERURL" ]] || [[ "$SERVERPORT" != "$ORIG_SERVERPORT" ]] || [[ "$PEERDNS" != "$ORIG_PEERDNS" ]] || [[ "$PEERS" != "$ORIG_PEERS" ]] || [[ "$INTERFACE" != "$ORIG_INTERFACE" ]] || [[ "$ALLOWEDIPS" != "$ORIG_ALLOWEDIPS" ]] || [[ "$PERSISTENTKEEPALIVE_PEERS" != "$ORIG_PERSISTENTKEEPALIVE_PEERS" ]]; then
if [[ "$SERVERURL" != "$ORIG_SERVERURL" ]] || [[ "$SERVERPORT" != "$ORIG_SERVERPORT" ]] || [[ "$PEERDNS" != "$ORIG_PEERDNS" ]] || [[ "$PEERS" != "$ORIG_PEERS" ]] || [[ "$INTERFACE" != "$ORIG_INTERFACE" ]] || [[ "$ALLOWEDIPS" != "$ORIG_ALLOWEDIPS" ]] || [[ "$PERSISTENTKEEPALIVE_PEERS" != "$ORIG_PERSISTENTKEEPALIVE_PEERS" ]] || [[ "$IP6_SUBNET" != "$ORIG_IP6_SUBNET" ]]; then
echo "**** Server related environment variables changed, regenerating 1 server and ${PEERS} peer/client confs ****"
generate_confs
save_vars
Expand Down