No description
Find a file
2025-12-24 14:02:12 +01:00
unmodified_configs initial commit 2025-12-08 13:07:19 +01:00
.gitignore add setup helper, update readme, pin release versions 2025-12-08 13:43:14 +01:00
compose.yml update and wait for network 2025-12-24 14:01:31 +01:00
example.env add setup helper, update readme, pin release versions 2025-12-08 13:43:14 +01:00
README.md Merge branch 'main' of https://git.univ-exp.com/stefan/wireguard-jumphost 2025-12-24 14:02:12 +01:00
setup_wireguard.sh fix PostUp/Down iptables syntax 2025-12-15 09:24:44 +01:00

WireGuard Thin-Client Gateway (Docker)

This project runs a WireGuard client inside Docker and exposes a remote LAN behind this device to your WireGuard network.

It uses the official LinuxServer.io WireGuard image and a simple compose.yml with IP forwarding enabled. Image version pinning is recommended (see Environment variables section).

Use case example:

  • Your WireGuard server is at 10.252.2.1/24
  • This thin client connects as 10.252.2.6/32
  • The thin client offers access to 192.168.100.0/24 (LAN behind it)

Example topology (logical)

            Mobile laptop (peer)
                  10.252.2.20/32
                        │
                        │ WireGuard tunnel
                        │
        ┌───────────────┴───────────────┐
        │     WireGuard server (WG)     │
        │       10.252.2.1/24           │
        └───────────────┬───────────────┘
                        │
                        │ WireGuard tunnel
                        │
        ┌───────────────┴───────────────┐
        │  Thin client gateway (Docker) │
        │      10.252.2.6/32 (wg0)      │
        └───────────────┬───────────────┘
                        │
                        │ NAT + forwarding (PostUp/PostDown)
                        │
          ┌─────────────┴─────────────┐
          │        Remote LAN         │
          │      192.168.100.0/24     │
          └─────────────┬─────────────┘
                        │
                 LAN devices (e.g. printer, NAS)

Requirements

  • Docker + Docker Compose (plugin version, not legacy docker-compose)
  • Linux host (Debian in this example)
  • A working WireGuard server configuration
  • tput (usually pre-installed, used for colored script output)

Included

  • compose.yml with:
    • NET_ADMIN capability
    • kernel modules mounted
    • forwarding enabled
    • Support for image version pinning via IMAGE_TAG environment variable
    • Network readiness checks via network_wait service
    • Automatic restart on failure via deunhealth service
  • setup_wireguard.sh helper script for automated configuration setup
  • example.env template for environment variable configuration
  • WireGuard client configs stored inside ./wireguard/wg_confs/ (generated after first container start)
  • Automatic NAT and forwarding rules using PostUp/PostDown

Directory Layout

project/
├─ compose.yml
├─ .env (optional)
├── example.env
├─ README.md
├─ setup_wireguard.sh
├─ unmodified_configs/
│  └─ wg_<NAME>.conf
└─ wireguard/
  └─ wg_confs/
    └─ wg_<NAME>.conf

The included setup_wireguard.sh script automates the configuration process:

  1. Place your unmodified WireGuard client config in ./unmodified_configs/

    • The filename must follow: wg_<NAME>.conf (e.g., wg_foo.conf)
    • Only letters and numbers after wg_ until .conf
  2. Run the setup script:

    ./setup_wireguard.sh
    

    The script will:

    • Check for required dependencies (docker, docker compose, tput)
    • Ensure the ./wireguard/wg_confs/ directory exists (starts container temporarily if needed)
    • Process all .conf files from ./unmodified_configs/
    • Automatically add PostUp and PostDown rules to the [Interface] section
    • Move processed configs to ./wireguard/wg_confs/
    • Remove original files from ./unmodified_configs/ after successful processing
    • Automatically start or restart the container

Note: The script requires docker compose (plugin version), not the legacy docker-compose standalone.

Manual setup

Prepare a client config

Copy your WireGuard client config into ./wireguard/wg_confs/ (generated after first container start)

The filename must follow: wg_<NAME>.conf

Allowed:

  • wg_foo.conf
  • wg_bar.conf
  • wg_123.conf

Not allowed:

  • wg_foo_bar.conf ← underscore after second word will not be detected
  • wg-foo.conf
  • wgMyClient.conf

Rule: after wg_ only letters + numbers until .conf

Modify the config (important!)

Before placing the file, update its [Interface] section to include the generic PostUp/PostDown rules used for gateway mode:

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o e+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o e+ -j MASQUERADE

Why this is required:

  • %i is replaced with the WireGuard interface (example: wg0)
  • NAT allows remote peers to reach the LAN behind the thin-client
  • e+ matches network interfaces like enp3s0, ens3, eth0, etc.

