Zero-Trust WireGuard: Deploy & Reconfigure wg-easy with Caddy

Deploying a self-hosted VPN often forces administrators to make a difficult trade-off between user-friendly management and production-grade security. Standard WireGuard installations with basic web dashboards often leave management ports exposed to the public internet, neglect automated SSL handling, and leave client networks vulnerable to lateral movement.

The wg-easy-billfoo-autoinstaller provides a hardened, zero-maintenance alternative. It pairs WG-Easy (WireGuard + Web-UI) with an automated Caddy reverse proxy on Ubuntu, enforcing strict Zero-Trust network access, automated Let’s Encrypt certificates, client-to-client isolation, and a self-healing Docker architecture.

Below is a comprehensive technical guide detailing the prerequisites, one-click installation, idempotent reconfiguration (smart auto-import), and underlying security architecture.

Prerequisites Before You Begin

Ensure the target environment meets the following specifications before launching the installer:

  • Fresh Ubuntu Server: You must be logged in as root. The script modifies kernel routing parameters and firewall rules, strictly requiring root execution.
  • Public IPv4 Address: The dedicated public IP address assigned to your server.
  • Registered Domain Name: At least one domain or subdomain (e.g., vpn.yourdomain.com).
  • Configured DNS Records: An A-Record at your domain registrar pointing directly to your server’s public IPv4.
  • Cloudflare DNS Users (Crucial): The A-Record for your Web-UI must be set to “DNS Only” (Grey Cloud). Enabling Cloudflare proxying (Orange Cloud) masks client IP addresses, which breaks the script’s internal Zero-Trust IP whitelisting rules.
  • Cloud Provider Firewall: If hosted on IONOS, AWS, Azure, or Hetzner, you must log into your provider’s control panel and open incoming UDP traffic on Port 51820.

One-Click Installation

To deploy the server, launch the interactive setup wizard using the universal command below. The command automatically falls back to wget if curl is absent on a minimal installation:

