Self-Host WireGuard VPN on Your VPS with wg-easy
In this guide, we'll set up a WireGuard VPN server on a VPS using Docker, secure the admin panel behind an SSH tunnel, and create client configs via the API.
Snehasis Ghosh
Self-Host WireGuard VPN on Your VPS with wg-easy
Running your own VPN gives you full control over your traffic — no third-party logging, no bandwidth limits, and a private tunnel you can share with your devices or team. WireGuard is fast, modern, and lightweight, and wg-easy wraps it with a clean web UI for managing clients.
In this guide, we'll set up a WireGuard VPN server on a VPS using Docker, secure the admin panel behind an SSH tunnel, and create client configs via the API.
Prerequisites
- A VPS (Ubuntu 22.04+ recommended) with at least 512MB RAM
- A public IP address for your VPS
- SSH access to the server
Step 1 — Connect to Your Server
ssh root@YOUR_SERVER_IP
Replace YOUR_SERVER_IP with your VPS public IP address.
Step 2 — Update the System
apt update && apt upgrade -y
Step 3 — Install Docker
For a quick installation, use Docker's convenience script:
curl -sSL https://get.docker.com | sh
Verify the installation:
docker --version
docker compose version
If both commands return version numbers, Docker is ready.
Step 4 — Create the wg-easy Project
Create a dedicated directory:
mkdir -p /opt/wg-easy && cd /opt/wg-easy
Create the Docker Compose file:
nano docker-compose.yml
Paste the following configuration:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
environment:
- PORT=51821
- HOST=0.0.0.0
- INSECURE=true
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
volumes:
etc_wireguard:
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64
What's happening here?
| Setting | Purpose |
|---|---|
51820/udp | WireGuard tunnel port (public) |
127.0.0.1:51821/tcp | Web UI bound to localhost only (private) |
INSECURE=true | Allows HTTP for the web panel (safe since it's localhost-only) |
NET_ADMIN, SYS_MODULE | Required Linux capabilities for WireGuard |
| IPv6 config | Enables dual-stack VPN support |
Security note: Port 51821 is intentionally bound to
127.0.0.1. The admin panel is never exposed to the internet.
Step 5 — Configure the Firewall
Allow only SSH and the WireGuard tunnel port:
ufw allow OpenSSH
ufw allow 51820/udp
ufw enable
ufw status
We deliberately do not open port 51821 — the web panel stays private and is only accessible via SSH tunnel.
Step 6 — Start the VPN Server
docker compose up -d
Verify the container is running:
docker ps
You should see a container named wg-easy with status "Up". Check the logs if needed:
docker logs wg-easy
Step 7 — Access the Web Panel via SSH Tunnel
Since the web UI is only available on localhost, we need to forward the port through SSH.
On your local machine, open a new terminal and run:
ssh -L 51821:localhost:51821 root@YOUR_SERVER_IP
Keep this terminal open, then navigate to:
http://localhost:51821
You'll see the wg-easy setup page. Create your admin credentials.
When prompted for the host, enter your VPS public IP (or domain if you have one). For the port, keep the default:
51820
Step 8 — Create Clients via the Web UI
The simplest way to add devices is through the web panel:
- Click "+ New" in the dashboard
- Give the client a name (e.g.,
my-phone,laptop-work) - Download the
.conffile or scan the QR code with the WireGuard app
Step 9 — Create Clients via the API (Optional)
If you prefer automation or need to script client creation, wg-easy exposes a REST API.
Create a new client
curl -u "admin:YOUR_PASSWORD" \
-H "Content-Type: application/json" \
-X POST \
-d '{"name": "user_1001", "expiresAt": null}' \
http://127.0.0.1:51821/api/client
Example response:
{
"success": true,
"clientId": 12
}
Download the client config file
curl -u "admin:YOUR_PASSWORD" \
-o user_1001.conf \
http://127.0.0.1:51821/api/client/12/configuration
Get a QR code (for mobile devices)
curl -u "admin:YOUR_PASSWORD" \
-o user_1001.svg \
http://127.0.0.1:51821/api/client/12/qrcode.svg
Replace
12with theclientIdreturned from the create step, andYOUR_PASSWORDwith your admin password.
Connecting Devices
- Install the WireGuard app on your device
- Import the
.conffile or scan the QR code - Toggle the connection on — you're now routing through your VPS
You can verify by checking your IP:
curl ifconfig.me
It should return your VPS IP address.
Troubleshooting
Container won't start?
- Check logs:
docker logs wg-easy - Ensure
/lib/modulesexists on the host (needed for WireGuard kernel module)
Can't connect to VPN?
- Verify port 51820/udp is open:
ufw status - Ensure the host IP in wg-easy settings matches your actual VPS public IP
Web panel not loading via tunnel?
- Make sure the SSH tunnel is active in your terminal
- Confirm the container is running:
docker ps
Handshake but no traffic?
- Check that IP forwarding sysctls are applied:
docker inspect wg-easy | grep -A5 Sysctls
Wrapping Up
You now have a self-hosted WireGuard VPN with a web dashboard — zero subscription fees, full control over your data, and client management through a clean UI or API. The SSH tunnel approach keeps the admin panel completely off the public internet without needing additional auth layers or reverse proxies.
Add devices as you need them, revoke access with one click, and sleep well knowing your traffic isn't being logged by anyone but you.

