Easy Self-Hosted WireGuard VPN (with Docker)

Stop trusting sketchy public Wi-Fi and overpriced commercial VPN providers. If you have a Virtual Private Server (VPS), you are ten minutes away from having your own blazing-fast, private VPN using the modern WireGuard protocol.

While WireGuard is notoriously difficult to configure manually, we are going to use Docker and a fantastic tool called wg-easy. This approach gives you a clean installation, easy updates, and a beautiful web dashboard to manage your devices with QR codes.

Why Use Docker for This?

You could install WireGuard directly onto your server’s operating system, but Docker is better for a few reasons:

  • Cleanliness: It keeps all the VPN dependencies isolated in a container. Your main server OS stays clean.
  • Portability: Moving servers? Just copy one folder, run one command, and your VPN is back up.
  • Ease of Use: The wg-easy container bundles WireGuard with a web UI, saving you from messing with complex config files manually.

Prerequisites

  • A VPS running Linux (Ubuntu 22.04/24.04 recommended).
  • Access to the terminal via SSH.
  • Your VPS Public IP address.

Step 1: Install Docker

First, ensure your server is updated, and Docker is installed. Run these commands one by one:

# Update package list
sudo apt update && sudo apt upgrade -y

# Install Docker using their official convenience script
curl -fsSL https://get.docker.com | sh

# Verify it's running
sudo systemctl status docker

Step 2: Generate Your Secure Password Hash

This is the most critical step.

Recent security updates to wg-easy forbid putting plain-text passwords in configuration files. You must generate a secure “bcrypt hash” of your desired password.

Run this temporary command. Replace YOUR_DESIRED_PASSWORD with the actual password you want to use for the Web UI login.

docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw 'YOUR_DESIRED_PASSWORD'

The output will look something like this. Copy it to a notepad temporarily: PASSWORD_HASH='$2b$12$7K1/....longstring....'


Step 3: Create the Configuration File

We will use Docker Compose to define our VPN service. This makes it easy to manage.

Create a folder and open a new configuration file:

mkdir ~/wireguard
cd ~/wireguard
nano docker-compose.yml

Paste in the following configuration.

IMPORTANT CONFIGURATION RULES:

  • Replace YOUR_VPS_IP with your server’s actual IP.
  • Replace the PASSWORD_HASH value with the one you generated in Step 2.
  • CRITICAL: You must change every single $ sign in your hash to a double $$. If you don’t, Docker will break your password.
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    environment:
      # REPLACE WITH YOUR VPS PUBLIC IP
      - WG_HOST=YOUR_VPS_IP
      # Paste your hash below. ENSURE YOU USE DOUBLE $$ SIGNS!
      # Example: $$2b$$12$$coPqCsPtcFO.Ab99xylBNOW4...
      - PASSWORD_HASH=PASTE_YOUR_HASH_HERE_WITH_DOUBLE_DOLLAR_SIGNS
      # Optional: Use Cloudflare DNS for speed and privacy
      - WG_DEFAULT_DNS=1.1.1.1, 1.0.0.1
    volumes:
      - .:/etc/wireguard
    ports:
      - "51820:51820/udp" # The VPN tunnel port
      - "51821:51821/tcp" # The Web UI Dashboard port
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1

Save the file by pressing CTRL+X, then Y, then Enter.


Step 4: Start the VPN and Open the Firewall

Start the Docker container in the background:

docker compose up -d

Configure your firewall (UFW) to allow the necessary traffic. Do not skip the SSH allowance or you will lock yourself out!

ufw allow 22/tcp           # Allow SSH
ufw allow 51820/udp        # Allow VPN traffic
ufw allow 51821/tcp        # Allow Web UI access
ufw enable                 # Turn firewall on

Step 5: Connect Your Devices

  1. Open your web browser and navigate to: http://YOUR_VPS_IP:51821 (Note: Make sure you use http, not https for now).
  2. Log in using the original plain-text password you used in Step 2 to generate the hash.
  3. Click “New Client” and name it (e.g., “iPhone”).
  4. Click the QR Code icon.
  5. Open the official WireGuard App on your phone, tap the + sign, scan the QR code, and activate the tunnel.

You are now connected securely through your own private server!


Troubleshooting Common Errors

If your container fails to start, check the logs using docker compose logs -f.

Error 1: “DO NOT USE PASSWORD ENVIRONMENT VARIABLE”

The Cause: You are using an older configuration format. The Fix: In your docker-compose.yml, ensure the environment variable is named PASSWORD_HASH, not just PASSWORD.

Error 2: “Variable is not set. Defaulting to a blank string.”

The Cause: Docker thinks the $ signs in your password hash are trying to reference variables. The Fix: You didn’t escape the dollar signs. Edit your docker-compose.yml. Everywhere your hash has a $, change it to $$.

  • Wrong: $2b$12$...
  • Right: $$2b$$12$$...

After fixing any errors in the YAML file, always apply the changes by running:

docker compose up -d --force-recreate

🔒 Bonus: How to Make Your Dashboard 100% Invisible

By default, Docker exposes your login page to the whole internet. Even with a strong password, this isn’t ideal.

Since you have a VPN, you can do something clever: Hide the dashboard so it only exists inside the VPN tunnel.

Step 1: Edit your config Open your docker-compose.yml file again.

Step 2: Remove the public port Find the ports section. You need to comment out or remove the line that exposes port 51821.

YAML

    
    ports:
      - "51820:51820/udp" # Keep the VPN port open!
      # - "51821:51821/tcp" # <--- Add a # to disable public access

Step 3: Update the container Apply the change:

Bash

docker compose up -d --force-recreate

Step 4: Access it securely Now, you cannot access the dashboard via your Public IP anymore. Instead:

  1. Connect to your WireGuard VPN on your device.
  2. Open your browser and go to: http://10.8.0.1:51821 (Note: 10.8.0.1 is the default internal IP for the VPN container).

Now you have military-grade security: A dashboard that is completely invisible to the outside world!