Blog · Networking · 7 min read

Split Horizon DNS: Why the VPN Resolves Differently

The same name answers twice, and which answer a laptop gets depends on which resolver the tunnel handed it. Here is how to prove it in two commands and fix it so it stays fixed.

By Marko Ristic, Editor Published Aug 14, 2026 · Updated Sep 9, 2026

The report is always the same shape. In the office, files.company.com opens instantly. On the VPN, it times out, or worse, it opens the public website instead of the file server. Both machines are joined to the same domain and neither has a problem the user can describe.

What is actually happening

The name exists twice. An internal DNS server answers it with a private address, and the public DNS answers it with a different one, or does not answer at all. Which reply a machine gets depends entirely on which resolver it asked, and the VPN decided that for it.

The ideaWhat split horizon DNS is

Split horizon DNS, sometimes called split brain, means one domain name has two sets of records: one served to queries from inside the network, another served to everyone else. The internal view points at private addresses. The external view points at whatever the public internet should reach, which is often nothing at all.

It exists for a good reason. Internal services live on private addresses, those addresses must never appear in public DNS, and staff should be able to use the same name from anywhere. Publishing 10.0.5.20 for your file server tells the whole internet how your network is numbered, and it does not work from outside anyway.

The failureWhy the VPN breaks it

A machine in the office gets its DNS servers from DHCP, and they are the internal ones. It asks them, it gets the private address, everything resolves.

A machine on the VPN has two possible resolvers and the tunnel decides which it uses. If the VPN does not push the internal DNS servers, or pushes them alongside a public resolver, the operating system is free to ask either one.

Windows in particular sends the query to whichever interface it considers primary, and that decision is made on interface metrics rather than on which network holds the answer.

So the same laptop resolves correctly on Monday and incorrectly on Tuesday, having changed nothing, because a metric changed when it joined a different wifi network first.

SymptomUsually means
Name resolves to a public address on the VPNThe query went to a public resolver
Name does not resolve at all on the VPNPublic DNS has no record, and the query went there
Resolves by full name, not by short nameThe DNS suffix is not being pushed
Works, then stops after reconnectingInterface metrics reordered the resolvers
Works on one laptop, not anotherOne of them has a manually set DNS server

DiagnosisTwo commands that end the argument

Do not test with a browser. A browser has its own cache, and increasingly its own resolver over HTTPS, so it can be right when the machine is wrong and wrong when the machine is right.

# Which server answered, and with what
nslookup files.company.com

# Ask the internal server directly, bypassing the resolver order
nslookup files.company.com 10.0.1.10

# Windows: the resolver order the machine will actually use
Get-DnsClientServerAddress

Read the server line in the first answer. If it names a public resolver, the diagnosis is finished: the query is leaving the tunnel. If it names the internal server and the answer is still wrong, the record itself is wrong and this is not a VPN problem.

RepairFixing it properly

There is a fix that works and a fix that appears to work, and telling them apart saves a return visit.

The fix that works is pushing the internal DNS servers and the internal search domain to VPN clients, and making sure nothing else is offered alongside them for the internal zone.

On a full tunnel VPN this is easy, because all traffic goes down the tunnel anyway. On a split tunnel it needs care, because the client keeps its local resolver for everything else and something has to decide which queries go where.

The fix that appears to work is editing the hosts file on the affected laptop. It resolves the ticket in two minutes and creates a machine that will fail confusingly the day the server is renumbered. If you do it, write it down.

On Windows, the durable answer for split tunnel VPNs is the Name Resolution Policy Table, which routes queries for a named suffix to a named server regardless of interface metrics. It is the one mechanism that makes the behavior deterministic rather than dependent on which network the laptop saw first.

The durable fixMaking it deterministic on Windows

A policy rule takes the decision away from interface metrics entirely. It says that queries for one namespace go to one set of servers, whatever the machine thinks the primary interface is this morning.

# Everything under company.com goes to the internal servers, always
Add-DnsClientNrptRule -Namespace ".company.com" -NameServers "10.0.1.10","10.0.1.11"

# What is in force right now, including rules arriving by policy
Get-DnsClientNrptPolicy

# Remove one you added by hand while testing
Get-DnsClientNrptRule | Where-Object Namespace -eq ".company.com" | Remove-DnsClientNrptRule

The leading dot matters. A namespace written as .company.com matches the domain and everything under it; written without it, it matches one name and nothing else, which is a rule that appears to do nothing for every host the reader actually cares about.

Add it by hand on one laptop to prove the behavior, then deploy it from Group Policy under Computer Configuration, Windows Settings, Name Resolution Policy, so it arrives on machines that never come back to the office.

A rule set by hand is a rule that exists on the one machine somebody tested, which is the same failure as the hosts file with more steps.

Mixed fleetEvery platform, and the cache that hides the fix

