JustToThePoint English Website Version
JustToThePoint en español

How to Deploy a Self-Hosted WireGuard VPN on Proxmox with Docker and Portainer

When the world says, ‘Give up,’ hope whispers, ‘Try it one more time’, Lyndon B. Johnson

VPN

VPN. Definition and benefits.

A Virtual Private Network (VPN) is an encrypted connection (aka an encrypted tunnel) that allows your device to communicate over the internet through a secure server. When connected to a VPN, all of your internet traffic is routed or travels through this tunnel, effectively masking your device’s IP address and location so

By utilizing a VPN, you will gain some benefits:

  1. Install and load the module WireGuard on Proxmox. SSH into Proxmox host, run:

    # Update package lists
    sudo apt update
    
    # Install WireGuard tools and kernel module
    sudo apt install -y wireguard
    
    # Load the WireGuard kernel module
    modprobe wireguard
    
    # Verify it's loaded
    lsmod | grep wireguard
    

    The Docker container relies on the host’s kernel module for WireGuard functionality.

  2. Container Network Rules. On the Proxmox host…

    # IP forwarding is explicitly enabled
    sudo sysctl -w net.ipv4.ip_forward=1
    
    # Persist across reboots, vi /etc/sysctl.conf:
    net.ipv4.ip_forward=1
    

    And a Network Address Translation/Masquerading (NAT) is applied and configured to persist across reboots. This MASQUERADE rule is vital for clients connected to the WireGuard VPN, as it allows their private VPN subnet traffic (10.13.13.0/24) to be translated to the Proxmox host’s public IP, enabling internet access through the host’s network interface.

    The WireGuard container’s INTERNAL_SUBNET is defined as 10.13.13.0/24, which aligns with this NAT rule.

    iptables -t nat -A POSTROUTING -s 10.13.13.0/24 -o eth0 -j MASQUERADE
    
    # Persist iptables rules
    apt update && apt install iptables-persistent
    sudo netfilter-persistent save
    # It will save your current rules to /etc/iptables/rules.v4 automatically.
    
    # Validate NAT Masquerade Rule
    root@myserver:/home/nmaximo7/homelab/mydockers# iptables -t nat -L POSTROUTING -v -n
    Chain POSTROUTING (policy ACCEPT 60 packets, 3796 bytes)
    pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  0    --  *      eth0    10.13.13.0/24        0.0.0.0/0
    

    We need MASQUERADE so that traffic from our VPN clients (10.13.13.x) to the internet is NAT’d to your host’s public IP.

    net.ipv4.conf.all.src_valid_mark is a setting related to network packet validation. Setting it to 1 enables source address validation on all network interfaces. This helps prevent routing loops and other network issues, especially in scenarios like VPN connections. In the context of WireGuard, this changeallows WireGuard to properly handle and route packets originating from the VPN interface, ensuring that the source address is validated.

    # SSH in the container.
    # This change allows WireGuard to properly handle and route packets originating from the VPN interface, ensuring that the source address is validated.
    sudo sysctl -w net.ipv4.conf.all.src_valid_mark=1
    # By adding the line to sysctl.conf, you ensure that this setting persists across reboots.
    echo 'net.ipv4.conf.all.src_valid_mark=1' | sudo tee -a /etc/sysctl.conf
    
    # Even though the container ran sysctl -w net.ipv4.conf.all.src_valid_mark=1
    # you still need to turn on forwarding on the CT itself.
    # IP forwarding allows the system to route traffic between different networks.
    # As root in the CT:
    sysctl -w net.ipv4.ip_forward=1
    
    # To make it survive a reboot, add to /etc/sysctl.conf:
    echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
    
    # Check your FORWARD chain policy & rules
    root@portainer:~# iptables -L FORWARD -n --line-numbers
    Chain FORWARD (policy DROP)
    num  target     prot opt source               destination
    1    DOCKER-USER  0    --  0.0.0.0/0            0.0.0.0/0
    2    DOCKER-FORWARD  0    --  0.0.0.0/0            0.0.0.0/0
    

    Docker often sets iptables -P FORWARD DROP. You need to allow packets from wg0 (10.13.13.1/32) to eth0 and vice-versa.

    # If policy is DROP, set it to ACCEPT
    root@portainer:~# iptables -P FORWARD ACCEPT
    root@portainer:~# iptables -L FORWARD -n --line-numbers
    # Warning: iptables-legacy tables present, use iptables-legacy to see them
    Chain FORWARD (policy ACCEPT)
    num  target     prot opt source               destination
    1    DOCKER-USER  0    --  0.0.0.0/0            0.0.0.0/0
    2    DOCKER-FORWARD  0    --  0.0.0.0/0            0.0.0.0/0
    

    If you cannot access ProxMox Web UI, access ssh into it, but first copy your client public key into your CT: ssh-copy-id root@192.168.1.40. If you have already use this IP, ssh-keygen -f "/home/nmaximo7/.ssh/known_hosts" -R "192.168.1.40", then repeat (ssh-copy-id root@192.168.1.40) and ssh into your Portainer CT (ssh root@192.168.1.40), and restart Portainer sudo docker restart myportainer.

  3. Create a stack for Duckdns, a free service which will point a DNS (sub domains of duckdns.org) to an IP of your choice. The service is completely free, and doesn’t require reactivation or forum posts to maintain its existence. In Portainer, Add stack, Name (duckdns), Web editor, paste the YAML, then Deploy:

    services:
      duckdns:
      image: lscr.io/linuxserver/duckdns:latest
      container_name: duckdns
      network_mode: host            # ← binds directly to host network
      environment:
        - PUID=1000                 # optional: if you want files owned by a non-root
        - PGID=1000                 # optional
        - TZ=Europe/Madrid          # change to your timezone
        - SUBDOMAINS=justtothepoint # ← only the subdomain part, not the full FQDN
        - TOKEN=YOUR-DUCKDNS-TOKEN
        - UPDATE_IP=auto            # or ipv4
        - LOG_FILE=false
      volumes:
        - /root/config/duckdns:/config  # store config & logs here
      restart: unless-stopped
    

    Confirm your DuckDNS domain resolves to your public IP: dig +short justtothepoint.duckdns.org. It should match the IP you see on whatismyip.com.

  4. Create a stack for WireGuard. In Portainer, Add stack, Name (duckdns), Web editor, paste the YAML, then Deploy:

    services:
      wireguard:
        image: lscr.io/linuxserver/wireguard:latest
        container_name: wireguard
        privileged: true
        cap_add:
          - NET_ADMIN
        network_mode: host
        # It allows the container to accurately detect and update the host's true public IP address.
        environment:
          - PUID=1000
          - PGID=1000
          - TZ=Europe/Madrid
          - SERVERURL=justtothepoint.duckdns.org
          # Use a dynamic DNS service to maintain a persistent endpoint for the VPN server. This is generally advisable for environments with dynamic public IP addresses.
          - SERVERPORT=51820 #optional
          - PEERS=3
          # It will automatically generate configurations for up to three client peers.
          - PEERDNS=8.8.8.8 #optional
          # Another option is PEERDNS=auto
          # or another public DNS in the container’s env.
          - INTERNAL_SUBNET=10.13.13.0/24 #optional
          - ALLOWEDIPS=0.0.0.0/0 #optional
        volumes:
          - /root/config/wireguard:/config
          - /lib/modules:/lib/modules:ro #optional
        ports:
          - 51820:51820/udp
        restart: unless-stopped
        ```
    
  5. Router Port Forwarding. I have established a port forwarding rule on my router. This rule directs incoming UDP traffic on port 51820 from the public internet to the Proxmox host’s internal IP address, 192.168.1.40.

    Name Protocol External Port Internal Port Internal IP
    wireguard UDP 51820 51820 192.168.1.40
  6. Download the png into your client PC: scp root@192.168.1.40:/root/config/wireguard/peer3/peer3.png /home/nmaximo7/peer3.png

  7. Install WireGuard in your Android, scan the image peer3.png, enable the VPN, and test the IP.

  8. Troubleshooting: Check the generated ~/config/wireguard/peer3/peer3.conf inside the container:

    [Interface]
    Address = 10.13.13.4
    PrivateKey = IKva5iXtJ6cpPst7Ab40PQwTBmsMDoj+lCdiGIgK/Us=
    ListenPort = 51820
    DNS = 8.8.8.8
    
    [Peer]
    PublicKey = sWKERcjX7yM+wAb4+HREXV2/qOIIHrzIhs/+Knt9TWw=
    PresharedKey = cfzPW4j6+02UOxz9fqr8SZT/E0H4tdKU7MA41x5x9Tc=
    Endpoint = justtothepoint.duckdns.org:51820
    AllowedIPs = 0.0.0.0/0
    

    Ensure it has [Interface] and [Peer] sections, a valid Endpoint = justtothepoint.duckdns.org:51820, and AllowedIPs = 0.0.0.0/0.

    # Debug WireGuard Container
    # Check live logs:
    docker logs -f wireguard
    [...]
    User UID:    1000
    User GID:    1000
    ───────────────────────────────────────
    Linuxserver.io version: 1.0.20210914-r4-ls77
    Build-date: 2025-06-12T11:25:49+00:00
    ───────────────────────────────────────
    
    Uname info: Linux portainer 6.8.12-11-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-11 (2025-05-22T09:39Z) x86_64 GNU/Linux
    **** It seems the wireguard module is already active. Skipping kernel header install and module compilation. ****
    **** Server mode is selected ****
    **** External server address is set to justtothepoint.duckdns.org ****
    **** External server port is set to 51820. ****
    **** Internal subnet is set to 10.13.13.0/24 ****
    **** AllowedIPs for peers 0.0.0.0/0 ****
    **** Peer DNS servers will be set to 8.8.8.8 ****
    

    You may also want to clear the WireGuard app cache and app data. Swipe up from the middle of the home screen and type Settings in the Search box, tap the Apps option, App Management, WireGuard, Storage usage, and tap Clear data and Clear cache.

Biography

  1. 7 Docker Basics for Beginners
  2. Wireguard
  3. TechHutTV’s github, homelab.
  4. TechHutTV’s YouTube, You NEED to setup Gluetun! (Route Your Docker Containers Through a VPN).
Bitcoin donation

JustToThePoint Copyright © 2011 - 2025 Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun. Social Issues, Join us.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.