Skip to content

Commit 2144553

Browse files
committed
update / 2026-03-14-Windows-WSL-DNS-and-VPN.md
1 parent c454ae9 commit 2144553

File tree

1 file changed

+63
-29
lines changed

1 file changed

+63
-29
lines changed

docs/posts/2026/2026-03-14-Windows-WSL-DNS-and-VPN.md

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ categories:
88
comments: true
99
date:
1010
created: 2026-03-14
11-
updated: 2026-03-14
11+
updated: 2026-03-15
1212
description: Windows 11 + WSL 2 DNS tunneling with OpenVPN split tunnel.
1313
---
1414

@@ -103,11 +103,13 @@ So if your main goal is "WSL should behave like Windows when the VPN connects,"
103103

104104
A split tunnel means only company routes go through the VPN. Your normal internet traffic keeps using the local connection, while internal company names still resolve correctly.
105105

106+
Here is the official OpenVPN Connect split-DNS documentation for mobile clients. The same DNS behavior also matches what this post relies on for Windows split DNS: https://openvpn.net/connect-docs/dns-servers-mobile-behavior.html
107+
106108
### OpenVPN Client Configuration
107109

108110
Use a client config like this:
109111

110-
```ovpn
112+
```bash title="part of split-tunnel.ovpn for OpenVPN Community version"
111113
# Prevent the VPN from replacing your normal internet route
112114
route-nopull
113115

@@ -119,38 +121,52 @@ route 172.16.0.0 255.240.0.0 vpn_gateway
119121
dhcp-option DNS 10.0.0.1
120122
dhcp-option DNS 10.0.0.2
121123

