The tunnel comes up. wg show prints a recent handshake. The client has its address inside the tunnel. And not a single byte reaches the internet.
Almost every guide answers this with "open UDP 51820 in the firewall". You already did that — it is why the handshake works at all. The problem is somewhere else, and UFW makes the distinction easy to miss:
Entering a machine and traversing it are two different permissions.
ufw allow 51820/udp lets packets arrive at the server. Your clients' traffic does not stop there — it goes through the box and out the public interface. That path lives in the FORWARD chain, which UFW denies by default and which no allow rule touches.
The four things to check, in order
1. IP forwarding — and the file that overwrites the other file
This is the one that costs hours, because the setting looks done.
UFW loads its own sysctl file at startup, and it takes precedence over the system one. A value you carefully set in /etc/sysctl.conf can be silently overwritten on the next ufw enable.
The right place is /etc/ufw/sysctl.conf:
net/ipv4/ip_forward=1
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1
Then check the effective value, not the file you just edited:
sysctl net.ipv4.ip_forward
2. Forwarding, which is not the same as ingress
Targeted, and the one to prefer:
sudo ufw route allow in on wg0 out on eth0
Or globally, in /etc/default/ufw:
DEFAULT_FORWARD_POLICY="ACCEPT"
The second opens forwarding for every interface. It is a good ten-second diagnostic and a poor permanent configuration.
3. NAT, which UFW never adds on its own
Without it, packets leave carrying their tunnel address, which nothing on the internet knows how to answer. In /etc/ufw/before.rules, at the very top, before the *filter line:
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
Two classic mistakes here: putting this block after *filter (it is then ignored), and copying eth0 without checking.
4. The real name of the egress interface
eth0 is an article convention, not a fact. Depending on the host it will be ens3, enp1s0, something else. The command that answers without guessing:
ip route get 1.1.1.1
The test that separates the two families of causes
From a connected client, try to reach the server's address inside the tunnel (often 10.8.0.1):
| What you observe | What it means |
|---|---|
| tunnel address answers, nothing external does | tunnel is fine — forwarding or NAT missing |
| tunnel address does not answer either | upstream: keys, AllowedIPs, client routing |
| everything answers but names do not resolve | not UFW at all — it is the DNS pushed to the client |
That third row is worth knowing. "I have no internet" very often means "no name resolves". Pinging an IP directly tells you in one command.
After applying
before.rules is only re-read on reload:
sudo ufw disable && sudo ufw enable
sudo ufw status verbose
sudo iptables -t nat -L POSTROUTING -n -v
One last thing that produces duplicate configurations: if your PostUp/PostDown rules in the WireGuard config already add MASQUERADE, you do not need the *nat block above. Both together rarely break anything, but they make the next outage much harder to read — and there will be a next one.
If you are still choosing a port or wondering whether 443/UDP is worth it, I wrote a longer piece on WireGuard ports, changing them and opening them properly.
One line to keep: an open port means packets can enter, not that they can traverse.
Top comments (0)