bash <(curl -sSL https://raw.githubusercontent.com/billfoo/wg-easy-billfoo-autoinstaller/main/install.sh || wget -O - https://raw.githubusercontent.com/billfoo/wg-easy-billfoo-autoinstaller/main/install.sh)

The Configuration Wizard

The installer runs on an interactive state machine. You can type b and press Enter at any point to step back to the previous question:

  1. VPN Host: The primary domain or IP address your WireGuard clients will connect to.
  2. Web-UI Domain: The domain for accessing the administration dashboard. You can reuse the VPN domain or define a separate subdomain (e.g., admin.yourdomain.com).
  3. Admin Password: A strong password to secure the dashboard.
  4. Access Control: Select between Public (accessible over the internet) or VPN-Only (Zero-Trust: the dashboard is strictly unreachable unless the client is actively connected to the VPN tunnel).

Re-Running the Script (Smart Auto-Import)

The script is fully idempotent and upgrade-safe. If you ever need to update your domain, modify passwords, switch from “Public” to “VPN-Only”, or apply updates, run the exact same installation command as root:

bash <(curl -sSL https://raw.githubusercontent.com/billfoo/wg-easy-billfoo-autoinstaller/main/install.sh || wget -O - https://raw.githubusercontent.com/billfoo/wg-easy-billfoo-autoinstaller/main/install.sh)

What Happens on an Existing Setup?

  1. Detection: The script checks for /opt/wg-easy/docker-compose.yml. If present, it automatically enters Update Mode.
  2. Configuration Extraction: It parses the active docker-compose.yml and Caddyfile to extract the active VPN Host, UI Host, and the existing bcrypt password hash.
  3. Default Overrides: During the prompts, your current configuration values are displayed inside brackets [...]. Simply pressing Enter retains the active configuration.
  4. Password Retention: You can leave the password prompt empty. The script reuses the existing, securely hashed password.
  5. Seamless Application: The installer updates the configuration files and executes docker compose up -d followed by docker compose restart caddy. Docker only recreates modified containers. Your existing client configurations, keys, and QR codes located in /opt/wg-easy/wireguard remain untouched.

What the Script Does (Under the Hood)

Upon execution, the script completes several low-level provisioning tasks autonomously:

  • Dependency Resolution: Refreshes apt package repositories and installs required utilities: curl, ufw, sed, and grep.
  • Kernel IP Forwarding: Creates /etc/sysctl.d/99-wireguard.conf, sets net.ipv4.ip_forward=1 and net.ipv6.conf.all.forwarding=1, and reloads sysctl to permit packet forwarding through the Linux kernel.
  • UFW Firewall Hardening: Applies a strict Default Deny incoming traffic policy. It opens only 22/tcp (SSH), 80/tcp & 443/tcp (Caddy/HTTPS), and 51820/udp (WireGuard), then enables UFW.
  • Docker Provisioning: Inspects the system for a Docker runtime and executes the official Docker convenience installation script if missing.
  • Native Bcrypt Hashing: Pulls the WG-Easy Docker image and runs its internal wgpw tool to compute a standard bcrypt hash. It escapes all $ symbols to $$ using sed to prevent variable expansion issues in Docker Compose.
  • Stack Generation: Creates the /opt/wg-easy directory structure and outputs a customized docker-compose.yml file managing WG-Easy, Caddy, and Autoheal.
  • Dynamic Proxy Rules: Generates the Caddyfile. If the “VPN-Only” mode is selected, it calculates internal Docker NAT ranges and writes them into Caddy’s remote IP whitelist.

Architecture & Security Deep Dive

FeatureImplementationSecurity Benefit
Invisible Caddy ProxyCaddy handles Let’s Encrypt validation. If “VPN-Only” is enabled, unauthorized external IP addresses receive a TCP abort drop.No login page is loaded, and no HTTP status codes (401/403) are returned to external IP addresses, shielding the server from automated scanners.
Docker NAT ResolutionThe script explicitly whitelists 172.16.0.0/12 within Caddy’s access rules.Traffic routed from the WireGuard container to Caddy is masqueraded by the Docker bridge network. Whitelisting prevents administrators from being locked out.
Client IsolationApplies iptables -I FORWARD -i wg0 -o wg0 -j REJECT during interface boot via WG_POST_UP.Connected VPN clients can reach the internet but cannot scan, ping, or communicate with each other inside the VPN subnet.
Self-HealingA Docker Autoheal container tests the wg0 interface every 30 seconds.If health probes fail 3 consecutive times, the container restarts automatically to resolve frozen kernel states without manual intervention.

Troubleshooting & FAQ

Connected to the VPN, but no internet traffic (0 Bytes received)?

This is almost always an external cloud firewall restriction. Ensure your cloud hosting control panel (Hetzner, AWS, IONOS, etc.) allows incoming UDP traffic on port 51820. WireGuard uses UDP exclusively.

“ERR_HTTP2_PROTOCOL_ERROR” when opening the Web UI?

This confirms that Caddy is actively dropping unauthorized connections via abort. Verify that your WireGuard tunnel is active on your device and ensure the Web-UI domain record on Cloudflare is configured as “DNS Only” (Grey Cloud).

502 Bad Gateway Error

This commonly occurs immediately after installation while Caddy negotiates SSL certificates with Let’s Encrypt. Wait 30 seconds and refresh. If the error persists, inspect container activity using:

sudo docker logs wg-easy
sudo docker logs caddy-proxy

Android WireGuard App shows “Unknown Section” on QR scan?

Ensure your web browser is not automatically translating the dashboard into another language (which changes configuration tags like [Interface]). The dashboard must remain in English to produce syntactically valid configuration files.

How to Completely Uninstall the Stack

To tear down the containers and remove all persistent data from your machine, execute:

cd /opt/wg-easy && sudo docker compose down -v
sudo rm -rf /opt/wg-easy

Backup & Disaster Recovery Strategy

All stateful assets—including client private/public keypairs, configuration templates, and internal database records—are stored in the directory:

/opt/wg-easy/wireguard

To back up your setup, copy this directory to an offsite location. If you migrate servers, copy the directory back to /opt/wg-easy/wireguard and execute the installation script. All existing clients will instantly reconnect without requiring new configurations or QR code scans.

Leave a Comment

Your email address will not be published.