The laptop has an IPv6 address, the tunnel is up over IPv4, and every IPv6 test fails. Or worse, the test succeeds and the traffic left through the ISP path without ever entering the tunnel. Both outcomes trace back to one missing decision: nobody gave the router a prefix it could route toward clients. Walking the path from the client outward finds which piece is absent.

Start where the client sits

Look at the client before touching the server. ip -6 addr should show a tunnel address from the pool, and ip -6 route should show the pushed prefix pointing at the tunnel device. When the address is there but no route is, the push is missing or the client ignored it. When neither is there, the server never assigned anything at all.

Then check what the client believes about the outside world. A default route over the tunnel claims all IPv6 traffic, while a route for 2000::/3 claims only global unicast. Both can be correct, and they answer different questions about leaking.

A client with no IPv6 default at all fails every public test while the tunnel itself is perfectly healthy. That is a policy gap rather than a broken tunnel.

Check the pool the server can actually route

A server can only assign from a prefix it actually controls. If the ISP delegates a /56, reserve one /64 for the tunnel and leave the rest for the LAN. Where the ISP hands over one /64 on the WAN with no further routing, leave the LAN prefix alone and give the tunnel a unique local range, accepting that clients will have no global exit.

The pool and the push belong together. server-ipv6 2001:db8:80:10::/64 assigns addresses, and push "route-ipv6 2000::/3" tells clients what to send into the tunnel. Pushing 2000::/3 covers global unicast while leaving link-local and multicast ranges out of the tunnel.

Write the chosen prefix into the deployment record. Whoever changes the ISP handoff next needs to know which /64 the tunnel already occupies.

Turn on forwarding for the second family

IPv4 forwarding does not imply IPv6 forwarding. Set net.ipv6.conf.all.forwarding=1 and mirror whatever the IPv4 FORWARD policy already permits. On a router whose FORWARD chain drops by default, a missing rule set looks exactly like a dead tunnel.

The handshake succeeds, the client receives an address, and nothing comes back. Add rules for both directions and for the WAN leg, because a tunnel that reaches the LAN but not the internet fails the third test. Check the chain counters after a failed attempt, since a rule that never matched is a different problem from one that matched and dropped.

ip6tables -A FORWARD -i br0 -o tun0 -j ACCEPT
ip6tables -A FORWARD -i tun0 -o br0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -A FORWARD -i tun0 -o wan0 -j ACCEPT

One side effect catches people who end up blaming the tunnel. Enabling forwarding makes the kernel skip Router Advertisement address configuration unless told otherwise, and a default route learned that way disappears with it. Either pin an explicit default route on the WAN interface, or deliberately set accept_ra to 2 there. After either change, confirm the router still has a usable IPv6 path out before testing clients again.

Answer neighbour discovery when upstream will not route

Upstream equipment sometimes treats the delegated /64 as belonging to its own link and refuses to route any sub-prefix to the VPN box. When that happens, something has to answer neighbour solicitations for each client address individually.

OpenVPN exposes the assigned address to a client-connect hook, so entries can follow the real pool instead of a hand-maintained list. The hook adds ip -6 neigh add proxy $ifconfig_pool_remote_ip6 dev eth0, and a matching client-disconnect hook removes it. Without the removal, the proxy table grows stale and a later client can inherit an entry for an address it never received. Confirm the state with ip -6 neigh show proxy.

Treat proxying as a workaround. A static route for the tunnel /64 aimed at the router survives client churn and needs no privileged shell hook at all.

Follow the path outward, one hop at a time

Test outward in three steps: the router tunnel address first, a LAN host second, and a public destination through traceroute6 third. The pool is proved by the first, forwarding and the LAN firewall by the second, and the upstream return path by the third. Each failure narrows the search to one device instead of reopening the whole design.

Watch the tunnel MTU, since IPv6 routers along the path will not fragment for you. An oversized MTU surfaces as a packet-too-big error and stalled TLS rather than a clean failure. Set it explicitly and test with a payload near the limit.

Ũhoro ũhĩtĩte