Exposing your homelab to the internet is risky. Port forwarding your home router, poking holes in firewalls, juggling dynamic IPs, dealing with CGNAT. It gets messy fast.
A much cleaner approach: install WireGuard on a cheap VPS, create a secure tunnel back to your homelab, and let the VPS act as your public gateway. From there, layer in a Caddy Docker proxy for HTTPS, controlled WireGuard port forwarding with iptables, and even a dual reverse proxy architecture for clean service routing.
I’ve done this more times than I can count. Getting WireGuard running was straightforward. The part that took hours to debug was iptables NAT and an MTU mismatch silently killing large packet transfers. Once I understood what was happening, the fix was two lines. Finding it was painful.
This guide walks through the entire setup, including the parts that usually break.
Architecture Overview: VPS Gateway + Homelab Client
Here’s what we’re building:
- VPS (public IP)
- WireGuard server
- Caddy reverse proxy (Docker)
- iptables NAT and optional port forwarding
- Homelab (behind NAT or CGNAT)
- WireGuard client
- Local services (Jellyfin, Nextcloud, dashboards, etc.)
- Optional internal reverse proxy (Nginx, Traefik, Caddy)
Traffic flow:
Internet -> VPS (Caddy) -> WireGuard tunnel -> Homelab service
Your home network stays private. No router port forwarding required.
Prepare the VPS (Ubuntu 24.04+)
This guide assumes Ubuntu 24.04 or 22.04 on your VPS. Debian 12 works too with the same commands.
Update the System
sudo apt update && sudo apt upgrade -y
Install WireGuard
sudo apt install wireguard -y
Recent Ubuntu releases include the kernel module out of the box.
If you’re on an old OpenVZ VPS and modprobe wireguard fails, you may need wireguard-go. Most modern VPS providers (Hetzner, Linode, Vultr, DigitalOcean) support the native kernel module. Prefer that.
Generate WireGuard Keys
On the VPS:
wg genkey | tee server_private.key | wg pubkey > server_public.key
server_private.keystays secret. Never paste it into chat, docs, or ticket systems.server_public.keywill be shared with the homelab.
Repeat the same process later on the homelab.
Lock down the private key:
chmod 600 server_private.key
Configure the VPS WireGuard Server
Create the config file:
sudo nano /etc/wireguard/wg0.conf
Example:
[Interface]
Address = 10.0.0.1/24
PrivateKey = <server_private_key>
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; \
iptables -A FORWARD -o wg0 -j ACCEPT; \
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; \
iptables -D FORWARD -o wg0 -j ACCEPT; \
iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
What This Does
10.0.0.1/24is the tunnel subnet.MASQUERADEenables outbound NAT on the VPS public interface.FORWARDrules allow traffic through the tunnel.
Avoid overlapping subnets. If your home LAN is 10.0.0.0/24, pick something else like 10.13.13.0/24.
If your VPS public interface is not eth0, check with:
ip addr
Use the correct interface name in the MASQUERADE rule. Many VPS providers now use ens3, ens18, or similar.
A Note on Firewall Coexistence
These iptables rules assume a clean firewall. If your VPS already runs UFW or if your provider uses nftables under the hood, those FORWARD rules could conflict with existing policies.
Check first:
sudo ufw status
sudo nft list ruleset
If UFW is active, you’ll need to allow forwarding in /etc/ufw/sysctl.conf (set net/ipv4/ip_forward=1) and add your FORWARD rules to /etc/ufw/before.rules. If nftables is running, consider writing your rules in nft syntax instead. On many VPS images, iptables is a compatibility layer for nftables. Your commands will still work, but it’s worth knowing what’s underneath.
Also check your cloud provider’s security group or firewall panel. Some providers (AWS, Oracle Cloud, Hetzner Cloud) have external firewalls that block traffic regardless of what you configure on the box itself.
Enable IP Forwarding
Temporarily:
sudo sysctl -w net.ipv4.ip_forward=1
Persist:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
Without this, the VPS won’t route traffic between wg0 and its public interface.
Start WireGuard
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Open UDP 51820 in your VPS firewall or cloud security group.
Verify:
sudo wg show
Configure the Homelab Client
On your homelab machine:
sudo apt install wireguard -y
Generate keys:
wg genkey | tee client_private.key | wg pubkey > client_public.key
chmod 600 client_private.key
Create the config:
sudo nano /etc/wireguard/wg0.conf
Example:
[Interface]
Address = 10.0.0.2/24
PrivateKey = <client_private_key>
DNS = 1.1.1.1
MTU = 1420
[Peer]
PublicKey = <server_public_key>
Endpoint = <VPS_PUBLIC_IP>:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
Important Options
AllowedIPs = 10.0.0.0/24gives you a split tunnel. Only tunnel traffic destined for that subnet goes through the VPS.AllowedIPs = 0.0.0.0/0creates a full tunnel. All traffic routes through the VPS.PersistentKeepalive = 25is required if your homelab is behind NAT or CGNAT. Without it, the NAT mapping expires and the tunnel goes silent.
Start it:
sudo wg-quick up wg0
Add the Homelab Peer to the VPS
On the VPS:
sudo wg set wg0 peer <client_public_key> allowed-ips 10.0.0.2/32
Important: the wg set command is not reboot-safe. Also add a [Peer] section to /etc/wireguard/wg0.conf:
[Peer]
PublicKey = <client_public_key>
AllowedIPs = 10.0.0.2/32
Then restart:
sudo wg-quick down wg0
sudo wg-quick up wg0
Check:
sudo wg show
You should see a recent handshake timestamp. If you don’t, jump to the troubleshooting section below.
Test from VPS:
ping 10.0.0.2
If that works, the tunnel is alive.
Set Up a Caddy Docker Proxy on the VPS
Now expose services safely using Caddy. This is where the caddy docker proxy pattern earns its keep.
Install Docker
sudo apt install docker.io -y
sudo systemctl enable --now docker
Run Caddy
docker run -d \
--name caddy \
-p 80:80 -p 443:443 \
-v $PWD/Caddyfile:/etc/caddy/Caddyfile \
-v caddy_data:/data \
caddy
Example Caddyfile (Multiple Services)
jellyfin.yourdomain.com {
reverse_proxy 10.0.0.2:8096
}
nextcloud.yourdomain.com {
reverse_proxy 10.0.0.2:443 {
transport http {
tls_insecure_skip_verify
}
}
}
dashboard.yourdomain.com {
reverse_proxy 10.0.0.2:3000
}
This forwards HTTPS traffic from the internet to the VPS, through WireGuard, to your homelab services.
Caddy handles Let’s Encrypt certificates automatically. Make sure each subdomain points to the VPS public IP in your DNS.
Preserving Client IP
By default, Caddy sets X-Forwarded-For headers. If your homelab service needs the real client IP (Jellyfin’s fail2ban integration, for example), make sure the internal service trusts the VPS tunnel address (10.0.0.1) as a proxy. Otherwise you’ll see every request coming from the tunnel IP.
WireGuard Port Forwarding with iptables
If you prefer raw port forwarding instead of a reverse proxy, use DNAT. This is useful for non-HTTP services like game servers, SSH, or custom TCP applications.
Example: forward VPS port 8080 to homelab port 80.
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 \
-j DNAT --to-destination 10.0.0.2:80
sudo iptables -t nat -A POSTROUTING -o wg0 -p tcp -d 10.0.0.2 \
--dport 80 -j SNAT --to-source 10.0.0.1
Restricting WireGuard Forwarding for Better Security
Those broad FORWARD rules from earlier let everything through. For a tighter setup, replace the blanket accept rules with specific ones:
# Drop the wide-open rules
sudo iptables -D FORWARD -i wg0 -j ACCEPT
sudo iptables -D FORWARD -o wg0 -j ACCEPT
# Allow only specific ports
sudo iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o wg0 -p tcp -d 10.0.0.2 --dport 80 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o wg0 -p tcp -d 10.0.0.2 --dport 443 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o wg0 -p tcp -d 10.0.0.2 --dport 8096 -j ACCEPT
sudo iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
Adjust port numbers to match the services you actually expose. This is WireGuard port forwarding done properly.
Persist rules:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Reverse proxy is cleaner for HTTP and HTTPS. Port forwarding is lower level and works for arbitrary TCP services. Use whichever fits your case.
MTU Problems: The Silent Killer
This cost me hours.
Symptoms:
- Small pings work.
- HTTPS loads halfway, then stalls.
- File uploads freeze.
- No obvious errors in logs.
The issue is usually an MTU mismatch between your VPS provider and home ISP. Packets too large for the tunnel get silently dropped because ICMP “fragmentation needed” messages are blocked somewhere along the path.
WireGuard defaults to 1420. Some VPS networks require lower values.
How to Diagnose MTU Issues
On homelab:
ping -M do -s 1372 10.0.0.1
Start at 1372 and increase by 10 until packets fail. The highest working value plus 28 bytes (IP + ICMP headers) is your effective MTU.
If fragmentation fails above 1380, try:
MTU = 1280
Add it to both configs under [Interface], then restart WireGuard on both ends.
This small change fixes a surprising number of “random” tunnel issues. If you’re seeing intermittent stalls on large transfers, check MTU first.
Dual Reverse Proxy Architecture
For larger setups:
- VPS: Caddy public-facing, handles TLS termination
- Homelab: Nginx, Traefik, or Caddy internally, handles service routing
Flow:
Internet
|
VPS Caddy (TLS termination, public certificates)
|
WireGuard tunnel
|
Homelab Reverse Proxy (service routing, internal TLS optional)
|
Service containers
Advantages:
- SSL termination at the VPS keeps certificate management in one place
- Flexible internal routing without touching VPS config for every new service
- Zero exposed home ports
The VPS proxy passes X-Forwarded-For and X-Real-IP headers. Your homelab proxy should trust the tunnel source IP (10.0.0.1) and preserve these headers for accurate logging.
This pattern scales well. You add new services by updating your homelab proxy config. The VPS side stays stable.
Using Private Internet Access WireGuard
If you already use Private Internet Access WireGuard, do not reuse that interface for your VPS tunnel.
Common problems:
- Conflicting routes that silently break one tunnel or the other
- Default route overrides that send your VPS traffic through PIA
- DNS conflicts where PIA’s DNS settings override your homelab DNS
Instead:
- Use
wg0for the VPS tunnel. - Use
wg1for PIA.
If you need both active simultaneously, you’ll need policy routing. Here’s the concept:
# Create a separate routing table for PIA
echo "200 piaroute" | sudo tee -a /etc/iproute2/rt_tables
# Mark traffic that should go through PIA
sudo ip rule add fwmark 0x1 table piaroute
Full policy routing is its own topic. The key point: keep your VPS tunnel and your VPN provider on separate interfaces with separate routing tables. Stacking them on one interface leads to routing chaos.
Troubleshooting Checklist
No Handshake
- Is UDP 51820 open on the VPS? Check both the OS firewall and the cloud provider’s security group.
- Correct public key on both sides?
- Correct endpoint IP and port?
- NAT blocking outbound UDP from the homelab?
PersistentKeepalive = 25set on the client?
Check:
sudo wg show
If no “latest handshake” time appears, this is a network-level issue. The two sides aren’t talking at all.
One-Way Ping
Usually a missing FORWARD or SNAT rule on the VPS.
Check:
sudo iptables -t nat -L -n -v
sudo iptables -L -n -v
Confirm MASQUERADE and FORWARD rules exist and have packet counters incrementing.
Client Cannot Reach Internet via VPS
If using a full tunnel (AllowedIPs = 0.0.0.0/0):
- VPS has MASQUERADE on its public interface.
- IP forwarding is enabled (
sysctl net.ipv4.ip_forwardshould return1). - VPS firewall allows forwarding (check UFW, security groups).
WireGuard Won’t Start on OpenVZ
Check:
modprobe wireguard
If unsupported, your VPS kernel lacks module support. You can try wireguard-go, but it’s slower and harder to maintain. Switching to a KVM-based provider is often easier and cheaper than fighting it.
High Latency or Slow Throughput
- Check MTU first. Seriously.
- Ensure no overlapping subnets between tunnel and LAN.
- Avoid double NAT when possible.
- Try lowering MTU to 1280 and test again.
- Check VPS provider for bandwidth caps or throttling.
Frequently Asked Questions
How do I forward specific ports from VPS to homelab over WireGuard?
Use DNAT in PREROUTING and SNAT in POSTROUTING with iptables. Point the destination to your homelab’s tunnel IP (e.g., 10.0.0.2). For HTTP and HTTPS, a reverse proxy like Caddy is usually cleaner because it handles TLS and routing for you.
What is the minimum iptables setup for NAT?
You need three things:
net.ipv4.ip_forward=1in sysctl- FORWARD accept rules for the
wg0interface POSTROUTING MASQUERADEon the VPS public interface
Without MASQUERADE, return traffic won’t route correctly because the homelab’s tunnel IP isn’t routable on the public internet.
Why is my WireGuard tunnel dropping packets?
Most of the time this is MTU mismatch. Lower MTU to somewhere between 1280 and 1400 and test with ping -M do. If that fixes it, you’ve found your problem.
Do I need PersistentKeepalive?
Yes, if the homelab is behind NAT or CGNAT. Set it to 25 seconds. Without it, the NAT translation table entry expires and incoming packets get dropped.
Can I run Caddy on the VPS and Nginx on my homelab?
Yes. This is the dual reverse proxy architecture described above. The VPS handles public TLS, the homelab proxy handles internal service routing. It’s the setup I recommend for homelabs with more than two or three exposed services.
How do I generate and exchange keys securely?
Generate keys locally on each machine. Share only public keys. Transfer them over SSH or a secure channel. Never paste private keys into chat apps, shared documents, or ticket systems.
Final Thoughts
WireGuard port forwarding through a VPS is one of the cleanest ways to expose your homelab to the internet.
The core steps:
- Install WireGuard on both ends.
- Generate keys.
- Configure
wg0.confon VPS and homelab. - Enable IP forwarding and NAT.
- Layer in a Caddy reverse proxy for HTTPS.
- Fix MTU if large traffic stalls.
The hardest part is rarely WireGuard itself. It’s NAT rules, routing logic, and MTU edge cases. Once you understand those pieces, the rest falls into place.
If you want to go further:
- Add fail2ban on the VPS for SSH and service protection.
- Use Cloudflare DNS with proxy mode for DDoS protection.
- Add a second VPS for high availability.
- Implement policy routing with multiple WireGuard tunnels.
Secure tunnels beat router port forwarding every time.
This is Part 2 of a 3-part series on building a VPS-fronted homelab.
- Part 1: Why Your Homelab Needs a VPS to Share Services Publicly
- Part 2: How to Install WireGuard on a VPS and Connect It to Your Homelab (this post)
- Part 3: WireGuard Client on OPNsense and pfSense: LAN Routing for Your VPS Tunnel