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

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:
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.
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.
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.
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
```
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 |
Download the png into your client PC: scp root@192.168.1.40:/root/config/wireguard/peer3/peer3.png /home/nmaximo7/peer3.png
Install WireGuard in your Android, scan the image peer3.png, enable the VPN, and test the IP.
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.