Routing traffic from a local subnet through a remote server (IPIP + Policy Routing)
Published on 2025-10-29
This guide will show how to configure two Linux servers so that all the Internet traffic from a specific local subnet (for example, 10.100.10.0/24) is routed not via its default gateway but through an IPIP tunnel to a remote server, which will then put that traffic onto the Internet.
This is useful if you need services in one subnet to go out to the world with the IP address of another server — for example, to bypass restrictions, centralize NAT, or hide the source.
Actors (example)
| Role | Name | External (WAN) IP | WAN Interface | Internal (Tunnel) IP | Note |
|---|---|---|---|---|---|
| Server A | Local Gateway | 198.51.100.10 | eth0 | 10.254.0.1/30 | Gateway for subnet 10.100.10.0/24 |
| Server B | Remote Egress Node | 203.0.113.20 | eth0 | 10.254.0.2/30 | Egresses traffic to the Internet |
- Subnet to route:
10.100.10.0/24 - Tunnel name:
ipip0 - Tunnel network:
10.254.0.0/30
Step 1: Configure Server B (Remote Egress Node)
Goal: Accept an IPIP tunnel from Server A and egress traffic from subnet 10.100.10.0/24 to the Internet via NAT using external IP 203.0.113.20.
1.1. Enable IP Forwarding
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
1.2. Tunnel, firewall and NAT setup
#!/bin/sh
set -e
TUN_NAME="ipip0"
REMOTE_IP="198.51.100.10"
LOCAL_IP="203.0.113.20"
TUN_NET="10.254.0.2/30"
TUN_PEER="10.254.0.1"
WAN_IFACE="eth0"
CLIENT_NET="10.100.10.0/24"
echo "[*] Создаю туннель $TUN_NAME"
ip tunnel del "$TUN_NAME" 2>/dev/null || true
ip tunnel add "$TUN_NAME" mode ipip remote "$REMOTE_IP" local "$LOCAL_IP" ttl 64
ip addr add "$TUN_NET" dev "$TUN_NAME"
ip link set "$TUN_NAME" up mtu 1480
echo "[*] Настраиваю iptables"
iptables -I INPUT -p 4 -s "$REMOTE_IP" -j ACCEPT
iptables -A FORWARD -i "$TUN_NAME" -s "$CLIENT_NET" -o "$WAN_IFACE" -j ACCEPT
iptables -A FORWARD -i "$WAN_IFACE" -d "$CLIENT_NET" -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s "$CLIENT_NET" -o "$WAN_IFACE" -j SNAT --to-source "$LOCAL_IP"
iptables -t mangle -A FORWARD -o "$WAN_IFACE" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
echo "[OK] Сервер Б готов."
MTU is set to
1480andTCPMSS clampis enabled to avoid PMTU Discovery issues.
Step 2: Configure Server A (Local Gateway)
Goal: Redirect traffic from 10.100.10.0/24 that is destined to the Internet into the tunnel to Server B.
2.1. Enable IP Forwarding and adjust rp_filter
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2
cat <<EOF >> /etc/sysctl.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOF
rp_filter=2— loose mode, required for asymmetric routing.
2.2. Create IPIP tunnel
#!/bin/sh
set -e
TUN_NAME="ipip0"
REMOTE_IP="203.0.113.20"
LOCAL_IP="198.51.100.10"
TUN_NET="10.254.0.1/30"
TUN_PEER="10.254.0.2"
echo "[*] Поднимаю туннель $TUN_NAME"
ip tunnel del "$TUN_NAME" 2>/dev/null || true
ip tunnel add "$TUN_NAME" mode ipip remote "$REMOTE_IP" local "$LOCAL_IP" ttl 64
ip addr add "$TUN_NET" dev "$TUN_NAME"
ip link set "$TUN_NAME" up mtu 1480
echo "[OK] Туннель поднят."
2.3. Policy Routing
Add a routing table:
grep -q "100[[:space:]]tunnel_route" /etc/iproute2/rt_tables || echo "100 tunnel_route" >> /etc/iproute2/rt_tables
Add a default route:
ip route add default via 10.254.0.2 dev ipip0 table tunnel_route
Create the rule:
ip rule add from 10.100.10.0/24 table tunnel_route pref 1000
ip route flush cache
2.4. Configure Firewall
CLIENT_NET="10.100.10.0/24"
TUN_NAME="ipip0"
iptables -A FORWARD -s "$CLIENT_NET" -o "$TUN_NAME" -j ACCEPT
iptables -A FORWARD -i "$TUN_NAME" -d "$CLIENT_NET" -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t mangle -A FORWARD -o "$TUN_NAME" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
echo "[OK] Firewall и policy routing настроены."
Step 3: Verification
Check the normal route:
ip route get 8.8.8.8
Expected:
8.8.8.8 via <your_gateway> dev eth0 src 198.51.100.10 ...
Check the route from the subnet:
ip route get 8.8.8.8 from 10.100.10.50
Expected:
8.8.8.8 from 10.100.10.50 via 10.254.0.2 dev ipip0 src 198.51.100.10 ...
🧩 Troubleshooting
1. Check the tunnel
ip addr show dev ipip0
ip -s tunnel show
ping -I ipip0 10.254.0.2
If ping fails — check that IPIP (protocol 4) is allowed in the firewall.
2. Check policy routing
ip rule show
ip route show table tunnel_route
There should be a line:
1000: from 10.100.10.0/24 lookup tunnel_route
3. Check NAT
iptables -t nat -L POSTROUTING -v -n
SNAT counters should increase when traffic is active.
4. Check external IP from inside the subnet
curl -4 ifconfig.me
Expected — 203.0.113.20.
5. MTU and MSS
If TCP stalls:
ip link show ipip0
ping -M do -s 1472 8.8.8.8
Set mtu 1480 and use --clamp-mss-to-pmtu.
6. tcpdump
tcpdump -ni ipip0
tcpdump -ni eth0 host 8.8.8.8
7. Temporary logging
iptables -I FORWARD 1 -j LOG --log-prefix "[FORWARD DROP] "
tail -f /var/log/kern.log
8. Check return path
traceroute -s 10.100.10.10 8.8.8.8
and on Server B:
traceroute -s 10.254.0.2 10.100.10.10
9. Save state
iptables-save > /etc/iptables/rules.v4
ip rule show > /etc/iprules.conf
⚙️ Additional recommendations
| Item | Recommendation |
|---|---|
| MTU | Set to 1480 and enable TCPMSS clamp |
| Firewall | Prefer nftables on newer systems |
| Persistency | Automate tunnel start with systemd |
| Monitoring | tcpdump -i ipip0 shows tunnel traffic |
| Security | Restrict IPIP access and add IPSec/WireGuard if needed |
✅ Done
Now all Internet traffic from 10.100.10.0/24 egresses via Server B’s IP (203.0.113.20), while Server A continues to use its normal route.
This is a simple and reliable setup for centralized NAT, bypassing restrictions, or building an internal VPN on plain Linux.