How to Set Up a WireGuard VPN Server on Raspberry Pi Using Docker

How to Set Up a WireGuard VPN Server on Raspberry Pi Using Docker

Running your own VPN server at home gives you secure, encrypted access to your local network from anywhere in the world. WireGuard is a modern, high-performance VPN protocol that is lightweight enough to run perfectly on a Raspberry Pi.

In this guide, we will use the linuxserver/wireguard Docker image to get a WireGuard server running quickly and reliably.


What is WireGuard?

WireGuard is a fast, modern VPN tunnel that uses state-of-the-art cryptography. Compared to OpenVPN or IPSec, WireGuard has a much smaller codebase, better performance, and simpler configuration. It is built into the Linux kernel and is ideal for low-powered devices like the Raspberry Pi.


Prerequisites

Before starting, make sure you have:

  • A Raspberry Pi (3B+ or newer) running Raspberry Pi OS
  • Docker and Docker Compose installed (see our Docker setup guide)
  • A static local IP address on your Pi
  • Access to your router's admin panel for port forwarding
  • Your public IP address or a dynamic DNS hostname

Step 1: Create a Project Directory

Bash
mkdir ~/wireguard && cd ~/wireguard

Step 2: Create the Docker Compose File

Create a docker-compose.yml file:

Bash
nano docker-compose.yml

Paste the following configuration:

YAML
services:
  wireguard:
    image: linuxserver/wireguard
    container_name: wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - SERVERURL=auto
      - SERVERPORT=51820
      - PEERS=phone,laptop,tablet
      - PEERDNS=auto
      - INTERNAL_SUBNET=10.13.13.0
      - ALLOWEDIPS=0.0.0.0/0
    volumes:
      - ./config:/config
      - /lib/modules:/lib/modules
    ports:
      - 51820:51820/udp
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

Important notes about this configuration:

  • SERVERURL: Set this to your public IP or dynamic DNS hostname. Using auto will attempt to detect it automatically.
  • PEERS: A comma-separated list of client names. Each one generates a separate config file.
  • INTERNAL_SUBNET: The private subnet WireGuard uses internally.
  • cap_add: WireGuard requires NET_ADMIN and SYS_MODULE capabilities to create network interfaces.
  • sysctls: Required for proper packet routing.

Step 3: Start WireGuard

Bash
docker-compose up -d

Wait a moment for the container to generate all peer configurations. You can watch the logs:

Bash
docker logs -f wireguard

Step 4: Retrieve Client Configurations

The container automatically generates config files for each peer you defined. Find them in the config directory:

Bash
ls ~/wireguard/config/peer_phone/
ls ~/wireguard/config/peer_laptop/
ls ~/wireguard/config/peer_tablet/

Each folder contains a peer_phone.conf file (or similar) and a QR code PNG image.

To display the QR code in your terminal for easy mobile setup:

Bash
docker exec -it wireguard /app/show-peer phone

This prints a QR code that you can scan directly with the WireGuard mobile app.

Step 5: Connect from Your Devices

On a Phone (iOS or Android)

  1. Install the WireGuard app from the App Store or Google Play.
  2. Tap the + button and select Create from QR code.
  3. Scan the QR code displayed in the terminal from Step 4.
  4. Toggle the tunnel on. You are now connected.

On a Laptop (Windows, macOS, or Linux)

  1. Install the WireGuard client from wireguard.com/install.
  2. Copy the .conf file from ~/wireguard/config/peer_laptop/ to your laptop.
  3. Import the tunnel configuration file in the WireGuard client.
  4. Activate the tunnel.

Step 6: Configure Port Forwarding on Your Router

For external access, you must forward UDP port 51820 on your router to your Raspberry Pi's local IP address.

  1. Log into your router's admin panel (typically at 192.168.1.1).
  2. Find the Port Forwarding section.
  3. Create a new rule:
    • Protocol: UDP
    • External Port: 51820
    • Internal IP: Your Pi's local IP (e.g., 192.168.1.50)
    • Internal Port: 51820
  4. Save and apply the rule.

If your ISP gives you a dynamic public IP, consider setting up a dynamic DNS service like DuckDNS or No-IP and using that hostname as your SERVERURL.


Firewall Notes

If you are running ufw on your Pi, allow the WireGuard port:

Bash
sudo ufw allow 51820/udp

Adding More Peers Later

To add new clients after the initial setup, update the PEERS environment variable in your docker-compose.yml and recreate the container:

Bash
docker-compose down
docker-compose up -d

New peer configs will be generated automatically without affecting existing ones.


Troubleshooting

  • Connection times out: Verify port forwarding is configured correctly. Test from outside your home network (e.g., using mobile data).
  • Handshake but no traffic: Check that ALLOWEDIPS is set to 0.0.0.0/0 for full tunnel, or your specific subnet for split tunnel.
  • Container won't start: Ensure /lib/modules is mounted and the wireguard kernel module is available. Run sudo modprobe wireguard on the host if needed.
  • Check logs: Run docker logs wireguard to see detailed output from the container.
  • DNS not resolving: Make sure PEERDNS is set correctly. You can point it to your Pi-hole if you have one running.

Conclusion

With WireGuard running on your Raspberry Pi via Docker, you have a lightweight, high-performance VPN server that lets you securely access your home network from anywhere. The linuxserver/wireguard image makes the setup straightforward, handling key generation and peer configuration automatically.