You set net.ipv4.conf.default.forwarding=1, restarted the VPN, and the tunnel still drops packets that the gateway should be routing. The setting looks right in the file, and the interface still reports forwarding disabled. Nothing is broken: you changed the value that future interfaces inherit, not the one the live tunnel is using.

Walk through this on the host that owns the tunnel, in the network namespace where the tunnel actually exists. A container or a network namespace can hold the interface while the host shows different numbers.

Read the three values together

Start by comparing the gateway-wide switch, the template for new interfaces, and the interface you care about. Replace tun0 with the real ingress interface, and read the return interface as well, because forwarding is evaluated at the point where the packet arrives.

sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.default.forwarding
sysctl net.ipv4.conf.tun0.forwarding

A common result is ip_forward = 0, default.forwarding = 1, and tun0.forwarding = 0. The first two disagree about the host's role, and the third shows what the interface is actually doing. That combination is consistent with a host that was never configured as a router, only given a template value. The three numbers are not contradicting each other; they are answering three different questions. Writing the template value does not decide the host's role either.

Why the global knob is the one that moves

net.ipv4.ip_forward is the gateway-wide control. Changing it updates the per-interface forwarding settings as part of the same operation, which is why a single write can make an existing tunnel start forwarding. default.forwarding only seeds interfaces that are initialized afterward, so a tunnel that already existed keeps the value it was born with.

The kernel documentation flags ip_forward as special for a related reason: changing it resets a set of other IPv4 parameters to host or router defaults. That makes it a blunt instrument. Setting it once as part of establishing the gateway role is fine; toggling it repeatedly as a debugging trick can silently rewrite configuration you spent time getting right.

Per-interface values also exist for a reason. An intentionally managed gateway may keep forwarding off on an interface that should never route, even while the host forwards elsewhere. That is why reading the live interface matters and why the global value alone is not the whole answer. Read such an exception as a deliberate decision, and write down why that interface is different. Otherwise the next engineer will helpfully "fix" it.

Make the setting persistent in the right place

A successful one-off sysctl -w proves the kernel accepts the value. It does not survive a reboot, and it does not survive a network manager that reapplies its own idea of the interface configuration on reconnect. Put the gateway role into the sysctl configuration file or the network manager that owns the host, then restart the service that applies it.

Order matters with tunnels. If the manager creates the interface at boot and applies forwarding afterwards, the interface may inherit the old default. A service restart that recreates the tunnel is also a creation event, which is easy to miss when the unit looks healthy. Check the sequence, not just the final file contents.

Re-read the same three values after the VPN reconnects and again after a reboot. A newly created tunnel is a new interface, and a new interface takes the default in force at creation time. If the numbers change across those events, the configuration is being applied by something you have not identified yet.

Do not repair this with the wrong tool

When forwarding looked broken, the temptation is to flush the firewall or add masquerading. Neither addresses the forwarding switch. Forwarding state, permission to forward, a route for the destination, and a return path are four separate requirements, and only the first one is the sysctl.

Also test with the right flow. A request made on the gateway itself does not exercise forwarding at all; it is locally generated traffic. The question is whether a packet arriving from a VPN client or a LAN host is accepted and routed onward, so the test has to originate behind the gateway.

Keep the IPv6 case separate

Everything here is IPv4. IPv6 forwarding, router advertisements, and the accept_ra behaviour have their own controls, and copying an ipv4 path into an ipv6 setting produces a file that looks plausible and does nothing useful. A dual-stack gateway needs both sets configured deliberately.

One last check: after the values agree, confirm the tunnel's route is selected for the destination and that the reply path exists. Forwarding enabled on an interface with no route is still a black hole, and that failure looks identical from the client side.

Makomi ya ndenge moko