# Windows 11 WSL2 DNS and OpenVPN Split Tunnel

On modern Windows 11 + WSL 2, the reliable VPN-friendly DNS path is WSL DNS tunneling. If you want Linux tools inside WSL to follow the same DNS policy as Windows apps, keep `/etc/resolv.conf` auto-generated and let the Windows DNS Client make the routing and suffix decisions.

This post focuses on the current Windows 11 + WSL 2 model, not the older manual `resolv.conf` workaround. It also shows how to keep OpenVPN in split-tunnel mode while preserving internal DNS resolution in both Windows and WSL.

## Modern WSL2 DNS on Windows 11

### Recommended Configuration

In Windows, configure `%UserProfile%\.wslconfig`:

```
[wsl2]
# Optional, already the default on current WSL releases
# dnsTunneling=true
```

In WSL, keep `/etc/wsl.conf` simple:

```
[network]
# Optional, already the default; keep it enabled for WSL DNS tunneling
# generateResolvConf=true
```

If you also want `systemd`, enable it separately:

```
[boot]
systemd=true
```

Then restart WSL from PowerShell:

```
wsl --shutdown
```

Notes:

- `dnsTunneling=true` is the feature that makes WSL DNS follow Windows DNS Client behavior. Microsoft specifically recommends it when you use a VPN, because many VPN clients rely on NRPT rules, and those rules are only applied to WSL DNS queries when DNS tunneling is enabled.
- What matters for DNS tunneling is that you do not disable `generateResolvConf`, because Microsoft notes DNS tunneling will not be successfully enabled if `generateResolvConf=false`.
- `systemd=true` is optional. It is useful for services and timers, but it does not need to "own" `/etc/resolv.conf` in this setup.

### What `/etc/resolv.conf` Should Look Like

With DNS tunneling enabled, WSL generates `resolv.conf` and points it at the Windows-side DNS tunnel IP:

```
$ cat /etc/resolv.conf
# This file was automatically generated by WSL.
nameserver 10.255.255.254
search home.lan corp.example
```

Notes:

- `10.255.255.254` is WSL's `dnsTunnelingIpAddress`.
- In NAT mode with DNS tunneling enabled, WSL brings over Windows DNS suffixes into the `search` line. The order follows Windows DNS behavior: global suffixes first, then supplemental suffixes, then per-interface suffixes.
- The exact `search` values change when your network or VPN state changes.

### The Resolution Path

When a WSL app resolves `app.corp.example`, the flow is:

1. The Linux app reads `/etc/resolv.conf` and sends the query to `10.255.255.254`.
2. WSL tunnels the request directly to Windows instead of sending a normal DNS packet from the VM to the host.
3. The Windows DNS Client applies Windows DNS policy: NRPT rules, DNS suffix search order, per-interface DNS servers, and Windows-side DoH or DoT settings.
4. Windows forwards the query to the appropriate resolver, such as your home router or the VPN's internal DNS servers.

This is the key change on modern WSL 2: Windows, not a hand-configured Linux stub resolver, makes the final DNS policy decision.

### Manual `resolv.conf` Mode (`generateResolvConf=false`)

You can still disable WSL's generated `resolv.conf` and point Linux at `systemd-resolved` or another local resolver, but for modern Windows 11 + WSL 2 + VPN setups that is an advanced custom path, not the recommended one.

If you set `generateResolvConf=false`:

- you opt out of the WSL-generated DNS tunnel configuration;
- you become responsible for upstream DNS servers and suffix search behavior inside Linux;
- WSL will no longer automatically mirror Windows VPN DNS behavior for you.

So if your main goal is "WSL should behave like Windows when the VPN connects," keep `generateResolvConf=true`.

