The client window says Connected, the tunnel address is there, and the browser still spins. That combination is normal, because a successful handshake only proves the tunnel was built; it says nothing about whether the server will forward your packets, rewrite their source, or answer your DNS queries. The mistake most people make is opening the server configuration first. Work outward from the client instead, because each step below tells you whether the next one is even worth checking.

Begin on the client and work outward

Run three tests in this order and write down where the first one fails. Ping the server's tunnel address, which proves the tunnel itself carries traffic. Ping a public address by number, which proves forwarding and translation without involving name resolution. Then try to resolve a hostname, which is the only test that exercises DNS. Each result removes a whole layer from suspicion.

Do not skip to the third test because a browser feels faster. A failed page load covers all three layers at once, and starting there is how people end up rebuilding a working tunnel. A failing page tells you nothing about which of the three broke.

Two captures show where the packets stop

With a ping to a public address running from the client, take one capture on the tunnel interface and one on the uplink that is supposed to reach the internet. Run both at the same time so the timestamps line up.

sudo tcpdump -ni tun0 -c 20 icmp
sudo tcpdump -ni enp1s0 -c 20 icmp

If the requests appear on the tunnel and never on the uplink, the server dropped them between the tunnel and the wire, and the cause is forwarding or the forward firewall policy rather than NAT. If they appear on both, the server did its part and the answer is missing further out, which points at translation, an upstream filter or a provider-side restriction.

Get the uplink name from the routing table rather than from a tutorial. Cloud images and renamed adapters rarely match the example interface in a guide, and a capture taken on the wrong device looks exactly like a server that is not forwarding:

ip -4 route get 9.9.9.9

The dev field in that output is the name the NAT rule will need later, so keep it.

Forwarding is a kernel switch and a firewall policy

The kernel has to agree to forward at all. Read the current value, then persist the setting instead of writing it into the running kernel only:

sysctl net.ipv4.ip_forward
printf 'net.ipv4.ip_forward=1\n' > /etc/sysctl.d/70-vpn-forward.conf
sysctl --system

Forwarding is only half of it. On a host using UFW, the forward policy is set in /etc/default/ufw and has to be ACCEPT before a reload takes effect. A default drop on the forward path leaves the handshake perfectly healthy while silently discarding every packet that tries to leave through the tunnel, which is the exact symptom you started with.

Hosts that manage nftables directly have the same question in a different syntax. Check the counters on the forward chain while a client test runs, because a chain that is consulted and a chain that drops both look identical in a configuration file.

Name the real egress interface in the NAT rule

Source translation belongs on the interface that actually leaves the host, with the pool the server hands out. Both halves of that rule have to describe reality, and neither is optional.

iptables -t nat -A POSTROUTING -s 10.44.0.0/24 -o enp1s0 -j MASQUERADE

The classic failure is a rule copied from a guide that says -o eth0 on a machine whose adapter is called something else. The rule installs without a warning, matches nothing, and the client still has no internet. The pool is the other half of the same mistake: if the server pushes 10.44.0.0/24 and the rule covers 10.8.0.0/24, nothing will ever match.

Watch the counters rather than trusting the rule text. The listing reports packets per rule, and a rule that never matches stays at zero however correct it looks. Give it a minute of real client traffic before reading anything into that number:

iptables -t nat -L POSTROUTING -v -n

A zero packet count after a real client test means the match is wrong, not that the tunnel is broken. Once it matches, save the ruleset so it survives a reboot; a fix that lives only in your shell history disappears at the first restart.

If the server is also a cloud NAT instance, the platform gets a vote. An instance that translates addresses usually needs its source and destination check disabled where the provider exposes that setting, and the VPN port itself has to be reachable from outside.

DNS is the second failure behind the first

Ping by address working while names fail is a resolver problem, and it has its own cause. A client that keeps its local resolver sends those queries into a full tunnel and waits for an answer that never comes, because the local resolver is now on the wrong side of the route.

Push a resolver from the server so the client learns where to ask. Two entries give the client a fallback if the first one is unreachable, and both should be resolvers the tunnel actually allows.

push "dhcp-option DNS 9.9.9.9"
push "dhcp-option DNS 149.112.112.112"

Windows clients may then fall back to the local resolver anyway, which is what block-outside-dns exists to prevent, at the cost of losing names that only the local network can resolve. Linux clients using systemd-resolved often ignore a pushed resolver unless a script installs it, so check resolvectl status for the tunnel device before concluding the push was ignored.

Finish the walkthrough the way a user experiences it. Load a real page from the client, confirm the address it leaves from, reboot the server, reconnect, and repeat the three tests. That second pass is the only one that proves the persisted rules, the firewall policy and the pushed resolver all survived together.

Akụkọ ndị metụtara ya