October 9, 2025Oct 9 Greetings - I have a working wireguard vpn tunnel set up through unraid's vpn config. Is there a way with unraid default config (not using something like gluetun) to allow some containers to use that wg tunnel to connect to WAN, while still allowing them to remain on the docker network's subnet? Currently, if I switch my container's network to wgX, it is no longer visible to my other dockers.
October 9, 2025Oct 9 Community Expert Brandon Martino - Personal SiteGuide-DockerNetworksBrandon Martino - Personal Siteyou will need to implemnt #66) --network=container:<name> (share another container’s network)and use a CLI to Connect container to networkI would recoemnd using compose where we can define more then 1 network added to a docker. Edited October 9, 2025Oct 9 by bmartino1
October 9, 2025Oct 9 Community Expert you can do this with policy routing + marks on the Unraid host so only the containers you choose egress over wg0, while they stay on a normal Docker bridge (so they’re still reachable by your other containers).Overview (what we’re doing)Keep containers on a user-defined Docker bridge (not wgX). That preserves service discovery and reachability.Give VPN-bound containers static IPs on that bridge (easy to target with firewall rules).Add policy routing on the host:Mark packets with those source IPs only when they go to the internet (exclude RFC1918 etc).Route marked packets out the WireGuard interface (wg0).Masquerade out wg0 so replies return properly.Do not touch intra-Docker / LAN traffic, so containers still see each other on the bridge.If I understand you correctly.When dealing with vpn docker on unraid Prefer to use docker copoaseDocker Compose (example) to go over and reveiw...#version: "3.8" networks: appnet: driver: bridge ipam: config: - subnet: 172.23.0.0/24 gateway: 172.23.0.1 services: qbittorrent: image: lscr.io/linuxserver/qbittorrent:latest container_name: qbittorrent networks: appnet: ipv4_address: 172.23.0.10 # <-- this one will be routed via wg0 ports: - "8080:8080" # example; expose if you actually need host access environment: - PUID=99 - PGID=100 - TZ=America/Chicago restart: unless-stopped prowlarr: image: lscr.io/linuxserver/prowlarr:latest container_name: prowlarr networks: appnet: ipv4_address: 172.23.0.11 # <-- also via wg0 restart: unless-stopped plex: image: lscr.io/linuxserver/plex:latest container_name: plex networks: appnet: {} # dynamic IP (not via wg0) restart: unless-stopped This creates a dedicated user bridge appnet (172.23.0.0/24) and pins a couple of “via-VPN” app IPs so we can match them.All 3 are on the same Docker bridge → they can reach each other by container name/IP.Only the IPs you list (e.g., 172.23.0.10, .11) will be policy-routed out wg0.If you already have a bridge you like, reuse it — just choose static IPs from that subnet for the “over-VPN” containers.Double-check your Wiregurad unraid host settings:My understanding is that your using urnaid vpn manger for your wire guard VPNWireGuard & host routing glueA. Make sure WireGuard can NAT outIf you used Unraid’s WG “Remote access to server” or similar, it likely added MASQUERADE already. If not, we’ll add it in our script below.If your /etc/wireguard/wg0.conf is managed by Unraid, don’t hand-edit it. We’ll add rules at array start using the User Scripts plugin so they persist.some can be made in the web UI, not a fan of unraids web ui... and hard to share and give examples with our security PHI issues...B. Policy routing script (drop into User Scripts → “At Startup of Array”)This marks packets from the selected container IPs to the WAN (not local ranges) and sends them through table 51820 (our WG table).user script:#!/bin/bash set -e WG_IF="wg0" WG_TABLE="51820" MARK_HEX="0xC8E4" # arbitrary mark value MARK_DEC=$((0xC8E4)) # List the container IPs that must egress via WireGuard VPN_SRC_IPS=( 172.23.0.10 172.23.0.11 ) # --- Set up routing table for WireGuard --- # Add default route via wg0 in our custom table ip -t route show table ${WG_TABLE} | grep -q "default" || ip route add default dev ${WG_IF} table ${WG_TABLE} || true # Add policy rule to use that table for marked packets ip rule show | grep -q "fwmark ${MARK_DEC} lookup ${WG_TABLE}" || ip rule add fwmark ${MARK_DEC} lookup ${WG_TABLE} # --- iptables chains (idempotent) --- iptables -t mangle -C PREROUTING -j WG_VPN 2>/dev/null || iptables -t mangle -N WG_VPN iptables -t mangle -C PREROUTING -j WG_VPN 2>/dev/null || iptables -t mangle -A PREROUTING -j WG_VPN iptables -t mangle -C OUTPUT -j WG_VPN 2>/dev/null || iptables -t mangle -A OUTPUT -j WG_VPN # Exempt local/LAN destinations early (so LAN stays LAN) for cidr in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 127.0.0.0/8; do iptables -t mangle -C WG_VPN -d $cidr -j RETURN 2>/dev/null || iptables -t mangle -A WG_VPN -d $cidr -j RETURN done # Mark traffic from the chosen container IPs for ip in "${VPN_SRC_IPS[@]}"; do iptables -t mangle -C WG_VPN -s ${ip} -m mark --mark 0x0 -j MARK --set-mark ${MARK_HEX} 2>/dev/null \ || iptables -t mangle -A WG_VPN -s ${ip} -m mark --mark 0x0 -j MARK --set-mark ${MARK_HEX} done # NAT/MASQUERADE out the WireGuard interface for internet return path iptables -t nat -C POSTROUTING -o ${WG_IF} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o ${WG_IF} -j MASQUERADE echo "Selective WireGuard policy routing is active." What this doesCreates routing table 51820 with default via wg0.Adds an ip rule to use that table when packets carry our mark.Uses the DOCKER-friendly path (PREROUTING and OUTPUT via a custom WG_VPN chain) to:Immediately return traffic destined to private/LAN ranges (so local stays local).Mark traffic that originates from the listed container IPs.MASQUERADE out wg0 so replies come back properly through the tunnel.If you reboot or restart the array, the rules will be re-applied automatically by the User Script.How to choose which containers use the VPNJust add their IP to VPN_SRC_IPS in the script and give them that static IP on your Docker bridge in compose.Remove an IP from the list to stop sending it over WG.This scales well: you can target a whole subnet (e.g., -s 172.23.0.0/24) if you create a dedicated “vpn-apps” bridge. I usually keep it to a handful of pinned IPs.Notes & gotchasContainers remain discoverable on the bridge and can talk to other containers exactly as before.Only WAN traffic (non-RFC1918/CGNAT/loopback) from the selected container IPs gets forced over wg0.If a container exposes ports to the host (e.g., -p 8080:8080), that still works — your browser → host → container is LAN and won’t get marked.If your WG config already does NAT or has special firewalling, keep those, but the script’s MASQUERADE is usually necessary.If you run additional WG tunnels, repeat the pattern with a different WG_TABLE/MARK_HEX/interface.Quick test# See rules ip rule ip route show table 51820 iptables -t mangle -S WG_VPN iptables -t nat -S | grep MASQUERADE # Exec into a VPN-bound container and check exit IP: Example docker exec -it qbittorrent bash curl -4 https://ifconfig.io # Should show your WireGuard egress IP you can use docker network inspect <network name>and get the docker IP and sue unraid temple the static asign one... Edited October 9, 2025Oct 9 by bmartino1 forum weridness with picture upload.
October 9, 2025Oct 9 Author @bmartino1 thank you for your detailed post - As you assumed I already had a wireguard config set up within unraid, so I stuck with that instead of making a new docker network. It is currently set to the mode "VPN access for docker" (let me know if that is problematic).What is odd is that when the script is running, the unraid server cannot touch the internet. I thought it may be that only the whitelisted vpn IPS could, but even those do not seem to access the wan.I modified your script in 3 ways:changing wg0 to wg1 adding a couple IPs to pass through,and i changed the subnets that are granted local access to my docker network's subnet:#!/bin/bashset -eWG_IF="wg1"WG_TABLE="51820"MARK_HEX="0xC8E4" # arbitrary mark valueMARK_DEC=$((0xC8E4))# List the container IPs that must egress via WireGuardVPN_SRC_IPS=( 172.18.0.100 172.18.0.101)# --- Set up routing table for WireGuard ---# Add default route via wg0 in our custom tableip -t route show table ${WG_TABLE} | grep -q "default" || ip route add default dev ${WG_IF} table ${WG_TABLE} || true# Add policy rule to use that table for marked packetsip rule show | grep -q "fwmark ${MARK_DEC} lookup ${WG_TABLE}" || ip rule add fwmark ${MARK_DEC} lookup ${WG_TABLE}# --- iptables chains (idempotent) ---iptables -t mangle -C PREROUTING -j WG_VPN 2>/dev/null || iptables -t mangle -N WG_VPNiptables -t mangle -C PREROUTING -j WG_VPN 2>/dev/null || iptables -t mangle -A PREROUTING -j WG_VPNiptables -t mangle -C OUTPUT -j WG_VPN 2>/dev/null || iptables -t mangle -A OUTPUT -j WG_VPN# Exempt local/LAN destinations early (so LAN stays LAN)for cidr in 10.0.0.0/8 172.18.0.0/12 192.168.0.0/16 100.64.0.0/10 127.0.0.0/8; do iptables -t mangle -C WG_VPN -d $cidr -j RETURN 2>/dev/null || iptables -t mangle -A WG_VPN -d $cidr -j RETURNdone# Mark traffic from the chosen container IPsfor ip in "${VPN_SRC_IPS[@]}"; do iptables -t mangle -C WG_VPN -s ${ip} -m mark --mark 0x0 -j MARK --set-mark ${MARK_HEX} 2>/dev/null \ || iptables -t mangle -A WG_VPN -s ${ip} -m mark --mark 0x0 -j MARK --set-mark ${MARK_HEX}done# NAT/MASQUERADE out the WireGuard interface for internet return pathiptables -t nat -C POSTROUTING -o ${WG_IF} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o ${WG_IF} -j MASQUERADEecho "Selective WireGuard policy routing is active."when I run ip rule, I don't see 51820root@BLADE:~# ip rule0: from all lookup local32760: from all fwmark 0xc8e4 lookup 5182032761: from 172.31.202.0/24 lookup 20232762: from all lookup main suppress_prefixlength 032763: not from all fwmark 0xca6c lookup 5182032764: from 172.31.201.0/24 lookup 20132765: from 172.31.200.0/24 lookup 20032766: from all lookup main32767: from all lookup defaultalso, when I run iptables -t mangle -S WG_VPN, for some reason the subnet you specified is still there and not reflecting my change to 172.18.* (ofc I have rebooted the server):root@BLADE:~# iptables -t mangle -S WG_VPN-N WG_VPN-A WG_VPN -d 10.0.0.0/8 -j RETURN-A WG_VPN -d 172.16.0.0/12 -j RETURN-A WG_VPN -d 192.168.0.0/16 -j RETURN-A WG_VPN -d 100.64.0.0/10 -j RETURN-A WG_VPN -d 127.0.0.0/8 -j RETURN-A WG_VPN -s 172.18.0.100/32 -m mark --mark 0x0 -j MARK --set-xmark 0xc8e4/0xffffffff-A WG_VPN -s 172.18.0.101/32 -m mark --mark 0x0 -j MARK --set-xmark 0xc8e4/0xffffffff any idea what I missed here? thanks again for your help
October 10, 2025Oct 10 Community Expert The symptoms you’re seeing line up with two overlapping issues:a rule/mark collision with Unraid’s “VPN tunneled access for docker” mode, andyour WG_VPN chain is additive (it never deletes the old entries), so your later edits don’t show up—and the broad RFC1918 exemption you saw is from the previous runAlso: you do have a 51820 rule—your ip rule output shows it at priority 32760:So that part looks right.What to change in Unraid firstIn your WireGuard tunnel, turn OFF “VPN tunneled access for docker.”That mode installs its own fwmark/table 51820 rules (you can see fwmark 0xca6c in your ip rule dump) and will fight our policy routing. We’ll do the container selection ourselves.Make sure the wg interface you’re using (wg1) is up and the peer allows 0.0.0.0/0 (or at least the destinations you expect) in AllowedIPs, with NAT enabled on the peer side if needed. If the peer doesn’t route 0.0.0.0/0, adding a “default dev wg1” in table 51820 won’t actually get you to the Internet.Drop-in script (replace your current one)Use this on array start via the User Scripts plugin.#!/bin/bash set -euo pipefail WG_IF="wg1" WG_TABLE="51820" # Arbitrary mark value (hex + dec) MARK_HEX="0xC8E4" MARK_DEC=$((0xC8E4)) # Container IPs that should egress via WireGuard VPN_SRC_IPS=( 172.18.0.100 172.18.0.101 ) # 1) Make sure replies from policy-routed flows are allowed # (wg-quick usually sets this; do it here to be safe) sysctl -w net.ipv4.conf.all.src_valid_mark=1 >/dev/null # 2) Ensure a default route in our custom table if ! ip -4 route show table "${WG_TABLE}" | grep -qE '^default '; then ip -4 route add default dev "${WG_IF}" table "${WG_TABLE}" || true fi # 3) Install the policy rule for our mark -> table 51820 if ! ip -4 rule show | grep -q "fwmark ${MARK_DEC} lookup ${WG_TABLE}"; then ip -4 rule add fwmark "${MARK_DEC}" lookup "${WG_TABLE}" fi # 4) Create (or flush+rebuild) the WG_VPN chain so edits take effect # We flush to avoid stacking old exemptions/marks forever. if iptables -t mangle -nL WG_VPN >/dev/null 2>&1; then iptables -t mangle -F WG_VPN else iptables -t mangle -N WG_VPN fi # 5) Make sure PREROUTING and OUTPUT jump into WG_VPN once for hook in PREROUTING OUTPUT; do iptables -t mangle -C "${hook}" -j WG_VPN 2>/dev/null || \ iptables -t mangle -A "${hook}" -j WG_VPN done # 6) Exempt LAN/RFC1918/bogon/etc so containers still talk to local Docker/LAN directly # Keep the full RFC1918 ranges; they already include 172.18.0.0/16. for cidr in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 127.0.0.0/8; do iptables -t mangle -A WG_VPN -d "$cidr" -j RETURN done # 7) Mark only the selected container sources for ip in "${VPN_SRC_IPS[@]}"; do iptables -t mangle -A WG_VPN -s "${ip}" -m mark --mark 0x0 -j MARK --set-mark "${MARK_HEX}" done # 8) Never mark traffic coming *from* the WireGuard interface itself iptables -t mangle -A WG_VPN -i "${WG_IF}" -j RETURN # 9) NAT out the WireGuard interface for return path symmetry iptables -t nat -C POSTROUTING -o "${WG_IF}" -j MASQUERADE 2>/dev/null || \ iptables -t nat -A POSTROUTING -o "${WG_IF}" -j MASQUERADE echo "Selective WireGuard policy routing active on ${WG_IF} for: ${VPN_SRC_IPS[*]}" Why these changes fix your issuesUnraid VPN-for-Docker collision: Your ip rule showed an extra rule with fwmark 0xca6c pointing to the same table 51820. That’s Unraid’s Docker-over-WG mode, and it can grab traffic you didn’t intend (and also modifies NAT rules). Turning it off removes that interference so only our 0xC8E4 marks use table 51820.Chain flushing: You changed the exemption list (e.g., to 172.18.*), but your earlier run had already inserted 172.16.0.0/12. Because your script only adds and never deletes, the old broader exemption stuck around. Flushing WG_VPN each run makes the chain reflect your current intent.RFC1918 exemptions: Keep 172.16.0.0/12 (it already includes 172.18.0.0/16). Replacing it with 172.18.0.0/12 is not what you want (and is an invalid supernet for a /12 starting at .18). The broad RFC1918 returns ensure inter-container and LAN access stays local, while only Internet egress is policy-routed.Host losing Internet: With the collision removed and the chain fixed, host-originated traffic (which doesn’t match -s 172.18.0.100/101) won’t be marked, so it will use the main table as usual. The OUTPUT -> WG_VPN jump is safe because we only mark on the -s matches. If you still want to be extra cautious, you can remove the OUTPUT jump entirely and rely solely on PREROUTING (containers hit PREROUTING on ingress to the host’s routing decision anyway).Quick verification stepsip -4 route show table 51820# should show: default dev wg1 (plus any on-link routes)ip -4 rule# look for: from all fwmark 0xc8e4 lookup 51820iptables -t mangle -S WG_VPNiptables -t nat -S | grep MASQUERADEip route get 1.1.1.1 from 172.18.0.100# Should resolve via wg1 (table 51820) if the peer routes 0.0.0.0/0If the 4 step shows it would go via wg1 but the container still can’t reach the WAN, double-check the peer’s AllowedIPs/NAT. Many commercial or upstream peers require you to allow 0.0.0.0/0 and/or enable NAT on their side for full Internet egress.
October 20, 2025Oct 20 Author Solution For anyone who also wants to achieve this, I found a solution that is much easier (imo).Create a wireguard connection to your vpn, using "VPN Tunneled Access for Docker"Assign any of your desired containers to this wireguard networkCreate a "shared network" that can bridge your normal docker network with your wg network, but does NOT route to WAN (--internal flag):docker network create --internal [shared-network-name]Join your VPN'd containers, as well as any you want to be able to access them, to this "shared network" (a container can join multiple docker networks!):docker network connect [shared-network-name] [container-name]that is all you need to do! Now any of your regular docker network containers can communicate with your containers that are on the wireguard networkyou can create a user script to always join a list of containers to your shared network:#!/bin/bash# Define the target networkTARGET_NETWORK="shared-network"# Define the list of container names to monitorCONTAINERS=("container-name""container2-name")# Function to check and connect containerconnect_if_needed() {local CONTAINER=$1# Check if container existsif ! docker inspect "$CONTAINER" &>/dev/null; thenecho "$(date): Container '$CONTAINER' does not exist, skipping..."returnfi# Get the networks the container is connected toNETWORKS=$(docker inspect "$CONTAINER" -f '{{range $key, $value := .NetworkSettings.Networks}}{{$key}} {{end}}')# Check if container is already connected to the target networkif echo "$NETWORKS" | grep -q "$TARGET_NETWORK"; thenecho "$(date): Container '$CONTAINER' already connected to '$TARGET_NETWORK'"elseecho "$(date): Connecting '$CONTAINER' to '$TARGET_NETWORK'..."docker network connect "$TARGET_NETWORK" "$CONTAINER"if [ $? -eq 0 ]; thenecho "$(date): Successfully connected '$CONTAINER' to '$TARGET_NETWORK'"elseecho "$(date): Failed to connect '$CONTAINER' to '$TARGET_NETWORK'"fifi}# Check if target network existsif ! docker network inspect "$TARGET_NETWORK" &>/dev/null; thenecho "Error: Network '$TARGET_NETWORK' does not exist"exit 1fiecho "$(date): Starting Docker event monitor for container updates..."# Monitor docker events for container creates AND startsdocker events --filter 'type=container' --filter 'event=create' --filter 'event=start' --format '{{.Actor.Attributes.name}}' | while read CONTAINER_NAMEdo# Check if this container is in our listfor TARGET_CONTAINER in "${CONTAINERS[@]}"; doif [ "$CONTAINER_NAME" = "$TARGET_CONTAINER" ]; thenecho "$(date): Detected event for '$CONTAINER_NAME'"# Small delay to ensure container is fully readysleep 2connect_if_needed "$CONTAINER_NAME"breakfidonedone Edited November 24, 2025Nov 24 by underw0rld adding script
October 21, 2025Oct 21 Community Expert 17 hours ago, underw0rld said:For anyone who also wants to achieve this, I found a solution that is much easier (imo).Create a wireguard connection to your vpn, using "VPN Tunneled Access for Docker"Assign any of your desired containers to this wireguard networkCreate a "shared network" that can bridge your normal docker network with your wg network, but does NOT route to WAN (--internal flag):docker network create --internal [shared-network-name]Join your VPN'd containers, as well as any you want to be able to access them, to this "shared network" (a container can join multiple docker networks!):docker network connect [shared-network-name] [container-name]that is all you need to do! Now any of your regular docker network containers can communicate with your containers that are on the wireguard networkhttps://docs.docker.com/reference/cli/docker/network/create/Requires the command ran any time the docker that's connected to goes down... the ip table's command I gave above is constant once setup... doesn't require interactions or running xyz command for other dockers...https://bmartino1.weebly.com/guide-dockernetworks.html OR you can use Docker compose to set 2 networks at once...
October 21, 2025Oct 21 Author The network assignments created in my previous post persist after bringing individual containers down/up, shutting down all containers associated with the "shared network", and even after disabling/enabling docker entirely. Just for anyone's future reference.
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.