Pinging the gateway works and reaching anything behind it does not, which is the shape of a forwarding problem rather than a tunnel problem. Local delivery to the gateway proves the tunnel came up and the address assignment worked; it says nothing about whether the kernel will pass a packet from one interface to another. Before touching a sysctl, confirm the machine is meant to route this traffic and locate the network context that holds the client-facing interface. Then read the value there, rather than wherever the last guide you read happened to look.

Reaching the gateway is not the same as forwarding

Two separate decisions happen inside the kernel. Delivering to a local address ends the packet's journey; forwarding sends it back out through another interface, and that second decision is governed per network namespace. A gateway can be perfectly reachable while forwarding is off, and from a ping prompt the two failures look identical.

Write down which interface the client packet arrives on and which interface the onward path leaves through. Both belong to one namespace, and that namespace holds the setting that matters.

Read the value where the packets actually are

On a gateway that routes in the host namespace, one command answers the question: sysctl net.ipv4.ip_forward. When the ingress and onward path live in a named namespace, ask that namespace instead: ip netns exec vpn-gw sysctl net.ipv4.ip_forward. The host value is not a proxy for the namespace value, and a one on the host is not evidence of anything.

Not every routing context has a label you can hand to ip netns. Container runtimes and services create namespaces that are never registered under a friendly name, and for those you use the runtime inspection tools. Substitute the real name for vpn-gw, or the runtime mechanism that reaches the same context, and stop guessing at the host level.

Give the setting a single owner

On a conventional systemd host gateway the durable form is a drop-in file with an administrator-chosen name such as /etc/sysctl.d/60-vpn-forwarding.conf holding net.ipv4.ip_forward = 1. Write the file, then load that specific file so live state changes too: sudo sysctl -p /etc/sysctl.d/60-vpn-forwarding.conf, followed by a read-back. The filename is an example rather than a reserved VPN path, and the leading number matters because later-sorted files win.

Precedence is where persistence quietly breaks. Check the distribution loader as well as your own drop-in, because procps sysctl --system also reads /etc/sysctl.conf at the end, while systemd's sysctl.d ordering is a different sequence entirely. A later assignment can set the same key straight back, leaving your file correct and the effective value wrong. Do not overwrite an existing administrator file to win that argument; find the owner and change it there.

A namespace created after boot is not governed by a host drop-in at all. Its setting belongs to whatever creates it, whether that is the container runtime or the provisioning unit for the namespace. Granting a container broad host privileges to dodge the question is a worse trade than spending ten minutes identifying the right owner.

Know what the switch costs before you flip it

ip_forward is not a harmless boolean. The kernel documentation notes that changing it resets several other IPv4 parameters to their host or router defaults, so a diagnostic flip can discard tuning that was set somewhere else and never mention it. Treat it as a configuration change with side effects rather than a probe you can run casually.

That also rules out toggling it off and on every time a client cannot reach a host. If the value is already one, the fault is elsewhere in the path, and rewriting the same key a third time will not move it.

Prove persistence, then test the rest of the path

Read the value back at four moments: right after the change, after the VPN interface appears, after a reconnect, and after a planned restart. A value that reverts at any of those points has a second owner, and one more one-off write only hides it until the next reboot.

Forwarding enabled still leaves three things to verify. The client needs a route to the destination, the gateway needs an onward route plus permission to use it, and the destination needs a return path to the client tunnel address. A private routed LAN does not automatically need NAT, so do not add masquerading before you know which of the three is missing, and test from the intended client rather than from the gateway itself, because a request originated on the gateway never touches the forwarding path you just enabled. IPv6 carries its own forwarding and routing policy, so a working IPv4 test says nothing about it.

Keep the drop-in path, the namespace owner and the read-back command together in the runbook. Whoever picks this up next should be able to read the value, see who owns it, and decide within a minute whether the problem is persistence or the rest of the path.

Tënë sô zo