Example client config:

[Interface]
Address = 10.252.2.6/32
PrivateKey = <SECRET>
MTU = 1350
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o e+ -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o e+ -j MASQUERADE
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <PSK>
AllowedIPs = 10.252.2.0/24
Endpoint = example.com:51821
PersistentKeepalive = 15

Notice: we do not add the LAN network here. The LAN network belongs in the server's peer config (AllowedIPs).


Server configuration reminder

On the server, configure the peer like this:

[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PSK>
# add the clients LAN network here
#                              |
#                              v
#             WG-IP        , LAN Network
AllowedIPs = 10.252.2.6/32,192.168.100.0/24
PersistentKeepalive = 15

Optional: Environment variables

You can customize the container behavior using environment variables in an .env file.

It is strongly recommended to pin the WireGuard image version to ensure reproducible deployments and avoid unexpected updates.

  1. Create an .env file (or copy example.env to .env):

    IMAGE_TAG=1.0.20250521
    
  2. Find available tags at Docker Hub

  3. The image will be: lscr.io/linuxserver/wireguard:${IMAGE_TAG}

    • With IMAGE_TAG=1.0.20250521: uses the specific version
    • Without .env file: defaults to latest (not recommended for production)

Custom container name

You can customize the container name by setting the NET_NAME environment variable. This is useful when running multiple instances on the same host.

  1. Add to your .env file:

    NET_NAME=my_home_network
    
  2. The container name will be: wireguard_provider_${NET_NAME}

    • With NET_NAME=my_home_network: container name is wireguard_provider_my_home_network
    • Without .env file: container name defaults to wireguard_provider_docker_compose

Note: Docker Compose automatically loads variables from .env file if present. You can combine both variables in the same .env file:

NET_NAME=my_home_network
IMAGE_TAG=1.0.20250521

Start container

docker compose up -d

Check logs:

# Default container name
docker logs -f wireguard_provider_docker_compose

# Or if using NET_NAME (replace with your actual container name)
docker logs -f wireguard_provider_<NET_NAME>

Services Overview

The compose.yml includes three services that work together to ensure reliable WireGuard connectivity:

1. network_wait Service

Image: busybox:latest

Purpose: Ensures the host network and DNS are fully operational before starting WireGuard.

Why we use it:

  • On system boot, Docker containers may start before the host network is ready
  • WireGuard needs DNS resolution to connect to servers (e.g., eample.com:51820)
  • Without DNS, WireGuard fails with "Try again" errors and "Configuration parsing error"
  • This service waits for both network connectivity (ping) and DNS resolution before allowing WireGuard to start

How it works:

  • Continuously checks network connectivity by pinging 8.8.8.8 or 1.1.1.1
  • Verifies DNS resolution by resolving google.com
  • Only becomes "healthy" when both conditions are met
  • WireGuard depends on this service being healthy before starting

2. wireguard_provider Service

Image: lscr.io/linuxserver/wireguard

Purpose: The main WireGuard client container that establishes the VPN tunnel.

Why we use it:

  • Provides a containerized WireGuard client with automatic configuration management
  • Supports multiple tunnel configurations
  • Handles iptables rules for NAT and forwarding automatically

Health checks:

  • Verifies WireGuard interface exists and is configured
  • Checks for active tunnel connection (latest handshake)
  • Monitors general network connectivity
  • If health checks fail, the container is marked as unhealthy

3. deunhealth Service

Image: qmcgaw/deunhealth

Purpose: Automatically restarts unhealthy containers.

Why we use it:

  • Docker's default behavior does not restart containers just because they're unhealthy
  • When WireGuard fails (e.g., DNS unavailable, server unreachable), the container stays running but unhealthy
  • deunhealth monitors containers with the deunhealth.restart.on.unhealthy=true label
  • When it detects an unhealthy container, it automatically restarts it
  • This ensures WireGuard keeps retrying until network conditions improve

How it works:

  • Runs with network_mode: none (no network access needed)
  • Monitors Docker socket for container health status
  • Restarts containers labeled with deunhealth.restart.on.unhealthy=true
  • Lightweight and efficient monitoring solution

Networking notes

  • NET_ADMIN is required to run iptables inside the container
  • NAT is enabled only for traffic exiting your Linux host uplink
  • Docker sysctls enable IPv4 forwarding automatically
  • DNS servers (8.8.8.8, 8.8.4.4, 1.1.1.1) are explicitly configured to ensure reliable DNS resolution

Troubleshooting

Check routes & interface

ip route ip addr

Check NAT table

iptables -t nat -L -n

Check filter rules

iptables -L -n

Based on

LinuxServer.io WireGuard documentation: their image, capabilities, and volume layout:

License

GNU General Public License v3.0 or later.

Credits