122-
# Set the VPN adapter's connection-specific DNS suffix
124+
# Send DNS queries for company domains to the above VPN DNS servers
123125
dhcp-option DOMAIN corp.example
124-
125-
# Optional: if your company uses more than one internal suffix
126-
# dhcp-option DOMAIN-SEARCH eng.corp.example
127-
# dhcp-option DOMAIN-SEARCH corp.example
126+
dhcp-option DOMAIN internal.example
128127
```
129128

130-
The key line here is `route-nopull`. Without it, many company VPN profiles push a full-tunnel default route, often via `redirect-gateway`, which sends all traffic through the VPN gateway instead of only company traffic.
129+
### Option `route-nopull`
131130

132-
That usually makes normal internet access slower and more fragile. Public traffic such as Microsoft Teams calls, public Docker image pulls, package downloads, and ordinary web browsing can all end up going through the corporate VPN path even though they do not need to.
131+
`redirect-gateway` is a common default company VPN config. It pushes a full-tunnel default route, which means it forces to send all traffic through the VPN gateway instead of only company traffic. But that usually makes normal internet access slower and more fragile. Public traffic such as Microsoft Teams calls, public Docker image pulls, package downloads, and ordinary web browsing can all end up going through the corporate VPN path even though they do not need to.
133132

134-
With `route-nopull`, you keep the VPN tunnel itself, but you only add the private routes you actually want to send through it.
133+
`route-nopull` tells the client not to install server-pushed routes. That is what prevents the VPN from silently turning your split tunnel back into a full tunnel. In a typical split-tunnel setup, this already makes a pushed `redirect-gateway` ineffective, because the server can no longer replace your default route.
135134

136135
!!! note "Manual routes required"
137136

138137
The tradeoff is that you need to know the company private subnets in advance and add them manually. If you miss one, that internal network will stay on your normal local route and will not be reachable through the VPN.
138+
Depending on your environment, you may be able to mitigate part of this caveat with domain-based routing instead of relying only on static subnet routes. OpenVPN Access Server documents this here: [Tutorial: Configure Domain Routing in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--domain-routing-in-access-server.html). This requires Access Server 3.1+ and DNS server proxy support.
139+
140+
### Option `dhcp-option DNS` and `dhcp-option DOMAIN`
139141

140-
### Why `dhcp-option DOMAIN` Matters
142+
In split-tunnel mode, `dhcp-option DNS` and `dhcp-option DOMAIN` work together. `dhcp-option DNS 10.0.0.1` tells the client which VPN DNS server to use, and repeated `dhcp-option DOMAIN ...` lines tell the client which domain suffixes should be resolved through that VPN DNS server instead of your local DNS path.
143+
144+
This is the OpenVPN Connect split-DNS behavior described in the official docs. In other words, a config like this:
145+
146+
```bash
147+
dhcp-option DNS 10.0.0.1
148+
dhcp-option DNS 10.0.0.2
149+
150+
dhcp-option DOMAIN corp.example
151+
dhcp-option DOMAIN internal.example
152+
```
141153

142-
`dhcp-option DOMAIN corp.example` does not create Windows NRPT rules by itself. What it does is set the VPN adapter's connection-specific DNS suffix.
154+
means "send DNS for `*.corp.example` and `*.internal.example` to `10.0.0.1` and `10.0.0.2`", all other DNS queries go through the local DNS path.
143155

144156
That matters for two reasons:
145157

146-
- short names like `app` can expand to `app.corp.example`;
147-
- with WSL DNS tunneling enabled, Windows DNS suffixes are propagated into WSL's `search` line, so Linux tools benefit from the same suffix.
158+
- `app.corp.example` can be resolved through the VPN DNS servers while unrelated public domains keep using your normal local DNS path;
159+
- if the first domain is also used as the primary DNS suffix on Windows, short names like `app` can expand to `app.corp.example`, and WSL DNS tunneling can inherit that suffix into WSL's `search` line.
148160

149-
If your VPN already deploys NRPT rules, Windows will still use those rules for `*.corp.example`. The `DOMAIN` option complements NRPT by making suffix search consistent instead of leaving Windows and WSL to guess.
161+
In practice, repeated `dhcp-option DOMAIN` lines can be used for multiple company domains. That matches the OpenVPN Connect split-DNS behavior described in the official docs, where added domains cause only those domains to use the VPN DNS servers.
150162

151-
This is also why `.local` is a poor example for corporate DNS. `.local` is typically treated as mDNS, not normal unicast DNS, so it can make a healthy VPN setup look broken.
163+
OpenVPN Access Server docs note that Windows clients might only use the first domain provided in DNS Resolution Zones as the DNS domain suffix. So if short-name expansion matters, put your primary suffix first and verify it with `ipconfig /all` or `Get-DnsClient`.
152164

153-
### How to Find Your VPN DNS Servers
165+
OpenVPN Connect docs describe split-tunnel DNS this way: if a pushed VPN DNS server is present with no added domains, all DNS requests go to that VPN DNS server; if added domains are present, only DNS requests for those domains go there.
166+
167+
This is also why `.local` is a poor example for corporate DNS. `.local` is typically treated as [mDNS](https://en.wikipedia.org/wiki/.local), not normal unicast DNS, so it can make a healthy VPN setup look broken.
168+
169+
### How to find company VPN DNS servers
154170

155171
To get the correct internal DNS IPs:
156172

@@ -160,24 +176,29 @@ To get the correct internal DNS IPs:
160176
4. Copy the IPs listed under `DNS Servers`.
161177
5. Reuse those IPs in your split-tunnel config.
162178

163-
### OpenVPN 2.7+ and `pull-filter`
179+
### OpenVPN Community Client 2.7 and `pull-filter`
164180

165-
`route-nopull` tells the client not to install server-pushed routes and DHCP-style DNS options. That is what prevents the VPN from silently turning your split tunnel back into a full tunnel. In OpenVPN 2.7+, you may still see warnings because the server did push those directives and your client is overriding them.
181+
This section is about OpenVPN Community Client 2.7 (the latest version as of March 2026) config syntax, not OpenVPN Connect.
166182

167-
`pull-filter ignore` is the cleanup step: it drops matching server-pushed directives before OpenVPN processes them. That keeps the logs quieter and makes it explicit that your local client config is authoritative.
183+
`pull-filter ignore` is the cleanup step: it drops matching server-pushed directives before OpenVPN processes them. In practice, that keeps the OpenVPN Community client logs quieter and makes it explicit that your local client config is authoritative.
168184

169-
```ovpn
170-
pull-filter ignore "redirect-gateway"
171-
pull-filter ignore "route"
172-
pull-filter ignore "dhcp-option "
185+
```bash
186+
pull-filter ignore "dhcp-option"
187+
pull-filter ignore "route "
173188
```
174189

