The log shows the option the server pushed, the tunnel is up, and internal names still fail. A query to the resolver directly answers, so the server is fine, yet every application keeps asking the local router. What arrived at the client is an offer, not a change to the resolver, and the component that owns name resolution differs on Linux, Windows and macOS. Once you separate the option from its owner, four faults cover almost every report: no hook, two competing hooks, split DNS working as designed, or a short name that needs a suffix rather than a server.

A pushed option is an offer, not an installation

On the server, two directives describe intent rather than effect:

push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"

The client does not receive a configured resolver. It receives environment variables, foreign_option_1 and onward, one per pushed line, and something local has to read them and rewrite the operating system's resolver state. On Linux nothing does that by default, which is why a correct server and a connected client can still resolve through the LAN.

Three stages sit between the two ends: the push must travel, the client must expose it, and a hook must apply it. Read the middle stage in the client log and the last stage in the resolver state, never the reverse, because a log line proves only the first two.

Confirm the push arrived and find the owner

Start by naming the launcher: a bare openvpn command, a systemd instance, NetworkManager, or a vendor client. That decides where a hook can live. On a systemd-resolved host, resolvectl status lists every link with the DNS servers and search domains it actually uses, and the tunnel link is not always called tun0.

If the pushed address appears in the log but not on any link, the integration is missing. If it appears on the link and queries still leave elsewhere, the problem is which queries select that link. NetworkManager with dns=dnsmasq in /etc/NetworkManager/NetworkManager.conf runs a local forwarder that implements split DNS on purpose: names inside the VPN domain go to the VPN resolver, everything else keeps its previous path. That behaviour looks like a fault when you expected all traffic to move; check the per-connection settings with nmcli connection show "My VPN", where ipv4.dns and ipv4.dns-search hold the values behind what the device reports, before calling it broken. If every query really should reach the VPN resolver, remove dns=dnsmasq from NetworkManager.conf and restart NetworkManager, which hands resolution back to the connection settings; otherwise keep split DNS and correct the pushed domain list instead.

Linux: hand the options to the resolver

Distribution packages ship a hook for this, and the client must be allowed to run it. A typical arrangement on a host without systemd-resolved:

script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf

With systemd-resolved the older resolv.conf script may change nothing that matters, because the stub resolver holds per-link state. The update-systemd-resolved helper documents a hook form that uses up-restart and down-pre, which matters when an interface outlives a single connection attempt. Whatever you pick, one component must own the state. Adding an unrelated hook next to NetworkManager gives two writers to the same link, and the result then depends on ordering rather than on your configuration.

There is also the case where NetworkManager ignored the pushed option entirely until the server address was entered in the connection's IPv4 settings, after which the resolver state showed it in use. That is worth trying before you rewrite scripts, because it moves the problem from a shell hook to the connection profile that already carries the tunnel.

Windows and macOS apply different parts

The Windows client does set the VPN adapter's DNS servers, but Windows still chooses an interface by metric and still needs a suffix to expand a short name. Confirm the adapter state rather than the profile:

Get-DnsClientServerAddress -InterfaceAlias "My VPN"
Get-DnsClientNrptPolicy

If the address is missing, set it with Set-DnsClientServerAddress -InterfaceAlias "My VPN" -ServerAddresses 10.8.0.1, then add the suffix to the adapter's IPv4 properties so fileserver becomes fileserver.corp.example. A machine-wide SearchList value under Tcpip\Parameters does the same. Where the long name resolves and the bare one does not, the resolver is already correct and the search list is the fault. The fixes, in order: push a DOMAIN so the client learns the suffix, add that suffix to the adapter or connection search list, and as a temporary measure pin the name in hosts, or LMHOSTS on Windows; NetBIOS browsing does not cross a router, so the broadcast that finds a short name on the office LAN never reaches a tunnel client.

block-outside-dns is a Windows client behaviour, not a portable profile line, and copying it to a Linux or macOS profile does not reproduce it there. If public names work and internal ones fail on Windows, compare the adapter order under Advanced settings with ipconfig /all. On macOS the per-service resolver order belongs to the system, so use scutil --dns to see which resolver is consulted for which domain, and treat any single reported profile fix as client-dependent rather than general.

A virtual machine on the same laptop inherits none of this. Give the guest its own resolver entry for the internal domain, or let the hypervisor NAT forward its queries through the host, which is only correct while the host tunnel is up.

History is the third cause. DNS servers or suffix settings from an earlier profile can survive reconnects, so when the adapter looks correct and lookups still fail, compare the live adapter with Get-VpnConnection and clear or overwrite the stale entry before you touch the server.

Prove where the query went

A capture during one lookup settles the argument: tcpdump -ni eth0 port 53 shows queries on the physical interface, which means they are leaving outside the tunnel and the resolver was never switched. dig @10.8.0.1 name.corp.example then separates the two remaining cases. If the direct query answers, the resolver works and the client is not using it; if it times out, the resolver is unreachable through the tunnel and no local change will help.

If the resolver still in use is the one the provider handed out, the tunnel encrypts the traffic while the names stay visible to that provider. That is a leak question rather than a functional one, and the same capture answers it: a query on the physical interface is going to someone outside the tunnel.

Reachability includes the firewall on the VPN host, which must permit UDP and TCP 53 from the pool. A dropped port looks exactly like a broken push, and so does a resolver that answers only from inside the tunnel network. Repeat the capture after disconnecting and reconnecting once, because settings that appear on the first connect and vanish on the second are a lifecycle fault in the hook, not a missing option.

Keep one owner and verify in order

A script that rewrites adapter DNS based on a guessed tunnel state duplicates configuration the profile already carries, and it fails quietly the first time the guess is wrong. Prefer the connection's own DNS settings or one documented hook, and record which one you chose so the next person does not add a second.

Close the loop with four checks: the pushed address present on the tunnel link, the internal name resolving as a fully qualified name, the same name resolving without the suffix, and both still true after one reconnect. One capture tells you which of the four failed. The OpenVPN 2.6 manual lists the dhcp-option and script directives with their platform notes.

Artiglyn mynjee