We all know that transitioning to IPv6 is great in theory, but a complete minefield in practice. If you decide to take it a step further and run both IPv4 and IPv6 traffic through the same VPN tunnel (i.e., go dual-stack), then congratulations; you have just bought yourself a whole new set of problems that will take weeks to solve. In the last 15 years, I have never seen any dual-stack VPN infrastructure I built, managed, or debugged work "flawlessly" on the first try.
In this post, I am putting those insidious technical details under the microscope—the ones that annoy me, constantly drop the tunnel, or silently swallow packets while running dual-stack VPNs on corporate networks and the infrastructures of side projects I have developed. Putting theoretical RFC definitions aside, I will explain why this setup constantly breaks based on real-world scenarios I have encountered in the field, and how I deal with it.
The MTU and MSS Mismatch Nightmare (The Price of the Header Difference)
The biggest and most silent killer of dual-stack VPNs is Maximum Transmission Unit (MTU) mismatch. A standard Ethernet frame carries 1500 bytes of data. However, when we enter a tunnel, things change; while the IPv4 header is 20 bytes, the IPv6 header takes up a fixed 40 bytes. On top of that, when the encryption and tunneling overhead of the VPN protocol (IPsec, OpenVPN, or WireGuard) is added, the usable space shrinks even further.
In a client project, I noticed that while users could access IPv4 sites without any issues, they would experience freezes while loading pages in certain corporate web applications running over IPv6. When I monitored the traffic with tcpdump, I saw that IPv6 packets were being fragmented at the tunnel boundary, and some routers along the tunnel path were blocking ICMPv6 "Packet Too Big" messages for security reasons. This situation rendered the famous Path MTU Discovery (PMTUD) mechanism completely useless.
In the table below, you can see how the remaining maximum MSS (Maximum Segment Size) values for IPv4 and IPv6 change across different VPN protocols:
| Protocol | IPv4 MTU | IPv4 MSS | IPv6 MTU | IPv6 MSS |
|---|---|---|---|---|
| Standard Ethernet | 1500 | 1460 | 1500 | 1440 |
| WireGuard (Standard) | 1420 | 1380 | 1420 | 1360 |
| IPsec (AES-GCM-256) | 1400 | 1360 | 1400 | 1340 |
| OpenVPN (UDP + AES) | 1360 | 1320 | 1360 | 1300 |
To solve this problem, applying MSS clamping on tunnel interfaces is a must. I usually use nftables or iptables on Linux-based VPN gateway servers to aggressively lower the MSS value for IPv6.
# MSS Clamping for IPv4 (Saves lives when PMTUD is not working)
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# MSS Clamping for IPv6 (Must be at least 40 bytes lower)
ip6tables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1340
If you do not apply this setting, small ping packets will pass through the tunnel, while large HTTP POST requests or database queries will get lost in the middle of it. As I also mentioned in my previously written [related: network segmentasyonu] article, it is impossible to build a stable WAN without optimizing packet sizes.
DNS Leaks and the IPv6 Bypassing Issue
The most common mistake in dual-stack VPN configurations is distributing only the IPv4 DNS server to the client and leaving the IPv6 DNS addresses blank. Modern operating systems (Windows 10/11, macOS, and current Linux distributions) prioritize IPv6 for DNS queries if there is an active IPv6 gateway on the network.
While setting up a remote access infrastructure for a bank's internal platform, we noticed that even when the VPN was active, test users could not resolve the company's internal domains; instead, they were hitting the public DNS servers of their internet service providers (ISPs). The reason was simple: the VPN tunnel was only pushing the IPv4 DNS (like 10.0.0.5). However, the router at the user's home had also assigned an IPv6 DNS address to the computer. The computer was querying the IPv6 DNS over the local (and insecure) line, causing internal domain names to leak and fail to resolve.
⚠️ DNS Leak is a Security Risk
When the VPN is active, having IPv6 DNS queries go through the local ISP does not just lead to name resolution errors. It also allows the ISP to log which addresses the user is visiting (DNS Leak).
To verify this leak while the tunnel is active, I run the following command from the terminal to check which DNS servers are responding:
# Listing active DNS servers on Windows
Get-DnsClientServerAddress -AddressFamily IPv6
# Checking resolve status on Linux
resolvectl status tun0
The solution is to explicitly define both IPv4 and IPv6 DNS addresses in the VPN server configuration and enforce the "route all traffic (0.0.0.0/0 and ::/0) through the tunnel" (Full Tunnel) rule on the client. If you are doing a Split Tunnel, you must ensure that your IPv6 routing table fully covers the internal corporate IPv6 blocks.
Happy Eyeballs Protocol (RFC 8305) and Tunnel Instability
Operating systems and browsers use the "Happy Eyeballs" (RFC 8305) algorithm to test whether IPv4 or IPv6 addresses will respond faster when connecting to a website. This algorithm sends connection requests to both addresses almost simultaneously. Whichever responds faster—on the order of milliseconds—gets to carry all the traffic for that session.
When a VPN tunnel is involved, this mechanism causes instability. Generally, IPv4 traffic is subjected to NAT (Network Address Translation) at the VPN gateway, while IPv6 traffic is routed end-to-end. The NAT process introduces a slight latency. This situation causes the Happy Eyeballs algorithm to constantly favor IPv6. However, if the IPv6 tunnel over the VPN is unstable or experiencing packet loss, the connection keeps dropping and reconnecting.
Client (Dual-Stack)
│
├───[ Happy Eyeballs Test ]
│ ├─── IPv4 SYN (VPN Tunnel + NAT) ───> Latency: 45ms
│ └─── IPv6 SYN (Direct Tunnel) ───> Latency: 12ms (Winner!)
│
└───> Entire session is established over IPv6.
└───> If there is an MTU issue in the IPv6 tunnel, the connection freezes!
I experienced this firsthand with the backend API servers of a side project I developed. Mobile clients would sometimes wait 5 seconds to connect to the API, and other times connect instantly. We found that the problem stemmed from unstable latency times in the operators' IPv6 tunnels. Because Happy Eyeballs chose the unstable IPv6 path, users were getting dropped.
To prevent this instability, you need to adjust the routing metrics (metric/distance) at the exit of the VPN server very precisely. Defining the metric of IPv6 routes slightly higher (i.e., less preferred) than IPv4 routes can prevent this artificial race.
Operating System Routing Priorities (Windows vs. Linux/macOS)
When a VPN tunnel is established, new routes are added to the operating system's routing table. However, each operating system calculates the priority (metric) of these routes differently. Windows assigns automatic metrics based on interface speed (Automatic Metric), while Linux follows specific rules in the ip route table.
Particularly when using a dual-stack VPN on Windows 11, the IPv6 metric of the physical Wi-Fi interface can remain lower (meaning higher priority) than the IPv6 metric of the VPN virtual interface (TAP/TUN). In this case, Windows decides, "since there is IPv6 outside anyway," bypasses the VPN tunnel entirely, and sends the traffic directly to the internet via the home router.
To solve this, we have to manually override the interface metrics in the VPN client profile. On the Linux side, I handle this with a script that runs when the tunnel comes up using iproute2:
#!/bin/bash
# tunnel interface: tun0
# We increase the metric value to override the physical interface's IPv6 route
ip -6 route add default dev tun0 metric 50
ip -6 route append default dev wlan0 metric 1000
On the Windows side, you need to add the route-metric parameter to the OpenVPN or WireGuard config file, or pin the interface metric via PowerShell. Otherwise, the operating system will choose the fastest path it sees fit and go outside the tunnel.
Asymmetry in Firewall Policies and Forgotten Rules
The biggest security vulnerabilities and access issues I observe usually stem from asymmetric firewall rules. System administrators (including myself at one point) write, test, and tighten iptables, nftables, or corporate firewall rules for the IPv4 side line by line. However, when it comes to IPv6, the rules are either bypassed or completely turned off, with the thought of "no one uses it anyway" or "we are still in the testing phase."
In a production ERP, we isolated the subnet where the operator screens run to allow only specific IPv4 IPs. However, when the dual-stack VPN was integrated into the system, a client receiving an IP from the VPN server's IPv6 pool was able to directly access the ERP database because no IPv6 firewall rules (ip6tables) were written. This was a very serious security vulnerability.
Below, I provide an example of basic firewall rules that should be run in parallel for both IPv4 and IPv6 on a dual-stack Linux VPN gateway:
# IPv4 Rules (iptables)
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -d 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -P FORWARD DROP
# IPv6 Rules (ip6tables) - Must match one-to-one!
ip6tables -A FORWARD -i tun0 -o eth0 -s fd00:db8:a::/64 -d fd00:db8:b::/64 -j ACCEPT
ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -P FORWARD DROP
If you do not run a separate security policy for IPv6 in your infrastructure, you open a massive backdoor in your network the moment you activate the dual-stack VPN. Every IPv4 rule must have a corresponding counterpart on the IPv6 side.
What is the Solution? A Pragmatic Approach and Workarounds
If everything I have explained has intimidated you, you are right. Managing a dual-stack VPN in a corporate environment can indeed bring an operational burden that is not worth the effort. So, what do I do? Here are the pragmatic approaches I apply in the field that save lives:
- Do Not Carry IPv6 in the Tunnel Unless Necessary: If your internal applications (ERP, databases, file servers) do not have a vital need for IPv6 support, configure the VPN tunnel as IPv4-only. Leave the client's IPv6 traffic outside the tunnel (local breakout).
- Using NAT64 and DNS64: If your clients are only on IPv6 networks (for example, some mobile operators only provide IPv6 IPs), you can set up NAT64 and DNS64 on the VPN server side to allow the client to come in over IPv6 and translate them back to IPv4 inside the network. This keeps the inside of the tunnel clean.
- Prefer WireGuard: If you absolutely must go dual-stack, use WireGuard instead of OpenVPN or IPsec. By design, WireGuard carries dual-stack traffic in a much lighter and more stable manner; it is more tolerant of MTU mismatches.
As I have touched upon in other topics I discussed before, like [related: fail2ban yapılandırması], the best rule in system administration is to minimize complexity. Dual-stack VPN, by its nature, doubles the complexity. If you choose this path, you must master the MTU, DNS, and routing details mentioned above and monitor every step.
My firm position is this: unless your infrastructure is fully ready for IPv6 end-to-end, do not get into the fantasy of dual-stack over VPN. IPv4 still works and will continue to work for a long time. Do not create unnecessary night shifts for yourself.
Top comments (0)