Split and full tunnels
A split tunnel sends only the private networks through the VPN; a full tunnel sends everything, so the user's internet traffic leaves from the VPN server. The full tunnel costs more bandwidth on the server, and in return the user is protected on untrusted networks and appears from your address.
The choice can be per user. alice's ccd file gains one line, push "redirect-gateway def1", and bob's doesn't.
def1 doesn't replace alice's default route. It adds two routes, 0.0.0.0/1 and 128.0.0.0/1, which together cover every address and, being more specific, win over the default. Disconnecting removes them, and the original default is still there. OpenVPN also adds a host route to the VPN server itself via the real gateway; otherwise the tunnel's own packets would try to go through the tunnel.
On the server, full-tunnel traffic needs two things: a forward rule from tun0 to the internet interface (here only for alice's address), and NAT (masquerade), because the internet can't route 10.8.0.0/24 back. The web server's log is the proof: alice now arrives as 203.0.113.10, the VPN server, and bob, still split, arrives as himself.
For full-tunnel users you'll usually also push a DNS server (push "dhcp-option DNS …"), so their lookups go through the tunnel too. Windows, macOS and mobile clients apply it automatically; on Linux it depends on the client (NetworkManager applies it; plain openvpn needs a helper script).
root@vpn:~# echo 'push "redirect-gateway def1"' >> /etc/openvpn/server/ccd/alice && cat /etc/openvpn/server/ccd/alice && systemctl restart openvpn-server@server
ifconfig-push 10.8.0.10 255.255.255.0
push "redirect-gateway def1"
root@alice:~# ip route
0.0.0.0/1 via 10.8.0.1 dev tun0
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
128.0.0.0/1 via 10.8.0.1 dev tun0
198.51.100.0/24 dev eth0 proto kernel scope link src 198.51.100.21
203.0.113.10 via 198.51.100.1 dev eth0
root@alice:~# curl -s -o /dev/null -w "%{http_code}\n" http://203.0.113.80/
200
root@bob:~# curl -s -o /dev/null -w "%{http_code}\n" http://203.0.113.80/
200
root@web:~# tail -2 /tmp/web.log
203.0.113.10 - - [21/Oct/2026 05:20:59] "GET / HTTP/1.1" 200 -
198.51.100.22 - - [21/Oct/2026 05:20:59] "GET / HTTP/1.1" 200 -
root@vpn:~# nft list chain ip nat postrouting
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "eth0" ip saddr 10.8.0.0/24 masquerade comment "full-tunnel users appear as the server"
}
}- Lines 1–3: One extra line in alice's ccd file, then a restart.
- Line 6: Half of all addresses…
- Line 11: …and the other half, both through the tunnel. More specific than the default route below, so they win.
- Line 7: The original default route is still there, for when she disconnects.
- Line 13: The VPN server itself is still reached directly, so the tunnel's own packets don't loop.
- Line 22: The web server sees alice as the VPN server's address…
- Line 23: …and bob as himself.
- Line 29: The NAT rule that makes the difference.