Routing to the office, and back
A VPN user reaching the office LAN is a routing problem with two halves: getting there, and getting back. OpenVPN handles the first half, but the second half is the most common reason a new VPN "doesn't work".
alice has a route for 10.10.0.0/24 through the tunnel, and the VPN server forwards her ping onto the office LAN. The office host receives it and replies to 10.8.0.10. But the office host's default gateway is the office router (lanrtr), not the VPN server, so that's where the reply goes. lanrtr has never heard of 10.8.0.0/24 and answers net unreachable. alice sees 100% loss, even though the request arrived.
The packet capture on the office host shows all three steps in order: request in, reply sent to the router's MAC address, and the router's rejection.
The fix is a return route on the office router: 10.8.0.0/24 (and the branch's 10.20.0.0/24) via the VPN server's office address, 10.10.0.1. Now the reply goes office host → lanrtr → VPN server → tunnel.
(While it works, lanrtr may send the office host an ICMP redirect saying "next time, send it straight to 10.10.0.1". That's normal and harmless.)
The alternative is to masquerade VPN traffic as the VPN server's own office address, with NAT on eth1. It needs no change to the office router, but every VPN user then looks like 10.10.0.1 to the office servers, so their logs and firewall rules can't tell users apart. The return route is better whenever you can add it.
Forwarding and the forward rule
Getting from the tunnel to the office also depends on two things on the server, and it's worth seeing each one fail. Both captures come from a second run of the lab, where the server was left with Debian's default of forwarding off (the lab's containers would otherwise have it on, from Docker).
- IP forwarding. With
net.ipv4.ip_forward = 0, alice can ping the server's own tunnel address, but nothing beyond it: the server receives her packets for the office and has no permission to route them. The/etc/sysctl.d/99-openvpn.conffile, loaded withsysctl --system, turns it on now and at every boot. - The forward rule. The firewall's forward chain drops everything by default, so forwarding only works where a rule allows it. Delete the "VPN users to the office LAN" rule and alice's pings are counted by the final drop counter instead. Loading the ruleset again puts it back.
So when traffic through the server fails, check forwarding, then the firewall counters, then the return route.
root@alice:~# ip route
default via 198.51.100.1 dev eth0
10.8.0.0/24 dev tun0 proto kernel scope link src 10.8.0.10
10.10.0.0/24 via 10.8.0.1 dev tun0
10.20.0.0/24 via 10.8.0.1 dev tun0
198.51.100.0/24 dev eth0 proto kernel scope link src 198.51.100.21
root@alice:~# ping -c 3 -W 1 10.10.0.20
PING 10.10.0.20 (10.10.0.20) 56(84) bytes of data.
--- 10.10.0.20 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2026ms
root@lanrtr:~# ip route get 10.8.0.10
RTNETLINK answers: Network is unreachable- Lines 4–5: alice knows to send office and branch traffic through the tunnel.
- Lines 8–12: Yet every ping is lost.
- Lines 15–16: The office router has no route back to the VPN subnet.
root@office:~# timeout 4 tcpdump -lnni eth0 -e icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
05:18:24.588170 3e:f3:0e:5c:2f:d7 > 86:00:6f:de:c9:97, ethertype IPv4 (0x0800), length 98: 10.8.0.10 > 10.10.0.20: ICMP echo request, id 19044, seq 1, length 64
05:18:24.588182 86:00:6f:de:c9:97 > 72:fb:9a:77:ea:65, ethertype IPv4 (0x0800), length 98: 10.10.0.20 > 10.8.0.10: ICMP echo reply, id 19044, seq 1, length 64
05:18:24.588196 72:fb:9a:77:ea:65 > 86:00:6f:de:c9:97, ethertype IPv4 (0x0800), length 126: 10.10.0.254 > 10.10.0.20: ICMP net 10.8.0.10 unreachable, length 92
05:18:25.632197 3e:f3:0e:5c:2f:d7 > 86:00:6f:de:c9:97, ethertype IPv4 (0x0800), length 98: 10.8.0.10 > 10.10.0.20: ICMP echo request, id 19044, seq 2, length 64
05:18:25.632213 86:00:6f:de:c9:97 > 72:fb:9a:77:ea:65, ethertype IPv4 (0x0800), length 98: 10.10.0.20 > 10.8.0.10: ICMP echo reply, id 19044, seq 2, length 64
05:18:25.632257 72:fb:9a:77:ea:65 > 86:00:6f:de:c9:97, ethertype IPv4 (0x0800), length 126: 10.10.0.254 > 10.10.0.20: ICMP net 10.8.0.10 unreachable, length 92
6 packets captured
6 packets received by filter
0 packets dropped by kernel- Line 4: alice's request arrives (from the VPN server's MAC).
- Line 5: The office host replies, to its default gateway's MAC: lanrtr's.
- Line 6: lanrtr: net 10.8.0.10 unreachable. The reply dies here.
root@lanrtr:~# ip route add 10.8.0.0/24 via 10.10.0.1 && ip route add 10.20.0.0/24 via 10.10.0.1 && ip route
10.8.0.0/24 via 10.10.0.1 dev eth0
10.10.0.0/24 dev eth0 proto kernel scope link src 10.10.0.254
10.20.0.0/24 via 10.10.0.1 dev eth0
root@alice:~# ping -c 3 -W 1 10.10.0.20
PING 10.10.0.20 (10.10.0.20) 56(84) bytes of data.
64 bytes from 10.10.0.20: icmp_seq=1 ttl=62 time=0.352 ms
64 bytes from 10.10.0.20: icmp_seq=2 ttl=62 time=0.941 ms
64 bytes from 10.10.0.20: icmp_seq=3 ttl=62 time=0.585 ms
--- 10.10.0.20 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2062ms
rtt min/avg/max/mdev = 0.352/0.626/0.941/0.242 ms- Lines 1–4: Both VPN-side networks via the VPN server's office address.
- Lines 8–13: Now every ping is answered.
root@alice:~# ping -c 2 -W 1 10.8.0.1 | tail -2; ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1010ms
rtt min/avg/max/mdev = 0.169/0.171/0.174/0.002 ms
2 packets transmitted, 0 received, 100% packet loss, time 1023ms
root@vpn:~# sysctl net.ipv4.ip_forward; nft list chain inet filter forward | grep -E "tun0.*eth1"
net.ipv4.ip_forward = 0
iifname "tun0" oifname "eth1" accept comment "VPN users to the office LAN"
root@vpn:~# cp 99-openvpn.conf /etc/sysctl.d/ && sysctl --system | grep ip_forward
net.ipv4.ip_forward = 1
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1016ms
rtt min/avg/max/mdev = 0.239/0.263/0.287/0.024 ms- Lines 1–4: The tunnel works (the server answers on 10.8.0.1), but the office host doesn't.
- Lines 7–9: Forwarding is off, even though the firewall would allow the traffic.
- Lines 11–12: Install the sysctl.d file and load it: forwarding on, now and after a reboot.
- Lines 14–16: alice reaches the office.
root@vpn:~# nft -a list chain inet filter forward | grep -E "policy|office LAN|everything"
type filter hook forward priority filter; policy drop;
iifname "tun0" oifname "eth1" accept comment "VPN users to the office LAN" # handle 9
counter packets 0 bytes 0 comment "everything else, including user to user, is dropped" # handle 12
root@vpn:~# nft delete rule inet filter forward handle 9
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 0 received, 100% packet loss, time 1031ms
root@vpn:~# nft list chain inet filter forward | grep everything
counter packets 2 bytes 168 comment "everything else, including user to user, is dropped"
root@vpn:~# systemctl restart nftables && nft list chain inet filter forward | grep -c "office LAN"
1
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1062ms
rtt min/avg/max/mdev = 0.184/0.198/0.212/0.014 ms- Lines 1–4: The forward chain: policy drop, the rule letting VPN users into the office, and the final counter.
-ashows each rule's handle. - Lines 6–9: Delete the office rule, and alice's pings stop.
- Lines 12–13: The final counter caught them.
- Lines 15–20: Reloading the saved ruleset brings the rule back, and alice gets through.
Check your understanding
A new OpenVPN user can't reach an office server, though their client shows the pushed route. A capture on the office server shows the request arriving and a reply leaving. What's the most likely problem?
- The office server's default gateway has no route back to the VPN subnet.
- The user's certificate has been revoked.
- client-to-client is off.
- The tls-crypt key is wrong.