When configuring port redirection with iptables, a common frustration occurs when the rules work perfectly for external clients but fail when testing from localhost. The standard NAT PREROUTING rule:
iptables -t nat -I PREROUTING --source 0/0 --destination 0/0 -p tcp \\
--dport 443 -j REDIRECT --to-ports 8080
doesn't catch local traffic because of how the Linux networking stack processes packets differently for local vs. remote connections.
Local connections bypass the PREROUTING chain entirely. The traffic flow looks like this:
External Client: PREROUTING → FORWARD → POSTROUTING Local Client: OUTPUT → POSTROUTING
To properly redirect both external and local traffic, we need two rules:
# For external connections
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
# For local connections
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-port 8080
For more complex scenarios (like when using hostnames instead of 127.0.0.1), consider:
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner root \\
--dport 443 -j REDIRECT --to-port 8080
Test with these commands:
# External test (from another machine)
curl https://your-server-ip
# Local test
curl https://localhost
curl https://127.0.0.1
curl https://$(hostname -I | awk '{print $1}')
To make these rules persistent across reboots on Ubuntu/Debian:
apt install iptables-persistent
iptables-save > /etc/iptables/rules.v4
- Check rule ordering with
iptables -t nat -L -v -n
- Verify no other services are listening on port 443 (
netstat -tulnp | grep 443
) - Test with
tcpdump -i lo port 443
to see if packets reach lo interface
When setting up port redirection with iptables, many developers encounter an irritating limitation: rules in the PREROUTING chain of the nat table don't affect locally-generated traffic. This explains why your wget https://localhost
fails while external connections work perfectly.
Here's what happens differently for local vs remote connections:
# External traffic flow:
Internet → PREROUTING (nat) → FORWARD (filter) → POSTROUTING (nat)
# Local traffic flow:
Application → OUTPUT (nat) → POSTROUTING (nat) → INPUT (filter)
We need to add rules to both PREROUTING (for external) and OUTPUT (for local) chains:
# For external connections
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
# For local connections
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-port 8080
iptables -t nat -A OUTPUT -p tcp -d --dport 443 -j REDIRECT --to-port 8080
After implementing these rules, test with:
# Test external IP (from another machine)
curl https://your-server-ip
# Test localhost
curl https://localhost
# Test public IP locally
curl https://your-public-ip
1. For services listening on 0.0.0.0, add this to ensure proper binding:
sysctl -w net.ipv4.conf.all.route_localnet=1
2. If using Docker or other containers, you may need additional rules for bridge networks.
3. For persistent rules, save them with iptables-save
or your distribution's preferred method.