Self-hosted WireGuard + AdGuard stack with one-command setup on Ubuntu 24.04.
CustomVPN works on any cloud provider (DigitalOcean, AWS, GCP, Azure, Hetzner, Vultr, etc.) and any region. Pick the region based on the egress location you want.
- WireGuard VPN server on your VPS
- DNS ad/tracker blocking with AdGuard Home
- Private-by-default dashboards (access via SSH tunnel)
- Weekly updates + nightly backups (via systemd timers)
- A VPS running Ubuntu 24.04 (1 vCPU / 1 GB RAM minimum)
- SSH access to the server
- Cloud firewall/security group allows:
22/tcp51820/udp
Create an Ubuntu 24.04 VPS on any provider and note its public IP.
ssh root@YOUR_SERVER_IPgit clone https://github.com/YOUR_USERNAME/customvpn.git /opt/customvpn
cd /opt/customvpnOption A (default, recommended): use prebuilt upstream images
chmod +x setup.sh
./setup.sh --quick --use-prebuilt-imagesOption B: build images locally from this repo, then run them
chmod +x setup.sh
./setup.sh --quick --build-local-imagesWhat setup does:
- installs Docker + required tools
- configures firewall and host settings
- generates
.envinteractively - starts
wg-easy+adguard - either pulls prebuilt images or builds local images (based on your option)
- installs update/backup timers
On your local machine:
ssh -L 51821:127.0.0.1:51821 -L 3000:127.0.0.1:3000 root@YOUR_SERVER_IPThen open:
http://127.0.0.1:51821(wg-easy)http://127.0.0.1:3000(AdGuard)
Windows PowerShell (OpenSSH) uses the same command.
- Open wg-easy:
http://127.0.0.1:51821 - Click
+ New - Enter a device name
- Scan QR with WireGuard app
/opt/customvpn/scripts/healthcheck.sh- iPhone/iPad: install WireGuard app, scan QR
- Android: install WireGuard app, scan QR
- Mac/Windows/Linux: install WireGuard desktop app, import config/QR
- Apple TV (tvOS 17+): install WireGuard app, import profile
Default mode keeps dashboards local-only. To expose dashboards publicly:
cd /opt/customvpn
./setup.sh --advanced --public-dashboards --regen-envYou must provide an IP/CIDR allowlist during env generation.
To switch between prebuilt and local-build modes, regenerate .env:
cd /opt/customvpn
./setup.sh --regen-env --use-prebuilt-images
# or
./setup.sh --regen-env --build-local-images- Stack status:
cd /opt/customvpn && docker compose ps
- Logs:
cd /opt/customvpn && docker compose logs -f --tail=200
- Health check:
/opt/customvpn/scripts/healthcheck.sh
- Manual update:
/opt/customvpn/scripts/update.sh
- Backup now:
/opt/customvpn/scripts/backup.sh
- Restore backup:
/opt/customvpn/scripts/restore.sh --file /path/to/backup.tar.gz.enc --yes
customvpn-update.timer(weekly updates)customvpn-backup.timer(nightly backup)customvpn-docker-fw.service(Docker firewall policy)
Check timers:
systemctl list-timers | grep customvpnCreate /etc/customvpn/backup.env:
cat >/etc/customvpn/backup.env <<'EOFCONF'
BACKUP_DIR=/var/backups/customvpn
BACKUP_PASSPHRASE=CHANGE_ME
# scp destination OR rclone remote (prefix with rclone:)
OFFSITE_TARGET=user@backup-host:/srv/customvpn/
EOFCONF
chmod 600 /etc/customvpn/backup.envdocker inspect customvpn-adguard --format '{{.State.Health.Status}}'
dig @10.8.1.10 google.com- Verify SSH tunnel is still active
- Check
.envvalues:WG_UI_BIND,ADGUARD_UI_BIND
cd /opt/customvpn
./setup.sh --regen-envsystemctl restart customvpn-docker-fw.servicecd /opt/customvpn
./setup.sh --uninstall