Setting Up a Secure VPN Server on Ubuntu and Docker (Windows)
Table of Contents
- Introduction
- Why Deploy a VPN, and Why Choose WireGuard
- Key Terms and Core Concepts
- Prerequisites
- Method 1: Native Deployment on Ubuntu Server
- Method 2: Docker Deployment on Windows (via Docker Desktop)
- Client Onboarding
- Diagnostics and Troubleshooting
- Conclusion
Introduction
WireGuard is a lightweight Virtual Private Network (VPN) protocol that supports both IPv4 and IPv6 connections. A VPN allows you to securely route your internet traffic through a private encrypted tunnel over untrusted networks, such as public Wi-Fi.
WireGuard uses modern cryptographic key pairs (public and private keys) to establish secure encrypted tunnels between peers.
Why Deploy a VPN, and Why Choose WireGuard?
In modern distributed systems, exposing internal services such as databases, APIs, or development environments directly to the public internet introduces significant security risks. A VPN mitigates this by creating a secure, encrypted tunnel that restricts access to authenticated devices only.
Traditional VPN solutions like OpenVPN and IPsec are powerful but often suffer from:
- Large and complex codebases
- Slow connection handshakes
- Higher resource consumption
WireGuard improves this model by being:
- Lightweight and kernel-level integrated
- Extremely fast with near-instant handshakes
- Built on modern cryptography (ChaCha20, Poly1305)
- Easy to audit due to its small codebase
This makes it ideal for cloud servers, homelabs, and containerized environments.
Key Terms and Core Concepts
WireGuard operates using a peer-to-peer model rather than a traditional client-server architecture.
| Term | Simple Definition | Practical Meaning |
|---|---|---|
| Peer | Any device connected using a cryptographic key | All devices are equal nodes in the VPN |
| Asymmetric Cryptography | Uses a private and public key pair | Private key stays secret, public key is shared |
| Allowed IPs | Defines what traffic is routed through a peer | Controls routing and access rules |
| Endpoint | Public IP and port of a peer | Where connections are initiated |
| IP Forwarding | Kernel feature allowing routing between networks | Required for internet sharing through VPN |
| NAT Masquerading | Translates private IPs to public IP | Enables internet access for VPN clients |
Prerequisites
- Machine to Run WireGuard You can use:
- Cloud VPS (Ubuntu 22.04+ recommended)
- Personal computer (Linux, Windows, macOS)
- Homelab server (PC, mini-PC, NAS)
Admin (sudo) access is required.
- Internet Connectivity A publicly reachable IP is required.
- Cloud servers → usually included
- Home/homelab → requires:
- Port forwarding OR
- Dynamic DNS (DDNS)
- Required Network Port
- UDP 51820 must be open
Examples:
- Cloud → Security group/firewall rules
- Home → Router port forwarding + OS firewall rule
- Windows Users (Optional) If using Windows:
- Install Docker Desktop
- Enable WSL 2 backend
🐧 Method 1: Native Deployment on Ubuntu Server
Step 1: Install WireGuard
First, ensure your local system repositories are updated and pull down the standard tools package.
sudo apt update && sudo apt upgrade -y
sudo apt install wireguard -y
Step 2: Generate Server Cryptographic Keys
WireGuard relies on asymmetric public/private key pairs for peer authentication. Let’s isolate our runtime directory and generate these keys securely:
### Move to the WireGuard directory and enforce strict file permissions
cd /etc/wireguard
sudo umask 077
# Generate private key and derive the public key from it
wg genkey | sudo tee private.key
sudo cat private.key | wg pubkey | sudo tee public.key
Example:
Step 3: Enable IP Forwarding
For your server to function as a gateway and route your client traffic out to the internet, you must explicitly enable IPv4 packet forwarding inside the Linux kernel.
Open /etc/sysctl.conf:
sudo nano /etc/sysctl.conf
Uncomment or add the following line:
net.ipv4.ip_forward=1
Apply the changes instantly:
sudo sysctl -p
Step 4: Create Server Config
Determine your physical public network interface identifier by pulling active route profiles:
ip route | grep default | awk '{print $5}'
(Common interface outputs include: eth0, enp1s0, wlan0, or ens3)
Example:
Cat and copy the output of /etc/wireguard/private.key . It will be used in the wireguard server interface configuration.
sudo cat /etc/wireguard/private.key
Example:
Create the main wireguard interface mapping file /etc/wireguard/wg0.conf:
sudo nano /etc/wireguard/wg0.conf
Server Configuration
Insert the following configuration into your WireGuard interface file, making sure to replace the placeholder values with those from your system. The PrivateKey identifies the WireGuard server and must be the private key you generated earlier. The Address defines the server’s internal VPN IP and subnet, while ListenPort specifies the UDP port WireGuard will use to accept incoming connections. The PostUp and PostDown rules automatically configure and clean up firewall and NAT settings when the tunnel starts and stops. These rules allow VPN traffic to be forwarded through the system and enable connected clients to access external networks by translating their private VPN IPs to the server’s outgoing network interface (for example, eth0). If your system uses a different network interface, ensure you update it accordingly to maintain proper routing and internet access.
[Interface]
PrivateKey=REPLACE_THIS_WITH_THE_OUTPUT_OF-/etc/wireguard/private.key-YOU_COPIED
Address=10.8.0.1/24
ListenPort=51820
# Automated routing engine (Swap 'eth0' with your actual interface string)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Example:
Step 5: Configure Firewall Policies (UFW)
Ensure the host-level firewall does not obstruct incoming cryptographic handshakes:
### Open WireGuard UDP port and protect your SSH management interface
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
### Reload firewall assets safely
sudo ufw disable && sudo ufw enable
Step 6: Start Wireguard
Bind the virtual network tunnel interface wg0 directly to the system initialization engine:
sudo systemctl start wg-quick@wg0
sudo systemctl enable wg-quick@wg0
Verify operational runtime status:
sudo systemctl status wg-quick@wg0
Example:
🐳 Method 2: Docker Deployment on Windows (via Docker Desktop)
This method runs WireGuard using Docker and wg-easy, simplifying management through a web UI.
Overview
Instead of manual installation, WireGuard runs inside a Docker container with a web-based dashboard.
Why Docker?
- Avoids Windows driver complexity
- Simplifies configuration
- Provides a clean web UI
- Uses WSL 2 for Linux networking layer
What is wg-easy?
A web-based interface that allows you to:
- Create VPN users
- Generate configs automatically
- Scan QR codes for mobile setup
- Manage peers visually
Step 1: Create Project Folder
mkdir C:\wireguard-server
cd C:\wireguard-server
Step 2: Docker Compose File
version: "3.8"
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: wg-easy
environment:
- WG_HOST=your.public.ip
- PASSWORD=StrongPasswordHere
- WG_DEFAULT_DNS=1.1.1.1,8.8.8.8
volumes:
- ./wg-easy:/etc/wireguard
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
Step 3: Start Container
docker compose up -d
Access:
http://localhost:51821
Client Onboarding
Native Ubuntu Client Generation Pattern
This section explains how to connect a new device (laptop or desktop) to your WireGuard VPN manually.
1. Get the Server Public Key
On the server machine, display and copy the public key:
cat /etc/wireguard/public.key
Example:
- This key identifies the server to all clients.
- You will use it in the client configuration under
PublicKey.
2. Create a Client Configuration File
On the client machine (or server for generation purposes), create a new file:
/etc/wireguard/client1.conf
This file defines how the client connects to the VPN.
3. Generate or Add Client Keys
Each client must have its own private key.
- PrivateKey (client): stays only on the client device
- PublicKey (client): is added to the server (not shown here but required) Generate private key and derive the public key from it This command are meant to generate key for the client/peer devicerr cd /etc/wireguard wg genkey | sudo tee client_private.key sudo cat private.key | wg pubkey | sudo tee client_public.key
4. Edit the client configuration file replacing the placeholders with the real values.
sudo nano /etc/wireguard/client1.conf
/etc/wireguard/client1.conf
[Interface]
Client’s private key (unique per device)
PrivateKey=<CLIENT_PRIVATE_KEY>
### VPN IP address assigned to this client
Address=10.8.0.2/32
DNS servers used while connected to VPN
DNS=1.1.1.1,8.8.8.8
[Peer]
Server public key (copied from server)
PublicKey=<SERVER_PUBLIC_KEY>
Public IP or domain of your VPN server
Endpoint=<SERVER_PUBLIC_WAN_IP>:51820
Route ALL traffic through VPN (full tunnel mode)
AllowedIPs=0.0.0.0/0
Keeps connection alive behind NAT/firewalls
PersistentKeepalive=25
Example:
5. Register this client identity with your active runtime server engine:
sudo wg set wg0 peer <CLIENT_PUBLIC_KEY> allowed-ips 10.8.0.2/24
6. Check the peer logs by running this command:
sudo wg show
Example:
Peer Configuration on Windows (Containerized Web Management Method)
This method uses a web-based WireGuard management interface (such as wg-easy) running inside a Docker container on Windows. It removes manual configuration complexity by providing a graphical dashboard for managing VPN clients.
1. Access the Web Dashboard
Open your browser and navigate to the WireGuard management interface:
- Example:
http://localhost:51821 - Or your server IP:
http://SERVER_IP:51821
This dashboard allows you to manage all VPN users (peers) visually.
2. Create a New Client (Peer)
Inside the dashboard:
- Click “New Client”
- Enter a clear and identifiable name (e.g.,
John-Laptop,Office-PC,Windows-Desktop)
This name is only for management purposes and helps you identify devices easily.
3. Generate Client Configuration
Once the client is created, the system automatically generates:
- A unique private key
- A VPN IP address
- A ready-to-use configuration profile
4. Connect Using QR Code or Configuration File
You now have two connection options:
Option A: QR Code (Recommended for Mobile Devices)
- Click “Show QR Code”
- Scan it using the WireGuard mobile app (iOS or Android)
- The VPN profile is imported automatically
Option B: Download Configuration File (For Windows/Linux/macOS)
- Click “Download Configuration”
- Save the
.conffile - Import it into the WireGuard desktop application
Steps for Windows WireGuard client:
- Open WireGuard application
- Click “Import tunnel from file”
- Select the downloaded
.conffile - Activate the tunnel
5. Activate the VPN
After importing:
- Click Activate
- The client will connect to the VPN server automatically
- Traffic will be routed based on the configuration (full or split tunnel)
Diagnostics and Troubleshooting
Issue 1: Connected but No Internet Traffic
Symptom:
The VPN shows as connected, but you cannot access external websites or send traffic through the tunnel.
Cause:
This usually happens when IP forwarding or NAT routing is not configured correctly.
Fix:
1. Verify IP forwarding is enabled:
cat /proc/sys/net/ipv4/ip_forward
- Expected output: 1
If the result is 0, enable it immediately.
2. Ensure your PostUp and PostDown rules match your real network interface (e.g., eth0, ens3, wlan0).
3. A mismatch here will break outbound routing.
Issue 2: VPN Connects but No Handshake Behind NAT (Home/Residential Networks)
Symptom:
The client connects but never completes a handshake, or connection drops frequently on unstable networks (e.g., mobile data, home Wi-Fi).
Cause:
Some routers and NAT devices silently drop inactive UDP sessions.
Fix:
Add the following line to the client configuration:
PersistentKeepalive = 25
What this does (simple explanation):
- Sends a small “heartbeat” every 25 seconds
- Keeps NAT/firewall sessions open
- Prevents connection timeouts on strict or unstable networks
Inspecting Live VPN Status (Ubuntu Server)
To check active peers and connection status in real time, use:
sudo wg show
This displays:
- Active peers
- Latest handshake time
- Data transfer (upload/download)
- Connection health
Conclusion
WireGuard provides a fast, secure, and lightweight approach to building modern VPN infrastructures across cloud, homelab, and enterprise environments. By combining cryptographic peer-to-peer connections, simple configuration, and efficient routing rules, it significantly reduces the complexity found in traditional VPN solutions while maintaining strong security and performance. The deployment approach outlined in this documentation is aligned with industry best practices as demonstrated in official implementation guides such as the DigitalOcean WireGuard setup tutorial, which provides a foundational reference for secure server and client configuration on Ubuntu systems .
This implementation and documentation have been developed and structured under Tuinnov8 Software Solutions, a software engineering company focused on building modern, scalable, and intelligent digital systems. You can learn more about our work and solutions at www.tuinnov8.com.








Top comments (1)
Was this helpful?