What it is
When a packet arrives on interface A and its destination is not the local host, the kernel consults the routing-table and decides: forward it through B or drop it. The decision to forward traffic destined for others is off by default, because an ordinary host is not a router.
You enable it with one sysctl:
# Runtime only (lost on reboot)
sudo sysctl -w net.ipv4.ip_forward=1
# Direct path (same effect)
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# Persistent (survives reboot)
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-router.conf
sudo sysctl --system
For IPv6 the equivalent is: net.ipv6.conf.all.forwarding.
When you need it
- Your machine as a router - multiple interfaces, need to pass packets between subnets
- NAT/masquerade - without forwarding, nat will not work: return packets cannot pass through
- VPN server - tunnel clients must reach the local network and vice versa
- Container networks - Docker and Kubernetes enable forwarding on the host
- Network namespaces - packets between netns go through the kernel and require forwarding
Per-interface vs global
Beyond the global net.ipv4.ip_forward there is a per-interface knob:
cat /proc/sys/net/ipv4/conf/eth0/forwarding
cat /proc/sys/net/ipv4/conf/all/forwarding
cat /proc/sys/net/ipv4/conf/default/forwarding
all/forwardingmirrors the global settingdefault/forwardingapplies to NEW interfaces created after boot- per-interface value applies only to that interface
To enable forwarding only for a specific pair:
sudo sysctl -w net.ipv4.conf.eth0.forwarding=1
sudo sysctl -w net.ipv4.conf.eth1.forwarding=1
What else you need
ip_forward=1 is a permission, not a complete solution. For packets to actually
flow in both directions you also need:
- Routes on your machine to every subnet you are forwarding between (or a default route)
- Return routes on remote hosts, otherwise replies will not come back
- NAT (nat) if internal IPs are not routable in the wider network
- Firewall that does not drop the FORWARD chain (nftables/iptables often block FORWARD by default)
Minimal example for a two-interface router:
sudo sysctl -w net.ipv4.ip_forward=1
# If NAT is needed (private network to public):
sudo nft 'add table inet nat'
sudo nft 'add chain inet nat postrouting { type nat hook postrouting priority 100; }'sudo nft 'add rule inet nat postrouting oifname "eth1" masquerade'
# Allow FORWARD in the firewall (simple accept policy)
sudo nft 'add chain inet filter forward { type filter hook forward priority 0; policy accept; }'Reverse path filtering
A related sysctl is net.ipv4.conf.*.rp_filter. When enabled, the kernel
drops incoming packets if the reply to them would not go back out the same
interface. This protects against spoofing but breaks asymmetric routing.
On a router with multiple uplinks you often disable it:
sudo sysctl -w net.ipv4.conf.all.rp_filter=0
Debug: packets leave but nothing comes back
When forwarding is on, routes are set, NAT is configured, but nothing works:
tcpdumpon each interface - do you see the packet?nft list ruleset/iptables -L FORWARD- is FORWARD being dropped?ip route get <dst>- which route will be selected?cat /proc/sys/net/ipv4/conf/<iface>/forwarding- is forwarding on for that specific interface?conntrack -L- is there a conntrack entry for this connection?
Inside a container
In Docker containers /proc/sys is read-only by default. To change
forwarding from inside a container you need --cap-add=NET_ADMIN plus SYS_ADMIN
and a remount of /proc/sys rw. It is usually better to enable forwarding on the host.