“Port forwarding” means sending traffic that arrives on one address and port to another. The term covers several different things: a home router forwarding a game port, a Linux server redirecting port 80 to an app, SSH tunnelling a database port to your laptop, and Docker publishing container ports. Here’s each one, and when to use it.

Ports in 30 seconds

A port (0–65535) identifies a service on an IP address: 22 for SSH, 80/443 for web, 3306 for MySQL. A connection is identified by source IP:port and destination IP:port. Forwarding rewrites the destination (and sometimes the source) so traffic ends up somewhere else. See check open ports.

1. Router port forwarding (home networks)

At home, devices share one public IP through NAT. Incoming connections don’t know which device to reach, so the router needs a rule: “TCP port 25565 on the public IP → 192.168.1.50:25565”.

Typical uses: a game server at home, a NAS, a camera system.

Problems you may hit:

  • CGNAT: many ISPs (especially mobile and some fibre) share one public IP between customers, so your router has no public IP to forward from. Check whether the router’s WAN IP matches what curl ifconfig.me shows. If not, you’re behind CGNAT.
  • Dynamic IPs change, so you need dynamic DNS.
  • Security: you’re exposing a home device to the internet.

Alternatives: a VPS with a static public IP running the service directly, or a tunnel from home to the VPS. See frp, Cloudflare Tunnel and NAT explained.

2. Forwarding on a Linux server (DNAT)

Redirect a port locally

Run an app on 8080 but accept traffic on 80, without running the app as root:

sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

A reverse proxy (Nginx) is usually the better answer for web traffic, since it adds TLS, logging and virtual hosts. See Nginx reverse proxy.

Forward to another machine

The VPS receives traffic on port 2222 and forwards it to a server on a private network or VPN:

# enable routing
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf && sudo sysctl --system

# DNAT incoming 2222 → 10.0.0.12:22, and masquerade the source so replies come back
sudo iptables -t nat -A PREROUTING -p tcp --dport 2222 -j DNAT --to-destination 10.0.0.12:22
sudo iptables -t nat -A POSTROUTING -d 10.0.0.12 -p tcp --dport 22 -j MASQUERADE
sudo iptables -A FORWARD -p tcp -d 10.0.0.12 --dport 22 -j ACCEPT

The nftables equivalent:

table ip nat {
  chain prerouting { type nat hook prerouting priority -100;
    tcp dport 2222 dnat to 10.0.0.12:22 }
  chain postrouting { type nat hook postrouting priority 100;
    ip daddr 10.0.0.12 tcp dport 22 masquerade }
}

With UFW, add the NAT rules to /etc/ufw/before.rules, and set DEFAULT_FORWARD_POLICY="ACCEPT" (or add specific route rules). See iptables, nftables and UFW.

Make the rules persistent (iptables-persistent / netfilter-persistent save, or the nftables config file), or they vanish at reboot.

For proxying at the application level instead of the packet level, socat or HAProxy in TCP mode are simpler and show up in logs. See HAProxy TCP proxy.

3. SSH port forwarding (tunnels)

SSH can forward ports through an encrypted connection, with no firewall changes:

# Local: reach the server's local-only MySQL from your laptop at localhost:3307
ssh -L 3307:127.0.0.1:3306 user@server

# Remote: expose your laptop's port 3000 on the server's localhost:8080
ssh -R 8080:localhost:3000 user@server

# Dynamic: a SOCKS5 proxy through the server
ssh -D 1080 user@server

This is ideal for admin access to databases and internal dashboards without opening ports. See SSH tunnels.

4. Docker port publishing

docker run -p 8080:80 nginx            # host 8080 → container 80, on all interfaces
docker run -p 127.0.0.1:5432:5432 postgres   # only reachable from the host itself

Docker creates its own iptables DNAT rules, which bypass UFW. Publish internal services on 127.0.0.1 only. See Docker networking and Docker security.

Debugging forwarding

sudo ss -tlnp                      # is the target listening?
nc -zv target-ip port              # can the forwarding host reach the target?
sudo iptables -t nat -L -n -v      # rules and packet counters
sudo tcpdump -i any -nn port 2222  # do packets arrive and leave?

See tcpdump and netcat.

Quick reference

Goal Tool
Home device reachable from internet Router port forward (or a tunnel if behind CGNAT)
Port 80 → app on 8080 Nginx reverse proxy (or iptables REDIRECT)
VPS → private server DNAT + MASQUERADE, ip_forward=1
Admin access to a private DB ssh -L
Share a local dev server ssh -R, frp, Cloudflare Tunnel
Containers -p 127.0.0.1:host:container

Related: NAT explained and ngrok alternatives.

  • Networking
  • Linux
  • Firewall