Forward Ports to a Tailscale VM Behind CGNAT Using IPTables

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

1 Like