A Turn-Key, Hardened WireGuard Stack for Debian 13

Updated 13 September 2026: this installer has been retired, and WGX has been rewritten from scratch as a WireGuard server of its own and renamed ihasvpn — see WGX Grew Up: It's ihasvpn Now. The repository address below now leads to ihasvpn, so the commands in this article no longer apply.

Introducing WGX Installer (WireGuard eXtended)

If you’ve ever stood up WireGuard on a fresh server and then layered on reverse proxying, TLS, firewalls, fail2ban, unattended updates, health checks, backups, and autostart… you know that “quick weekend project” can spiral. We built a single, quiet, interactive installer that does it all — and keeps the web UI HTTPS-only behind Caddy with Let’s Encrypt.

Today we’re open-sourcing it:
Source: https://git.coffeylabs.org/coffey-labs/ihasvpn


What it is

A hardened, production-minded deployment of WireGuard + wg-easy using Docker, fronted by Caddy with automatic Let’s Encrypt certificates and an explicit HTTP→HTTPS redirect. The installer is interactive (CLI or optional TUI), runs quietly after prompts with a percentage progress bar, and logs everything. It also sets up:

  • UFW firewall rules (22/tcp, 80/tcp, 443/tcp, WG UDP)
  • fail2ban jails for SSH and the Caddy UI
  • unattended-upgrades for security updates
  • Autostart for Docker, Caddy, and the compose stack
  • Backup/restore utility
  • Uninstall utility (safe & reversible)
  • Healthcheck tool (ports/services/TLS/DNS) with JSON & webhook support

Why you might want this

  • Security by default: UI bound to loopback; all external access is over HTTPS via Caddy (optionally with Basic Auth and IP allowlisting).
  • Fast & repeatable: A single command on a fresh Debian 13 host; re-run safely to update/repair.
  • Ops-friendly: Logs every step; includes timers/cron examples; exit codes for monitoring.

One-command install (on Debian 13)

# As root (or sudo -i), after cloning the repo:
git clone https://git.coffeylabs.org/coffey-labs/ihasvpn.git
cd WGX
sudo bash install-wg-easy.sh

You’ll choose TUI or CLI prompts, set your domain (e.g., vpn.example.com), email for ACME, and (optionally) enable Basic Auth, IP allowlist, UFW, fail2ban, and unattended-upgrades.

Ensure your DNS A/AAAA records point to the server before running, and that ports 80/443 are free.


What you get after install

  • Web UI: https://<your-domain>/ (wg-easy proxied via Caddy)
  • wg-easy bound privately on 127.0.0.1:<port>
  • Explicit 308 redirect from HTTP to HTTPS
  • HSTS & security headers
  • Optional Basic Auth: one more layer for the UI
  • Autostart via systemd + Docker restart policies

Included tools

Backup & restore

# Install helper
sudo install -m 0750 wg-easy-backup.sh /usr/local/sbin/wg-easy-backup.sh

# Backup (creates ./wg-easy-backup-*.tgz)
sudo wg-easy-backup.sh backup

# Include Caddy cert/key state (sensitive!)
sudo wg-easy-backup.sh backup --include-certs

# Restore from a tarball
sudo wg-easy-backup.sh restore ./wg-easy-backup-YYYYmmdd-HHMMSS.tgz

Uninstall (safe & interactive)

sudo install -m 0750 wg-easy-uninstall.sh /usr/local/sbin/wg-easy-uninstall.sh
sudo wg-easy-uninstall.sh

Lets you stop the stack, remove the compose unit, optionally remove configs, fail2ban snippets, Caddyfile entries, and even the packages — with backups.

Healthcheck (with JSON & webhook)

sudo install -m 0755 wg-easy-healthcheck.sh /usr/local/sbin/wg-easy-healthcheck.sh

# Human output + log
sudo wg-easy-healthcheck.sh --domain vpn.example.com

# JSON output + webhook (e.g., healthchecks.io)
sudo wg-easy-healthcheck.sh --domain vpn.example.com --json --webhook "https://hc-ping.com/UUID"

Checks Docker, Caddy, optional fail2ban/UFW/unattended-upgrades, wg-easy container, port listeners (80/443 + WG UDP), HTTP→HTTPS redirect, HTTPS reachability, TLS days to expiry, and DNS vs public IP. Exit codes: 0 OK, 1 WARNING, 2 CRITICAL, 3 UNKNOWN.


Create /etc/systemd/system/wg-easy-health.service:

[Unit]
Description=wg-easy stack healthcheck

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/wg-easy-healthcheck.sh --domain vpn.example.com --warn-days 30 --crit-days 10 --log /var/log/wg-easy-health.log

Create /etc/systemd/system/wg-easy-health.timer:

[Unit]
Description=Run wg-easy healthcheck every 15 minutes

[Timer]
OnBootSec=2m
OnUnitActiveSec=15m
AccuracySec=1m
Unit=wg-easy-health.service

[Install]
WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now wg-easy-health.timer

Under the hood

  • Docker Engine + compose plugin enabled at boot
  • wg-easy container with restart: unless-stopped
  • Optional systemd unit to re-up -d the compose stack on boot
  • Caddy (enabled) with ACME (staging/production) + security headers
  • UFW (optional) opens only SSH, HTTP(S), and the WG UDP port
  • fail2ban (optional) for SSH + Caddy HTTP errors (401/403)
  • unattended-upgrades (optional) with optional email notices
  • net.ipv4.ip_forward=1 (+ optional IPv6 forwarding)

Requirements & scope

  • OS: Debian 13 “Trixie” (Debian 12+ likely compatible)
  • Access: root/sudo on a clean server
  • Networking: DNS A/AAAA → server; ports 80/443 available externally

Get the code


License

All scripts are released under GPLv3.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; see the full license for details. https://www.gnu.org/licenses/