The mechanism has a different name everywhere, and the commands that matter are the same three: show the resolver order, send one suffix somewhere specific, and empty the cache.

PlatformShow the resolver orderRoute one suffixFlush the cache
WindowsGet-DnsClientServerAddressNRPT ruleClear-DnsClientCache
macOSscutil --dnsA resolver file, or the VPN profiledscacheutil -flushcache
Linux, systemd-resolvedresolvectl statusresolvectl domain on the tunnelresolvectl flush-caches

The cache is the reason a correct fix gets rolled back. A machine that asked the wrong resolver has the wrong answer stored, and a name that failed to resolve is stored too: negative answers are cached for a period the zone itself sets, and it is often long enough to outlast the person testing.

So the fix goes in, the name still fails, and somebody concludes the fix did not work.

Flush before every retest, and check what is actually held rather than trusting that it cleared. On Windows Get-DnsClientCache lists the entries including the negative ones, which is also the fastest way to prove to somebody else that the machine never asked the server they think it asked.

The new oneEncrypted DNS, which bypasses all of it

Browsers and operating systems increasingly send DNS over HTTPS to a resolver of their own choosing. That resolver has no idea your internal zone exists, and it will never return a private address for it.

The symptom is precise and worth recognizing: the name resolves correctly at the command line and fails in one browser. Nothing about the network changed. The browser simply asked somebody else.

The answer is policy rather than plumbing. Every major browser can be told to leave encrypted DNS off for managed machines, and every major operating system honors a setting for it. On a network with internal names, that setting belongs in the standard build.

  1. Run nslookup and read the server line first

    The name it resolved to matters less than which server answered. A public resolver in that line is the whole diagnosis.

  2. Query the internal server by address directly

    If the internal server returns the right record, the zone is fine and the problem is resolver selection on the client.

  3. Push the internal DNS servers and the search suffix to VPN clients

    On a full tunnel this is enough. On a split tunnel, add a rule that sends queries for the internal suffix to the internal server regardless of interface metrics.

  4. Turn off encrypted DNS in the managed browser build

    A browser resolving over HTTPS to a public resolver will never see your internal zone, and the symptom looks like a network fault rather than a browser setting.

What to check, in order

Each one rules out a whole class of cause, so working down the list is faster than guessing.

Serverwhich resolver answered the query
Recordwhat the internal server actually holds
Orderthe resolver list the client will use

FAQQuestions from the comments

Why not just publish the internal names in public DNS?

Because the addresses are private and unreachable from outside, and publishing them hands an attacker a map of your internal numbering for free.

Is the hosts file an acceptable fix?

As a temporary one, yes. It stops being acceptable the moment nobody remembers it is there, which is usually the week the server is renumbered.

Does a full tunnel VPN avoid this entirely?

Mostly. All traffic including DNS goes down the tunnel, so the internal resolver is the only one in play. Encrypted DNS in the browser can still route around it.

Why does it work by full name but not by short name?

The search suffix is not being pushed. The machine has nothing to append, so a short name is never turned into a name the internal server recognizes.

Can I use the same domain internally and externally?

Yes, and split horizon DNS is exactly how. The alternative, a separate internal domain such as company.local, avoids the problem and creates certificate problems instead.

How do I actually create an NRPT rule?

Add-DnsClientNrptRule with a namespace and the internal name servers, and the leading dot matters: a namespace written as .company.com matches the domain and everything under it, while the same string without the dot matches one name and nothing else.

Prove it by hand on one laptop, then deploy it from Group Policy so it reaches machines that never come back to the office.

The fix is in and the name still fails.

Flush the cache and try again. The machine stored the wrong answer, and a name that failed to resolve is stored too, because negative answers are cached for a period the zone sets and it is often long enough to outlast whoever is testing. Get-DnsClientCache shows what is actually held, negative entries included.

What is the equivalent on macOS and Linux?

On macOS, scutil --dns shows the resolver order and the VPN profile or a resolver file routes a suffix. On Linux with systemd-resolved, resolvectl status shows the order and resolvectl domain attaches a suffix to the tunnel interface. Both cache, and both need flushing between tests.

Why does one laptop work and an identical one not?

Usually a DNS server set by hand on one of them, left over from troubleshooting something else, which overrides everything the tunnel pushes. Get-DnsClientServerAddress shows it in one line, and it is worth checking before anything more interesting.

Should we just use a separate internal domain instead?

It removes this problem and adds a certificate problem. A name that only exists internally cannot get a certificate from a public authority, so every internal service needs a private authority and every device needs to trust it. Split horizon keeps one name and one certificate and costs this article instead.

Does this affect anything besides file shares?

Anything reached by an internal name, which by now is most things: the intranet, the print server, the certificate revocation list a client checks before trusting a certificate, and the update server. The last two fail quietly and produce symptoms nobody connects to DNS.