WG-Easy

A Turn-Key, Hardened WireGuard Stack for Debian 13

Introducing the WG-Easy Installer by LINUXexpert.org

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:
👉 GitHub: https://github.com/LINUXexpert-org/WG-Easy-Installer


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://github.com/LINUXexpert-org/WG-Easy-Installer.git
cd WG-Easy-Installer
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.


Systemd timer (recommended) for health checks

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/

Other Recent Posts