175190
Notes:
176191

177192
- `pull-filter` uses prefix matching.
178-
- `pull-filter ignore "route"` drops pushed `route`, `route-gateway`, and other route-related directives.
179-
- `pull-filter ignore "dhcp-option "` drops pushed DNS and domain suffix options so they do not conflict with the values you set locally.
180-
- Only add these filters if the server really is pushing overlapping directives and you want the local client config to win.
193+
- With `route-nopull` in place, you normally do not need `pull-filter ignore "redirect-gateway"`, because `route-nopull` already prevents the pushed full-tunnel route from being installed. We still add `pull-filter ignore "dhcp-option"` and `pull-filter ignore "route "` because they stop the OpenVPN Community client from trying to process those pushed options and cluttering the logs with warnings such as:
194+
195+
```bash
196+
Options error: option 'dhcp-option' cannot be used in this context ([PUSH-OPTIONS])
197+
Options error: option 'route' cannot be used in this context ([PUSH-OPTIONS])
198+
```
199+
200+
!!! warning "pay attention to trailing whitespace in `"route "`"
201+
Keep the trailing space in `pull-filter ignore "route "`. If you change it to `pull-filter ignore "route"`, it also matches pushed options such as `route-gateway`, which can break manual routes that rely on `vpn_gateway`.
181202

182203
## Verification
183204

@@ -189,11 +210,24 @@ grep -E '^(nameserver|search)' /etc/resolv.conf
189210
# search home.lan corp.example
190211
```
191212

213+
If you use `systemd-resolved`, `/etc/resolv.conf` may instead point to the local stub at `127.0.0.53`. In that case, check the effective upstream DNS server:
214+
215+
```bash
216+
❯ resolvectl status | grep -A 5 'Global'
217+
Global
218+
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
219+
resolv.conf mode: foreign
220+
DNS Servers: 10.255.255.254
221+
Fallback DNS Servers: 1.1.1.1
222+
```
223+
224+
That means Linux apps talk to the local stub first, but the real upstream is still WSL's Windows-side DNS tunneling IP (`10.255.255.254`).
225+
192226
Then test name resolution and routing:
193227

194228
- `getent hosts app.corp.example`: confirms that a fully qualified internal name resolves.
195229
- `getent hosts app`: if you rely on short names, this confirms that the DNS suffix search list is working.
196230
- `nc -zv app.corp.example 443`: confirms both DNS resolution and reachability over the VPN, if the target app is actually listening on TCP port 443.
197-
- `curl ifconfig.me`: should still show your normal local public IP, proving that internet traffic is not being forced through the VPN.
231+
- `curl ifconfig.me`: can be a quick sanity check that public traffic is not being forced through the VPN, but verify that the resolved `ifconfig.me` IP is not itself inside one of your VPN-routed prefixes.
198232

199-
If `/etc/resolv.conf` points at `127.0.0.53` or a manually configured public DNS server instead of `10.255.255.254`, you are not using the modern WSL DNS tunneling path described above.
233+
If `/etc/resolv.conf` points directly at `10.255.255.254`, you are using the simple WSL DNS tunneling layout. If it points at `127.0.0.53` but `resolvectl status` still shows `10.255.255.254` upstream, you are still using WSL DNS tunneling underneath a local `systemd-resolved` stub.

0 commit comments

Comments
 (0)