This guide explains how to forward public ports on a VPS through Tailscale to game servers or services running on VMs behind CGNAT. It uses iptables and works for both TCP and UDP traffic. I’m using an Ionos VPS in this case due to the low cost. It’s not an endorsement, but I have had good luck with them so far.
While Tailscale is free up to a set amount, the VPS will incur a cost.
The Services VM will selectively forward the ports for HTTPS/3 and Plex.
The Game VM will forward a preset range of ports, but also use an exit node for all outbound internet traffic so game servers will be joinable by direct connect or game server browsers. You may follow the same steps for Services VM if you do not want to forward all traffic and prefer users to use direct connect only.
Throughout this guide I reference ens6 on the VPS which is the default name of the network interface on Ionos. Yours may differ so adjust the commands accordingly. You can determine the name by running ip addr.
I’m running Services VM in a virtual machine on Proxmox and Game VM in an LXC container on the same machine.
| Host | Role | Interface | Tailscale IP | Notes |
|---|---|---|---|---|
| VPS | Public Gateway | ens6 |
100.111.232.20 |
Has public IP, running Tailscale |
| Services VM #1 | Game / Web Server | tailscale0 |
100.110.232.20 |
Ports 80, 443, 32400 |
| Game VM #2 | Game Server | tailscale0 |
100.92.19.21 |
Ports 40200–40399 TCP/UDP |
1. Install TailScale
If running in an unprivileged LXC container you must add a couple lines to the container’s config file:
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
2.Setup the VPS as an exit node (If tunneling all traffic)
Follow the steps here:
3. Connect the VMs to Tailscale
Use the following commands depending on whether you want to route all traffic or select traffic:
Services VM (Select traffic)
sudo tailscale up
Game VM (All traffic)
sudo tailscale up --exit-node=100.111.232.20 --exit-node-allow-lan-access=true
4.Enable Packet Forwarding on the VPS
This allows the VPS to act as a router between the public internet and your Tailscale network.
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
5. Forward Ports for VM #1 (100.110.232.20)
Ports: 80 (TCP), 443 (TCP/UDP), 32400 (TCP)
# Accept inbound traffic from the internet
sudo iptables -A INPUT -i ens6 -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -i ens6 -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i ens6 -p udp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i ens6 -p tcp --dport 32400 -j ACCEPT
# Forward public traffic to the services VM
sudo iptables -t nat -A PREROUTING -i ens6 -p tcp --dport 80 -j DNAT --to-destination 100.110.232.20:80
sudo iptables -t nat -A PREROUTING -i ens6 -p tcp --dport 443 -j DNAT --to-destination 100.110.232.20:443
sudo iptables -t nat -A PREROUTING -i ens6 -p udp --dport 443 -j DNAT --to-destination 100.110.232.20:443
sudo iptables -t nat -A PREROUTING -i ens6 -p tcp --dport 32400 -j DNAT --to-destination 100.110.232.20:32400
# Allow the traffic through
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p tcp -d 100.110.232.20 --dport 80 -j ACCEPT
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p tcp -d 100.110.232.20 --dport 443 -j ACCEPT
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p udp -d 100.110.232.20 --dport 443 -j ACCEPT
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p tcp -d 100.110.232.20 --dport 32400 -j ACCEPT
# Masquerade return packets through Tailscale
sudo iptables -t nat -A POSTROUTING -o tailscale0 -d 100.110.232.20 -j MASQUERADE
6. Forward Port Range for VM #2 (100.92.19.21)
Ports: 40200–40399 (TCP + UDP)
# Accept inbound traffic from the internet
sudo iptables -A INPUT -i ens6 -p tcp --dport 40200:40399 -j ACCEPT
sudo iptables -A INPUT -i ens6 -p udp --dport 40200:40399 -j ACCEPT
# Forward the traffic through Tailscale
sudo iptables -t nat -A PREROUTING -i ens6 -p tcp --dport 40200:40399 -j DNAT --to-destination 100.92.19.21
sudo iptables -t nat -A PREROUTING -i ens6 -p udp --dport 40200:40399 -j DNAT --to-destination 100.92.19.21
# Allow the packets to pass through
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p tcp -d 100.92.19.21 --dport 40200:40399 -j ACCEPT
sudo iptables -A FORWARD -i ens6 -o tailscale0 -p udp -d 100.92.19.21 --dport 40200:40399 -j ACCEPT
# Masquerade return traffic
sudo iptables -t nat -A POSTROUTING -o tailscale0 -d 100.92.19.21 -j MASQUERADE
7. Allow Established and Loopback Connections
Good practice to ensure replies and local services keep working:
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
8. Allow Inbound Ports on Each VM
For Services VM (100.110.232.20)
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i tailscale0 -p udp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 32400 -j ACCEPT
For Games VM (100.92.19.21)
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 40200:40399 -j ACCEPT
sudo iptables -A INPUT -i tailscale0 -p udp --dport 40200:40399 -j ACCEPT
9. Make the Rules Persistent
sudo apt install iptables-persistent
You will be prompted to save the rules as they are when installing iptables-persistent, but if you need to make more changes you can save again using:
sudo netfilter-persistent save
10. Set the Public URL for Plex
Since we didn’t forward all outbound internet traffic for the Services VM through the VPS, we need to tell the Plex clients how to access our server. You can do this in the Plex settings under Settings → Network → Custom server access URLs.
Assuming you’re using the default port of 32400, replace the <VPS_IP> with the Public IP of your VPS:
http://<VPS_IP>:32400