// DevOps
Routing traffic from a local subnet through a remote server (IPIP + Policy Routing)
Published on 2026-09-22
The guide shows how to configure two Linux servers so that all internet traffic from a specific local subnet (for example, 10.100.10.0/24) goes not via its usual gateway but through an IPIP tunnel to a remote server that releases it to the Internet.
This scheme is needed when services from one subnet must access the Internet using the IP address of another server: for centralized NAT, working with partners by whitelisting addresses, or a single egress point for multiple sites. Other traffic from the gateway itself continues via the normal path.
IPIP does not encrypt traffic. If the tunnel traverses the Internet and data must be protected, use WireGuard or IPsec — see the article “Redundant links: between offices” for options for inter-site connectivity.
Initial data (example addresses)
| Role | Name | External (WAN) IP | WAN interface | Address in tunnel | 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 | Releases 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 (egress node)
Goal: accept the IPIP tunnel from Server A and release traffic from the subnet 10.100.10.0/24 to the Internet via NAT using address 203.0.113.20.
1.1. Enable IP forwarding
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/90-ipip-forward.conf1.2. Tunnel, return route, firewall and NAT
#!/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 "[*] Creating tunnel $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 "[*] Adding return route to client subnet via tunnel"
ip route replace "$CLIENT_NET" via "$TUN_PEER" dev "$TUN_NAME"
echo "[*] Configuring 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 conntrack --ctstate 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] Server B is ready."The return route is mandatory: after address translation the responses are addressed to machines in 10.100.10.0/24, and without this route Server B will send them to the Internet instead of into the tunnel.
IPIP adds 20 bytes of header to each packet, so the tunnel MTU is 1480. The TCPMSS --clamp-mss-to-pmtu rule reduces TCP segment size to match this MTU so that connections don’t stall due to PMTU Discovery issues.
Step 2: Configure Server A (local gateway)
Goal: direct traffic from subnet 10.100.10.0/24 that is destined for the Internet into the tunnel to Server B.
2.1. IP forwarding and 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.d/90-ipip-gw.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOFThe mode rp_filter=2 (loose) is needed because routing here is asymmetric: responses from the Internet arrive via the tunnel rather than via the interface that the default route points to. The kernel applies the maximum of the all and interface values — why this is and how to check it is discussed in the article “What is rp_filter”.
2.2. Create the 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 "[*] Bringing up tunnel $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] Tunnel is up."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_tablesIf the file /etc/iproute2/rt_tables does not exist on your system, create it manually or use the table number 100 directly in the commands instead of the name.
Add a default route into that table
ip route add default via 10.254.0.2 dev ipip0 table tunnel_routeCreate a rule
ip rule add from 10.100.10.0/24 table tunnel_route pref 1000
ip route flush cacheThe rule sends all traffic from the subnet into the tunnel, including accesses to other local networks. If the subnet should reach neighboring networks directly, add a rule with a lower preference before it, for example: ip rule add from 10.100.10.0/24 to 10.0.0.0/8 table main pref 900
2.4. 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 conntrack --ctstate 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 and policy routing configured."Step 3: Testing
Route for traffic from Server A itself
ip route get 8.8.8.8Expected normal route:
8.8.8.8 via <your_gateway> dev eth0 src 198.51.100.10 ...Route for a packet from the subnet
To test forwarding, specify the interface on which the packet arrives from the LAN (iif): without it the kernel checks the route for a locally generated packet.
ip route get 8.8.8.8 from 10.100.10.50 iif eth1Here eth1 is Server A’s interface towards the subnet 10.100.10.0/24. Expected route via the tunnel:
8.8.8.8 from 10.100.10.50 via 10.254.0.2 dev ipip0 ...Diagnostics
1. Tunnel
ip addr show dev ipip0
ip -s tunnel show
ping -I ipip0 10.254.0.2If the ping fails, check that firewalls on both servers and the providers allow the IPIP protocol (IP protocol number 4).
2. Policy routing
ip rule show
ip route show table tunnel_routeThe output of ip rule show should contain a line:
1000: from 10.100.10.0/24 lookup tunnel_route3. NAT on Server B
iptables -t nat -L POSTROUTING -v -nWith active traffic, the counters for the SNAT rule should increase.
4. External address for machines in the subnet
On any machine in the 10.100.10.0/24 subnet:
curl -4 ifconfig.meExpected 203.0.113.20.
5. MTU and MSS
If TCP connections stall:
ip link show ipip0
ping -M do -s 1452 8.8.8.8A 1452-byte payload plus 28 bytes of ICMP and IP headers exactly fits the tunnel MTU of 1480. Check that the tunnel shows mtu 1480 and that the --clamp-mss-to-pmtu rule is in place.
6. tcpdump
tcpdump -ni ipip0
tcpdump -ni eth0 host 8.8.8.87. Temporary logging
iptables -I FORWARD 1 -j LOG --log-prefix "[FORWARD] "
tail -f /var/log/kern.logAfter diagnostics remove this rule: it logs every forwarded packet.
8. Return path
From a machine in the subnet:
traceroute 8.8.8.8The first hop should be Server A, the next hop should be Server B’s tunnel address 10.254.0.2. From Server B:
traceroute -s 10.254.0.2 10.100.10.10If traceroute from Server B goes to the Internet instead of into the tunnel, check the return route from step 1.2.
9. Persisting settings
The commands above are effective until reboot. Save iptables rules like this (on Debian and Ubuntu with the iptables-persistent package):
iptables-save > /etc/iptables/rules.v4The tunnel, routes and policy routing rules must be recreated after reboot: the most convenient way is to wrap the scripts from steps 1.2, 2.2 and 2.3 into systemd services. Saving the output of ip rule show to a file by itself does not restore anything.
Additional recommendations
| Item | Recommendation |
|---|---|
| MTU | 1480 for the tunnel and TCPMSS clamp on both servers |
| Firewall | On modern systems consider using nftables |
| Autostart | Start the tunnel and routes via systemd |
| Monitoring | tcpdump -i ipip0 shows tunnel traffic |
| Security | Allow IPIP only from the second server’s address; for encryption — WireGuard or IPsec |
A similar task on MikroTik — returning traffic via the same gateway it came from — is described in the article “MikroTik: return traffic via the same gateway”.
Conclusion
All Internet traffic from subnet 10.100.10.0/24 exits to the Internet using Server B’s address (203.0.113.20), while Server A continues to use its normal route. Three things are required for this scheme to work: a tunnel with the correct MTU, a policy routing rule on Server A, and a return route to the subnet on Server B.
// Contact
Need help?
Get in touch with me and I'll help solve the problem
I reply within one business day (03:00-13:00 GMT)
Или оставьте заявку здесь:
// Related