When working with dynamic network interfaces like VPN tunnels (tun0
) or temporary interfaces, traditional methods of enabling IP forwarding fall short. The main challenge comes from:
# These fail during early boot:
net.ipv4.conf.enp0s3.forwarding=1
net.ipv4.conf.tun0.forwarding=1
For systemd-based systems (v229+), we can leverage networkd-dispatcher or custom units to handle dynamic interfaces:
# /etc/systemd/network/99-ipforwarding.network
[Match]
Name=enp0s3 tun0
[Network]
IPForward=yes
For physical interfaces that persist across reboots:
# /etc/sysctl.d/99-ipforward.conf
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv4.conf.all.rp_filter=2
Create a udev rule for interface hotplug events:
# /etc/udev/rules.d/99-ipforward.rules
ACTION=="add", SUBSYSTEM=="net", \
RUN+="/usr/bin/sh -c 'echo 1 > /proc/sys/net/ipv4/conf/%k/forwarding'"
After implementing any solution, verify with:
sudo sysctl -p
ip route show table all
cat /proc/sys/net/ipv4/conf/*/forwarding | grep -v 1
If forwarding still fails:
- Check iptables/nftables rules:
sudo iptables -L -v -n
- Verify interface MTU settings:
ip link show dev tun0
- Test basic connectivity:
ping -I enp0s3 8.8.8.8
When enabling IP forwarding in Linux, many administrators encounter a frustrating scenario: while net.ipv4.ip_forward=1
gets set globally, individual interfaces (especially dynamic ones like VPN tunnels) may still block forwarding. Here's what's happening under the hood:
# Typical sysctl.conf entry
net.ipv4.ip_forward = 1
net.ipv4.conf.default.forwarding = 1
net.ipv4.conf.all.forwarding = 1
Yet checking interface-specific settings reveals the problem:
cat /proc/sys/net/ipv4/conf/enp0s3/forwarding
0
cat /proc/sys/net/ipv4/conf/tun0/forwarding
0
The root cause lies in the timing of systemd-sysctl service execution. During early boot:
- Persistent network interfaces (like enp0s3) may not be fully initialized
- Dynamic interfaces (like tun0) don't exist yet
Systemd logs reveal the failure:
journalctl -u systemd-sysctl | grep forwarding
systemd-sysctl[85]: Couldn't write '1' to 'net/ipv4/conf/enp0s3/forwarding'
systemd-sysctl[85]: Couldn't write '1' to 'net/ipv4/conf/tun0/forwarding'
Method 1: NetworkManager Dispatcher Script (Recommended for Dynamic Interfaces)
Create a script that triggers whenever interfaces change:
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/99-ip-forwarding
INTERFACE="$1"
ACTION="$2"
if [ "$ACTION" = "up" ]; then
echo 1 > /proc/sys/net/ipv4/conf/${INTERFACE}/forwarding
fi
Make it executable:
chmod +x /etc/NetworkManager/dispatcher.d/99-ip-forwarding
Method 2: Systemd Service for Persistent Interfaces
For static interfaces, create a service that runs after networking:
[Unit]
Description=Set interface forwarding
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo 1 > /proc/sys/net/ipv4/conf/enp0s3/forwarding"
[Install]
WantedBy=multi-user.target
Method 3: Udev Rule for All Interface Events
This catches both existing and new interfaces:
# /etc/udev/rules.d/99-ip-forwarding.rules
ACTION=="add", SUBSYSTEM=="net", RUN+="/bin/sh -c 'echo 1 > /proc/sys/net/ipv4/conf/%k/forwarding'"
After implementing any solution, verify with:
# Check global setting
sysctl net.ipv4.ip_forward
# Check all interfaces
find /proc/sys/net/ipv4/conf/ -name forwarding -exec sh -c 'echo "$1: $(cat $1)"' sh {} \;
For temporary testing, you can manually enable forwarding:
# For existing interfaces
for intf in /proc/sys/net/ipv4/conf/*/forwarding; do
echo 1 > "$intf"
done
# For new interfaces (run after creation)
echo 1 > /proc/sys/net/ipv4/conf/tun0/forwarding
While these methods work, consider these additional sysctl parameters for complete forwarding:
# /etc/sysctl.d/99-ip-forward.conf
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_redirects = 0