From 8d641a2b8cd3f77770385e3eb0b7f8dc4c1552e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 01:22:46 +0000 Subject: [PATCH 1/6] Add optional IPv6 GUA dual-stack support via IP6_SUBNET Introduce the IP6_SUBNET environment variable for native IPv6 global unicast address support on WireGuard tunnels. When set the server receives ::1/128 and peers are assigned sequential ::2/128, ::3/128, etc. addresses alongside their existing IPv4 addresses. The feature is entirely opt-in; omitting IP6_SUBNET preserves current behaviour with zero changes to generated configs. Changes ------- * root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run - Validate IP6_SUBNET: strip optional CIDR prefix, enforce prefix length >= /64, require trailing colon, reject non-hex characters. - Set IP6_ADDR_SERVER for the server template. - Extract CLIENT_IP6 from existing peer confs (cut -d, -f2 on the Address field); invalidate on subnet change for reassignment. - IPv6 conflict-detection loop mirrors the existing IPv4 logic. - Include CLIENT_IP6 in server-conf AllowedIPs for each peer. - Persist ORIG_IP6_SUBNET in save_vars; add IP6_SUBNET to the change-detection condition that triggers conf regeneration. - Backward-compat sed patches upgrade existing user templates that predate this change (mirrors the earlier PresharedKey migration). * root/defaults/server.conf - Address line conditionally appends , when set. * root/defaults/peer.conf - Address line conditionally appends , when set. * readme-vars.yml - Add IP6_SUBNET to opt_param_env_vars with full description. - List IP6_SUBNET among vars that trigger conf regeneration. - New "IPv6 GUA Support" section: requirements (SYS_MODULE, router routes), complete host-mode and bridge-mode docker-compose examples, and explicit notes on ephemeral host routes. https://claude.ai/code/session_01QdSdYNzFJjZVymw6NNbGP5 --- readme-vars.yml | 92 ++++++++++++++++++- root/defaults/peer.conf | 2 +- root/defaults/server.conf | 2 +- .../s6-rc.d/init-wireguard-confs/run | 63 ++++++++++++- 4 files changed, 152 insertions(+), 7 deletions(-) diff --git a/readme-vars.yml b/readme-vars.yml index 643bce8e..2bdebfef 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -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."} @@ -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. @@ -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 + ``` + + 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. diff --git a/root/defaults/peer.conf b/root/defaults/peer.conf index d987dba9..3169774d 100644 --- a/root/defaults/peer.conf +++ b/root/defaults/peer.conf @@ -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} diff --git a/root/defaults/server.conf b/root/defaults/server.conf index 757682d6..b803a74f 100644 --- a/root/defaults/server.conf +++ b/root/defaults/server.conf @@ -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 diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run index 4279d315..53d85347 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run +++ b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run @@ -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 @@ -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}" @@ -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 @@ -95,11 +120,11 @@ DUDE if [[ -n "${!SERVER_ALLOWEDIPS}" ]]; then echo "Adding ${!SERVER_ALLOWEDIPS} to wg0.conf's AllowedIPs for peer ${i}" cat <> /config/wg_confs/wg0.conf -AllowedIPs = ${CLIENT_IP}/32,${!SERVER_ALLOWEDIPS} +AllowedIPs = ${CLIENT_IP}/32${CLIENT_IP6:+,${CLIENT_IP6}},${!SERVER_ALLOWEDIPS} DUDE else cat <> /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 @@ -133,6 +158,7 @@ ORIG_PEERS="$PEERS" ORIG_INTERFACE="$INTERFACE" ORIG_ALLOWEDIPS="$ALLOWEDIPS" ORIG_PERSISTENTKEEPALIVE_PEERS="$PERSISTENTKEEPALIVE_PEERS" +ORIG_IP6_SUBNET="$IP6_SUBNET" DUDE } @@ -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. ****" @@ -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 From dd2806eb8abe5db1207d71b117fba6f8f88db40a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 01:34:59 +0000 Subject: [PATCH 2/6] Rewrite README for fork: strip LSIO branding, add IPv6 GUA docs * Replace linuxserver.io header badges and boilerplate with a fork notice block pointing back to upstream for unrelated issues. * Add "Changes from upstream" section summarising the single delta (IP6_SUBNET / IPv6 GUA support) and linking to the detailed section. * Add full "IPv6 GUA Support" section with host-mode and bridge-mode setup instructions, complete docker-compose examples, and notes on ephemeral host routes and required router configuration. * Add IP6_SUBNET to both the docker-compose and docker cli example blocks, the Parameters table, and the Server Mode regeneration- trigger variable list. * Update image references throughout from lscr.io/linuxserver/wireguard to ohshitgorillas/wireguard; update git clone URL in "Building locally" to point at this fork. * Remove linuxserver-specific sections: Docker Mods badge block, upstream image version check, Diun update-notification tip, and the generic "most of our images are static" preamble in Updating Info. * Retain references to linuxserver docs/utilities that are still valid (read-only docs, qemu-static image, historical changelog entries). https://claude.ai/code/session_01QdSdYNzFJjZVymw6NNbGP5 --- README.md | 185 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 5e0f6df2..3f476a92 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,24 @@ - - -[![linuxserver.io](https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/linuxserver_medium.png)](https://linuxserver.io) - -[![Blog](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=Blog)](https://blog.linuxserver.io "all the things you can do with our containers including How-To guides, opinions and much more!") -[![Discord](https://img.shields.io/discord/354974912613449730.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=Discord&logo=discord)](https://linuxserver.io/discord "realtime support / chat with the community and the team.") -[![Discourse](https://img.shields.io/discourse/https/discourse.linuxserver.io/topics.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=discourse)](https://discourse.linuxserver.io "post on our community forum.") -[![GitHub](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitHub&logo=github)](https://github.com/linuxserver "view the source for all of our repositories.") -[![Open Collective](https://img.shields.io/opencollective/all/linuxserver.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=Supporters&logo=open%20collective)](https://opencollective.com/linuxserver "please consider helping us by either donating or contributing to our budget") - -The [LinuxServer.io](https://linuxserver.io) team brings you another container release featuring: - -* regular and timely application updates -* easy user mappings (PGID, PUID) -* custom base image with s6 overlay -* weekly base OS updates with common layers across the entire LinuxServer.io ecosystem to minimise space usage, down time and bandwidth -* regular security updates - -Find us at: - -* [Blog](https://blog.linuxserver.io) - all the things you can do with our containers including How-To guides, opinions and much more! -* [Discord](https://linuxserver.io/discord) - realtime support / chat with the community and the team. -* [Discourse](https://discourse.linuxserver.io) - post on our community forum. -* [GitHub](https://github.com/linuxserver) - view the source for all of our repositories. -* [Open Collective](https://opencollective.com/linuxserver) - please consider helping us by either donating or contributing to our budget - -# [linuxserver/wireguard](https://github.com/linuxserver/docker-wireguard) - -[![Scarf.io pulls](https://scarf.sh/installs-badge/linuxserver-ci/linuxserver%2Fwireguard?color=94398d&label-color=555555&logo-color=ffffff&style=for-the-badge&package-type=docker)](https://scarf.sh) -[![GitHub Stars](https://img.shields.io/github/stars/linuxserver/docker-wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/linuxserver/docker-wireguard) -[![GitHub Release](https://img.shields.io/github/release/linuxserver/docker-wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/linuxserver/docker-wireguard/releases) -[![GitHub Package Repository](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitHub%20Package&logo=github)](https://github.com/linuxserver/docker-wireguard/packages) -[![GitLab Container Registry](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=GitLab%20Registry&logo=gitlab)](https://gitlab.com/linuxserver.io/docker-wireguard/container_registry) -[![Quay.io](https://img.shields.io/static/v1.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=linuxserver.io&message=Quay.io)](https://quay.io/repository/linuxserver.io/wireguard) -[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=pulls&logo=docker)](https://hub.docker.com/r/linuxserver/wireguard) -[![Docker Stars](https://img.shields.io/docker/stars/linuxserver/wireguard.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=stars&logo=docker)](https://hub.docker.com/r/linuxserver/wireguard) -[![Jenkins Build](https://img.shields.io/jenkins/build?labelColor=555555&logoColor=ffffff&style=for-the-badge&jobUrl=https%3A%2F%2Fci.linuxserver.io%2Fjob%2FDocker-Pipeline-Builders%2Fjob%2Fdocker-wireguard%2Fjob%2Fmaster%2F&logo=jenkins)](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-wireguard/job/master/) +# docker-wireguard + +> **Fork notice:** This is a community fork of [linuxserver/docker-wireguard](https://github.com/linuxserver/docker-wireguard). +> It is **not** an official LinuxServer.io image. Issues specific to this fork (IPv6 GUA support) should be +> filed here. General WireGuard container issues should be reported [upstream](https://github.com/linuxserver/docker-wireguard/issues). [WireGuard®](https://www.wireguard.com/) is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable. It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry. [![wireguard](https://www.wireguard.com/img/wireguard.svg)](https://www.wireguard.com/) -## Supported Architectures +## Changes from upstream + +This fork adds one feature on top of the upstream linuxserver image: -We utilise the docker manifest for multi-platform awareness. More information is available from docker [here](https://distribution.github.io/distribution/spec/manifest-v2-2/#manifest-list) and our announcement [here](https://blog.linuxserver.io/2019/02/21/the-lsio-pipeline-project/). +* **IPv6 GUA (Global Unicast Address) support** — set the `IP6_SUBNET` environment variable to give the WireGuard tunnel a dual-stack (IPv4 + IPv6) configuration. The server gets `::1/128`; peers are assigned sequential `/128` addresses (`::2`, `::3`, …). The feature is entirely opt-in: omitting `IP6_SUBNET` produces behaviour identical to upstream. See the [IPv6 GUA Support](#ipv6-gua-global-unicast-address-support) section below for full setup instructions. -Simply pulling `lscr.io/linuxserver/wireguard:latest` should retrieve the correct image for your arch, but you can also pull specific arch images via tags. +Everything else — base image, s6 init, CoreDNS, key management, QR code generation — is unchanged from upstream. + +## Supported Architectures + +This fork is not published to a container registry. Build the image locally using the instructions in [Building locally](#building-locally). The upstream linuxserver image supports multi-platform builds via Docker manifest; the same applies here. The architectures supported by this image are: @@ -67,7 +41,7 @@ Some hosts may not load the iptables kernel modules by default. In order for the 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. @@ -83,6 +57,95 @@ Do not set the `PEERS` environment variable. Drop your client conf(s) into the c 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: ohshitgorillas/wireguard:latest + 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 +``` + +Configure a static IPv6 route on your router pointing `IP6_SUBNET` to the host device IP. + +Example (bridge mode): + +```yaml +services: + wireguard: + image: ohshitgorillas/wireguard:latest + 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. @@ -141,7 +204,7 @@ To help you get started creating a container from this image you can either use --- services: wireguard: - image: lscr.io/linuxserver/wireguard:latest + image: ohshitgorillas/wireguard:latest container_name: wireguard cap_add: - NET_ADMIN @@ -155,6 +218,7 @@ services: - PEERS=1 #optional - PEERDNS=auto #optional - INTERNAL_SUBNET=10.13.13.0 #optional + - IP6_SUBNET= #optional - e.g. 2001:db8:b00b:420:: - ALLOWEDIPS=0.0.0.0/0 #optional - PERSISTENTKEEPALIVE_PEERS= #optional - LOG_CONFS=true #optional @@ -183,6 +247,7 @@ docker run -d \ -e PEERS=1 `#optional` \ -e PEERDNS=auto `#optional` \ -e INTERNAL_SUBNET=10.13.13.0 `#optional` \ + -e IP6_SUBNET= `#optional - e.g. 2001:db8:b00b:420::` \ -e ALLOWEDIPS=0.0.0.0/0 `#optional` \ -e PERSISTENTKEEPALIVE_PEERS= `#optional` \ -e LOG_CONFS=true `#optional` \ @@ -191,7 +256,7 @@ docker run -d \ -v /lib/modules:/lib/modules `#optional` \ --sysctl="net.ipv4.conf.all.src_valid_mark=1" \ --restart unless-stopped \ - lscr.io/linuxserver/wireguard:latest + ohshitgorillas/wireguard:latest ``` ## Parameters @@ -209,6 +274,7 @@ Containers are configured using parameters passed at runtime (such as those abov | `-e PEERS=1` | Number of peers to create confs for. Required for server mode. Can also be a list of names: `myPC,myPhone,myTablet` (alphanumeric only) | | `-e PEERDNS=auto` | 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. | | `-e INTERNAL_SUBNET=10.13.13.0` | Internal subnet for the wireguard and server and peers (only change if it clashes). Used in server mode. | +| `-e IP6_SUBNET=` | IPv6 subnet for dual-stack tunnel configuration (e.g. `2001:db8:b00b:420::`). Must end with `:`. Accepts optional CIDR prefix /64–/112. Server gets `::1/128`, peers get sequential `::2/128`, `::3/128`, etc. Requires `SYS_MODULE` cap and a static IPv6 route on your router. See [IPv6 GUA Support](#ipv6-gua-global-unicast-address-support). | | `-e ALLOWEDIPS=0.0.0.0/0` | 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. | | `-e PERSISTENTKEEPALIVE_PEERS=` | 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. | | `-e LOG_CONFS=true` | Generated QR codes will be displayed in the docker log. Set to `false` to skip log output. | @@ -258,12 +324,6 @@ Example output: uid=1000(your_user) gid=1000(your_user) groups=1000(your_user) ``` -## Docker Mods - -[![Docker Mods](https://img.shields.io/badge/dynamic/yaml?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=wireguard&query=%24.mods%5B%27wireguard%27%5D.mod_count&url=https%3A%2F%2Fraw.githubusercontent.com%2Flinuxserver%2Fdocker-mods%2Fmaster%2Fmod-list.yml)](https://mods.linuxserver.io/?mod=wireguard "view available mods for this container.") [![Docker Universal Mods](https://img.shields.io/badge/dynamic/yaml?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=universal&query=%24.mods%5B%27universal%27%5D.mod_count&url=https%3A%2F%2Fraw.githubusercontent.com%2Flinuxserver%2Fdocker-mods%2Fmaster%2Fmod-list.yml)](https://mods.linuxserver.io/?mod=universal "view available universal mods.") - -We publish various [Docker Mods](https://github.com/linuxserver/docker-mods) to enable additional functionality within the containers. The list of Mods available for this image (if any) as well as universal mods that can be applied to any one of our images can be accessed via the dynamic badges above. - ## Support Info * Shell access whilst the container is running: @@ -284,17 +344,9 @@ We publish various [Docker Mods](https://github.com/linuxserver/docker-mods) to docker inspect -f '{{ index .Config.Labels "build_version" }}' wireguard ``` -* Image version number: - - ```bash - docker inspect -f '{{ index .Config.Labels "build_version" }}' lscr.io/linuxserver/wireguard:latest - ``` - ## Updating Info -Most of our images are static, versioned, and require an image update and container recreation to update the app inside. With some exceptions (noted in the relevant readme.md), we do not recommend or support updating apps inside the container. Please consult the [Application Setup](#application-setup) section above to see if it is recommended for the image. - -Below are the instructions for updating containers: +Rebuild the image and recreate the container to pick up changes: ### Via Docker Compose @@ -332,11 +384,7 @@ Below are the instructions for updating containers: ### Via Docker Run -* Update the image: - - ```bash - docker pull lscr.io/linuxserver/wireguard:latest - ``` +* Rebuild the image (see [Building locally](#building-locally)). * Stop the running container: @@ -357,22 +405,17 @@ Below are the instructions for updating containers: docker image prune ``` -### Image Update Notifications - Diun (Docker Image Update Notifier) - ->[!TIP] ->We recommend [Diun](https://crazymax.dev/diun/) for update notifications. Other tools that automatically update containers unattended are not recommended or supported. - ## Building locally -If you want to make local modifications to these images for development purposes or just to customize the logic: +Clone this fork and build: ```bash -git clone https://github.com/linuxserver/docker-wireguard.git +git clone https://github.com/ohshitgorillas/docker-wireguard.git cd docker-wireguard docker build \ --no-cache \ --pull \ - -t lscr.io/linuxserver/wireguard:latest . + -t ohshitgorillas/wireguard:latest . ``` The ARM variants can be built on x86_64 hardware and vice versa using `lscr.io/linuxserver/qemu-static` From 22b354c0516f03c9af0269ab94a091191b763362 Mon Sep 17 00:00:00 2001 From: ohshitgorillas <96808410+ohshitgorillas@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:01:53 -0800 Subject: [PATCH 3/6] Enhance IPv6 support documentation in README Clarified IPv6 configuration instructions and added critical notes regarding routing requirements. --- README.md | 89 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 3f476a92..c83ab79f 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,26 @@ If you get IPv6 related errors in the log and connection cannot be established, ## 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. +This fork of 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. +**CRITICAL: IPv6 routing WILL NOT WORK out of the box. ADDITIONAL CONFIGURATION IS REQUIRED.** -### Host Mode +A static IPv6 route must be configured on your router pointing `IP6_SUBNET` to your host's link-local IPv6 address via the router's LAN interface. Without this, IPv6 traffic will fail to reach your WireGuard peers. + +The host's link-local address can be found by: +```bash +ip -c -6 -brief addr | grep +``` +Look for the address beginning with `fe80::`. -Enable IPv6 forwarding on the host by adding the following sysctls to your docker-compose: +Finally, `SYS_MODULE` capability is not optional: it must be added to `cap_add` when using IPv6. -```yaml -sysctls: - - net.ipv4.conf.all.src_valid_mark=1 - - net.ipv6.conf.all.disable_ipv6=0 - - net.ipv6.conf.all.forwarding=1 +### Host Mode + +Enable IPv6 forwarding on the host by enabling the following sysctls: +```conf +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. @@ -88,17 +95,14 @@ services: - NET_ADMIN - SYS_MODULE volumes: - - /path/to/wireguard/config:/config + - ./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:: + - IP6_SUBNET=2001:db8:b00b:42a:: - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - PERSISTENTKEEPALIVEPEERS=all restart: unless-stopped ``` @@ -110,42 +114,61 @@ Create an external Docker network with IPv6 support: 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 -``` - Configure a static IPv6 route on your router pointing `IP6_SUBNET` to the host device IP. Example (bridge mode): ```yaml +networks: + wg6: + enable_ipv6: true + ipam: + driver: default + config: + - subnet: "2001:db8:b00b:42b::/64" + services: wireguard: image: ohshitgorillas/wireguard:latest container_name: wireguard + networks: + - wg6 + ports: + - 51820:51820/udp cap_add: - NET_ADMIN - SYS_MODULE - ports: - - 51820:51820/udp - volumes: - - /path/to/wireguard/config:/config - - /lib/modules:/lib/modules - networks: - wireguard_net: + sysctls: + - 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:: + - PEERS=3 - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - INTERNAL_SUBNET=10.14.14.0/24 + - IP6_SUBNET: 2001:db8:b00b:42a:: + - ALLOWEDIPS=0.0.0.0/0, ::/0 + - PERSISTENTKEEPALIVE_PEERS=all + volumes: + - ./config:/config + - /lib/modules:/lib/modules + privileged: true restart: unless-stopped -networks: - wireguard_net: - external: true ``` +Add a static IPv6 route on the host pointing `IP6_SUBNET` to the Docker network gateway. +```bash +sudo ip -6 route add 2001:db8:b00b:42a::/64 via 2001:db8:b00b:42b::2 +``` + +Confirm that the `via` address is correct by executing: +```bash +docker exec wireguard ip -6 -brief addr | grep eth0 +``` + +**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 container or host reboot. + + ## 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. From 6cc089f68aacc1141e6710602bbf1c598c909316 Mon Sep 17 00:00:00 2001 From: ohshitgorillas <96808410+ohshitgorillas@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:04:42 -0800 Subject: [PATCH 4/6] Revise networking section headers in README Updated section headers from 'Host Mode' and 'Bridge Mode' to 'Host Networking' and 'Bridge Networking'. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c83ab79f..cd763372 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Look for the address beginning with `fe80::`. Finally, `SYS_MODULE` capability is not optional: it must be added to `cap_add` when using IPv6. -### Host Mode +### Host Networking Enable IPv6 forwarding on the host by enabling the following sysctls: ```conf @@ -106,7 +106,7 @@ services: restart: unless-stopped ``` -### Bridge Mode +### Bridge Networking Create an external Docker network with IPv6 support: From 10a48ed458f473fa9603c51928fdef87ff86986b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 02:21:33 +0000 Subject: [PATCH 5/6] Fix YAML parsing of IPv6 addresses in docker-compose examples Colons in IPv6 addresses (IP6_SUBNET, PEERDNS) are interpreted as YAML key-value separators when unquoted, producing the error: services.wireguard.environment.[N]: unexpected type map[string]interface {} Wrap the affected environment entries in double quotes in all four example blocks (host + bridge in both README.md and readme-vars.yml). https://claude.ai/code/session_01QdSdYNzFJjZVymw6NNbGP5 --- README.md | 8 ++++---- readme-vars.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3f476a92..1eae7d5f 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,8 @@ services: environment: - PEERS=3 - SERVERURL=wireguard.domain.com - - IP6_SUBNET=2001:db8:b00b:420:: - - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - "IP6_SUBNET=2001:db8:b00b:420::" + - "PEERDNS=8.8.8.8,2001:4860:4860::8888" restart: unless-stopped ``` @@ -138,8 +138,8 @@ services: environment: - PEERS=3 - SERVERURL=wireguard.domain.com - - IP6_SUBNET=2001:db8:b00b:420:: - - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - "IP6_SUBNET=2001:db8:b00b:420::" + - "PEERDNS=8.8.8.8,2001:4860:4860::8888" restart: unless-stopped networks: wireguard_net: diff --git a/readme-vars.yml b/readme-vars.yml index 2bdebfef..236f77d5 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -117,8 +117,8 @@ app_setup_block: | environment: - PEERS=3 - SERVERURL=wireguard.domain.com - - IP6_SUBNET=2001:db8:b00b:420:: - - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - "IP6_SUBNET=2001:db8:b00b:420::" + - "PEERDNS=8.8.8.8,2001:4860:4860::8888" restart: unless-stopped ``` @@ -158,8 +158,8 @@ app_setup_block: | environment: - PEERS=3 - SERVERURL=wireguard.domain.com - - IP6_SUBNET=2001:db8:b00b:420:: - - PEERDNS=8.8.8.8,2001:4860:4860::8888 + - "IP6_SUBNET=2001:db8:b00b:420::" + - "PEERDNS=8.8.8.8,2001:4860:4860::8888" restart: unless-stopped networks: wireguard_net: From 53a9e9927426ec28c2b51780c1104ef6e5073948 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 02:32:35 +0000 Subject: [PATCH 6/6] Fix IPv6 subnet validation rejecting valid trailing-zero forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The colon-suffix check fired before any normalisation, so inputs like 2001:db8:b00b:420::0 or the fully-expanded 2001:db8:b00b:420:0:0:0:0 were rejected even though they represent the same prefix as the accepted 2001:db8:b00b:420::. Two normalisation passes are now applied after CIDR stripping and before validation: 1. Strip trailing zeros that follow :: (::0 → ::, ::0000 → ::). 2. For fully-expanded addresses with no :: present, collapse trailing :0 groups and append :: (420:0:0:0:0 → 420::). The validation itself is tightened from :$ to ::$ to match the actual requirement: the script concatenates host IDs directly onto the subnet string (${IP6_SUBNET}1), so a double-colon suffix is mandatory. https://claude.ai/code/session_0196yPjc8RVdqEMtqh1X6Xse --- .../s6-rc.d/init-wireguard-confs/run | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run index 53d85347..4f280ee0 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run +++ b/root/etc/s6-overlay/s6-rc.d/init-wireguard-confs/run @@ -198,9 +198,25 @@ if [[ -n "$PEERS" ]]; then 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. ****" + # normalize: strip trailing zeros after :: (e.g. ::0 → ::, ::000 → ::) + while [[ "$IP6_SUBNET" =~ ::0+$ ]]; do + IP6_SUBNET="${IP6_SUBNET%0}" + done + # normalize: fully-expanded trailing :0 groups without :: (e.g. 2001:db8:420:0:0:0:0 → 2001:db8:420::) + if [[ ! "$IP6_SUBNET" =~ :: ]]; then + ORIG_SUBNET="$IP6_SUBNET" + while [[ "$IP6_SUBNET" =~ :0+$ ]]; do + IP6_SUBNET="${IP6_SUBNET%:*}" + done + if [[ "$IP6_SUBNET" != "$ORIG_SUBNET" ]]; then + IP6_SUBNET="${IP6_SUBNET}::" + fi + fi + fi + if [[ -n "$IP6_SUBNET" ]]; then + # must end with :: to allow host-ID concatenation (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