## OpenVPN Split Tunnel with DNS

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.

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](<https://openvpn.net/connect-docs/dns-servers-mobile-behavior.html>)

### OpenVPN Client Configuration

Use a client config like this:

part of split-tunnel.ovpn for OpenVPN Community version

```
# Prevent the VPN from replacing your normal internet route
route-nopull

# Keep the active VPN server endpoint on the local internet path
# this route ensure you can contact the vpn gateway
route remote_host 255.255.255.255 net_gateway

# Send only company networks through the VPN
route 10.0.0.0       255.0.0.0       vpn_gateway
route 172.16.0.0     255.240.0.0     vpn_gateway

# Use company DNS on the VPN adapter
dhcp-option DNS 10.0.0.1
dhcp-option DNS 10.0.0.2

# Send DNS queries for company domains to the above VPN DNS servers
dhcp-option DOMAIN corp.example
dhcp-option DOMAIN internal.example
```

### Option `route-nopull`

`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.

`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.

> [!NOTE]
>
> **Manual routes required**
>
> 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. 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. One more caveat: if one of your company public subnets also contains the VPN server's own public IP, add `route remote_host 255.255.255.255 net_gateway`. That keeps the active VPN endpoint on your local internet route. Without it, a broad route such as `188.65.120.0/21 -> vpn_gateway` can accidentally capture the VPN server itself, send the control channel back into the tunnel, and make the connection flap or die after it comes up.

### Why `route remote_host ... net_gateway` matters

`remote_host` is the active `remote` endpoint IP that OpenVPN is currently connected to. In client mode, OpenVPN updates it to the selected server address, so:

```
route remote_host 255.255.255.255 net_gateway
```

creates a host route for only that one VPN server IP through your normal local gateway.

That is important when the VPN server IP lives inside a broader company subnet that you also need to send through the tunnel. For example, this combination is valid:

```
188.65.121.190/32 -> 192.168.111.1
188.65.120.0/21   -> 192.168.200.1
```

The `/32` route is more specific, so Windows keeps the VPN server itself on Wi-Fi while the rest of the `188.65.120.0/21` company range still goes through the VPN.

Do not solve that by sending the whole public company block to `net_gateway`, unless you really want all of it outside the tunnel. Usually you only want the VPN server endpoint itself to stay outside.

### Option `dhcp-option DNS` and `dhcp-option DOMAIN`

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.

This is the OpenVPN Connect split-DNS behavior described in the official docs. In other words, a config like this:

```
dhcp-option DNS 10.0.0.1
dhcp-option DNS 10.0.0.2

dhcp-option DOMAIN corp.example
dhcp-option DOMAIN internal.example
```

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.

That matters for two reasons:

- `app.corp.example` can be resolved through the VPN DNS servers while unrelated public domains keep using your normal local DNS path;
- 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.

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.

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`.

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.

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.

### How to find company VPN DNS servers

To get the correct internal DNS IPs:

1. Connect to the VPN using the original working profile.
2. In PowerShell, run `ipconfig /all`.
3. Find the OpenVPN adapter.
4. Copy the IPs listed under `DNS Servers`.
5. Reuse those IPs in your split-tunnel config.

### OpenVPN Community Client 2.7 and `pull-filter`

This section is about OpenVPN Community Client 2.7 (the latest version as of March 2026) config syntax, not OpenVPN Connect.

`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.

```
pull-filter ignore "dhcp-option"
pull-filter ignore "route "
```

Notes:

- `pull-filter` uses prefix matching.
- 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:

```
Options error: option 'dhcp-option' cannot be used in this context ([PUSH-OPTIONS])
Options error: option 'route' cannot be used in this context ([PUSH-OPTIONS])
```

> [!WARNING]
>
> **pay attention to trailing whitespace in `"route "`**
>
> 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`.

## Verification

Inside WSL, confirm that DNS tunneling is active:

```
grep -E '^(nameserver|search)' /etc/resolv.conf
# nameserver 10.255.255.254
# search home.lan corp.example
```

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:

```
❯ resolvectl status | grep -A 5 'Global'
Global
           Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
    resolv.conf mode: foreign
         DNS Servers: 10.255.255.254
Fallback DNS Servers: 1.1.1.1
```

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`).

Then test name resolution and routing:

- `getent hosts app.corp.example`: confirms that a fully qualified internal name resolves.
- `getent hosts app`: if you rely on short names, this confirms that the DNS suffix search list is working.
- `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.
- `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.

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.
