docs: explain macOS DNS resolver behavior for match-only vs primary nameservers (#912)

* docs: explain macOS two-resolver-stack DNS behavior and the match-only vs primary nameserver split

Lab-validated against client 0.76.3 (macOS 26, NetBird Cloud):

- DNS troubleshooting: new Issue 5 'dig and host fail, but browsers and
  curl work (macOS)' with the scoped-resolver vs resolv.conf explanation,
  the language-runtime split table (pure-Go/dnspython/c-ares vs
  getaddrinfo), and the Windows nslookup-vs-NRPT analog; renumbered
  Issues 5-8 to 6-9; checklist step 6 now says why it prescribes
  dscacheutil/Resolve-DnsName
- Internal DNS Servers: primary-vs-match now explains that match-only
  leaves resolv.conf untouched on macOS; new warning that emptying a
  match group's domains silently drops the search suffix (masked on
  domain-joined Windows); split-horizon example gains the
  route-everything-internal variant (the OpenVPN migration shape)
- DNS overview: macOS line now distinguishes scoped resolvers from the
  primary case, where configd regenerates resolv.conf with NetBird's
  resolver

* docs: add dashboard screenshot for the route-all-internal nameserver example

* docs: polish wording in the macOS DNS additions

* docs: promote the route-all-internal example to its own section

* docs: drop the Example prefix from the nameserver scenario headings

* docs: state the public-resolution prerequisite for an internal primary nameserver

* docs: make the direct-resolver check precise

* docs: anchor the scoped-resolver term to scutil output and split the dense solution paragraph
This commit is contained in:
Jack Carter
2026-08-11 16:47:56 +02:00
committed by GitHub
parent ffe10558ec
commit 2cbebc3323
5 changed files with 94 additions and 9 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -98,7 +98,7 @@ Windows Firewall or endpoint security software can block NetBird traffic before
DNS on Windows has a few platform-specific failure modes worth checking separately:
- **Match-domain names don't resolve, even though the NRPT (Name Resolution Policy Table) rule was written.** A lingering Group Policy `DnsPolicyConfig` container can stop NetBird's rule from taking effect on an off-domain machine. See [DNS Troubleshooting: Issue 8 (lingering GPO)](/manage/dns/troubleshooting#issue-8-windows-nrpt-rule-is-written-but-never-takes-effect-lingering-gpo).
- **Match-domain names don't resolve, even though the NRPT (Name Resolution Policy Table) rule was written.** A lingering Group Policy `DnsPolicyConfig` container can stop NetBird's rule from taking effect on an off-domain machine. See [DNS Troubleshooting: Issue 9 (lingering GPO)](/manage/dns/troubleshooting#issue-9-windows-nrpt-rule-is-written-but-never-takes-effect-lingering-gpo).
- **Active Directory login, mapped drives, or DFS fail** while a file share by IP works. This is usually a DC-locator (`SRV` record) problem. See [Domain Controllers as routing peers](/manage/dns/internal-dns-servers#domain-controllers-as-routing-peers).
- **NetBird won't start on a Domain Controller** and the peer shows disconnected. The Windows DNS Server service can claim WireGuard's UDP port 51820 before NetBird does, so the tunnel never comes up. See [WireGuard port conflict on Domain Controllers](/manage/dns/internal-dns-servers#wire-guard-port-conflict-on-domain-controllers).

View File

@@ -100,7 +100,7 @@ You don't need to configure custom nameservers to use NetBird. On Linux, peer do
NetBird configures the operating system to use its DNS resolver:
- **Linux**: Always sets up DNS via `/etc/resolv.conf` or `resolvconf`. Original nameservers preserved as upstream.
- **macOS**: Uses system APIs (does **not** modify `/etc/resolv.conf` directly)
- **macOS**: Uses system APIs. Match-domain nameservers become scoped resolvers (visible in `scutil --dns`, not in `/etc/resolv.conf`); a primary nameserver makes macOS regenerate `/etc/resolv.conf` with NetBird's resolver in it
- **Windows**: Sets the network adapter's DNS server to the local NetBird resolver
- **Android/iOS**: Uses VPN DNS configuration

View File

@@ -98,11 +98,17 @@ For details, see [Manage Network Access](/manage/access-control/manage-network-a
Each peer should have exactly **one** primary nameserver. If you don't configure a primary, peers use their original DNS settings for non-matched queries.
</Note>
A primary nameserver does more than catch unmatched queries. On macOS, a match-domain-only configuration registers a *scoped* resolver through system APIs and leaves the nameservers in `/etc/resolv.conf` untouched. Applications that use the system resolver follow the scoped entry, but tools that read `resolv.conf` directly (`dig`, `host`, and some language runtimes) keep querying your LAN or public nameservers and get `NXDOMAIN` for internal names. Adding a primary group puts NetBird's resolver into `resolv.conf`, so both kinds of tools query the same server. If your users report that browsers resolve internal names but `dig` does not, see [dig and host fail, but browsers and curl work](/manage/dns/troubleshooting#issue-5-dig-and-host-fail-but-browsers-and-curl-work-mac-os).
### Search Domains
Toggle **Mark match domains as search domains** to enable domain suffix searching. When enabled, typing `server` expands to `server.company.internal`. Only applies to match domain nameservers.
### Example: Split-Horizon DNS
<Note>
**Don't convert a match-domain group to primary by emptying its domains.** Removing the match domains also removes the search domains, so short names like `server` stop expanding. Domain-joined Windows machines hide the regression because Active Directory supplies its own suffix, so it surfaces as "short names broke for some users." To add a primary, create a **second** nameserver group with the same servers and leave the existing match-domain group in place.
</Note>
### Split-Horizon DNS
A common setup with two nameservers:
@@ -115,6 +121,27 @@ Result:
- `app.company.internal` → Internal DNS (10.0.0.1)
- `server` → Expanded to `server.company.internal` → Internal DNS
### Route All Queries to Internal DNS
If you are migrating from a VPN that pushed your internal DNS servers for everything (a common OpenVPN setup), the equivalent is the same two groups as in the split-horizon example, but both pointing at your **internal servers**:
<Note>
This setup requires internal DNS servers that resolve public domains too, by recursing or forwarding upstream. Active Directory DNS servers do this by default. If your servers are authoritative-only for the internal zone, making them primary cuts off public resolution; use the split-horizon setup above instead.
</Note>
**Primary (internal)**: Custom DNS with your internal DNS server IPs (e.g., 10.0.0.1, 10.0.0.2). Assign to your target group. Leave match domains empty.
**Match domain (internal)**: Same servers. Add match domain `company.internal`. Enable search domains.
<img src="/docs-static/img/manage/dns/nameservers-route-all-internal.png" alt="Two nameserver groups pointing at the same internal DNS servers: one primary with no match domains, one with match domain company.internal and search domains enabled" className="imagewrapper-big"/>
Result:
- All queries, public and internal, go to your internal DNS
- `server` still expands to `server.company.internal`
- Tools that read `/etc/resolv.conf` directly and tools that use the system resolver query the same server
Two groups look redundant when they hold the same servers, but they do different jobs: the primary receives every query, while the match-domain group provides the search-domain expansion. A VPN "pushing DNS servers" maps to the **primary** group, not to a match-domain group.
### DNS Failover and Overlapping Domains
Configure multiple nameserver IPs within a single nameserver entry for automatic failover. The client tries servers in order.

View File

@@ -42,12 +42,14 @@ dig @10.0.0.53 app.internal.company.com
# Replace 10.0.0.53 with your actual nameserver IP
# 6. Can you resolve match domains through the system resolver?
# Use the tools below, not dig/host/nslookup: those test a DIFFERENT path
# (see Issue 5) and can report NXDOMAIN while the system resolver works.
# Linux:
resolvectl query app.internal.company.com
# macOS:
dscacheutil -q host -a name app.internal.company.com
# Windows/Cross-platform:
nslookup app.internal.company.com
# Windows:
Resolve-DnsName app.internal.company.com
# 7. Confirm public domains still resolve (verifies split DNS is working)
nslookup google.com
@@ -372,7 +374,63 @@ cat /etc/resolv.conf
**Fix**: Limit to 3-4 most important search domains.
### Issue 5: DNS Works on NetBird Network, Fails Outside
### Issue 5: dig and host Fail, but Browsers and curl Work (macOS)
**Symptoms**:
- Browsers, `curl`, and most applications resolve internal names fine
- `dig` and `host` return `NXDOMAIN` for the same names, short and fully qualified
- Typically on macOS with a match-domain nameserver and no primary; hits CLI tooling and IDEs hardest
**Diagnosis**:
macOS resolves names through two separate paths, and they can disagree:
- **The system resolver** (`getaddrinfo`, via mDNSResponder): used by browsers, `curl`, and most applications. It follows *scoped* resolvers, the per-domain entries that `scutil --dns` lists (flagged `Supplemental` in its output).
- **`/etc/resolv.conf` readers**: `dig`, `host`, and some language runtimes read this file directly and query the nameservers in it themselves.
A match-domain nameserver registers a scoped resolver, and the `resolv.conf` format cannot express "use this server, but only for this domain". So `resolv.conf` keeps your LAN or public nameservers, and `dig` asks those, receives an authoritative `NXDOMAIN` for the internal zone, and reports it confidently. Resolution did not fail; it succeeded against the wrong server.
Search domains sharpen the trap: they *do* merge into `resolv.conf`, so `host myserver` expands the short name to `myserver.company.internal` and then still asks the wrong nameserver.
Confirm the split:
```bash
# System resolver path — works:
dscacheutil -q host -a name app.company.internal
# resolv.conf path — NXDOMAIN:
dig app.company.internal
# The scoped resolver dig cannot see:
scutil --dns | grep -B1 -A4 company.internal
# Query NetBird's resolver directly — works, showing the query succeeds
# through NetBird's resolver (replace 100.x.255.254 with the nameserver
# from the scutil --dns resolver block whose domain is your internal zone):
dig @100.x.255.254 app.company.internal
```
If `dscacheutil` and `dig @100.x.255.254` resolve while plain `dig` does not, the client is working as configured: `dig` is just reading `resolv.conf`.
Language runtimes split the same way (verified on macOS), which is how this reaches developers:
| Reads `resolv.conf` (misses scoped resolvers) | Uses the system resolver (works) |
|---|---|
| Go's pure-Go resolver (`GODEBUG=netdns=go`, the default in `CGO_ENABLED=0` builds) | Go's cgo resolver |
| Python `dnspython` | Python `socket.getaddrinfo` |
| Node.js `dns.resolve4()` and friends (c-ares) | Node.js `dns.lookup()` |
Windows has the same two-path split under different names: NetBird steers match domains with an NRPT rule, which `Resolve-DnsName` honors but `nslookup` bypasses (it queries the adapter's nameservers directly). This is why the diagnostics on this page prescribe `dscacheutil` on macOS and `Resolve-DnsName` on Windows.
**Solution**:
Add a **primary** nameserver group (leave Match Domains empty) pointing at the same internal DNS servers, alongside the existing match-domain group. The primary puts NetBird's resolver into `/etc/resolv.conf`, so both paths query the same server. The internal servers must resolve public domains too (by recursing or forwarding upstream, which Active Directory DNS servers do by default); if they are authoritative-only for the internal zone, point the primary group at a resolver that handles public domains instead.
Keep the match-domain group with search domains enabled; do not convert it by emptying its domains, which silently drops the search suffix (see [Search Domains](/manage/dns/internal-dns-servers#search-domains)).
**Verify**: `dig app.company.internal` and `host myserver` both resolve, and `cat /etc/resolv.conf` shows `nameserver 100.x.255.254` plus your search domain.
### Issue 6: DNS Works on NetBird Network, Fails Outside
**Symptoms**:
- DNS works when connected to NetBird
@@ -426,7 +484,7 @@ sudo systemctl disable netbird
curl -sSL https://pkgs.netbird.io/install.sh | sh
```
### Issue 6: DNS Rebinding Protection
### Issue 7: DNS Rebinding Protection
**Symptoms**:
- Router blocks internal DNS queries
@@ -457,7 +515,7 @@ config domain
---
### Issue 7: Active Directory Login or DFS Fails (but a file share by IP works)
### Issue 8: Active Directory Login or DFS Fails (but a file share by IP works)
**Symptoms**:
- Domain login, mapped drives, or DFS paths fail for remote users — yet the file server is reachable by IP
@@ -479,7 +537,7 @@ Running the NetBird client directly **on** a Domain Controller is a separate cas
---
### Issue 8: Windows NRPT rule is written but never takes effect (lingering GPO)
### Issue 9: Windows NRPT rule is written but never takes effect (lingering GPO)
**Symptoms**:
- Match-domain names don't resolve on a Windows client, even though `netbird status -d` shows the nameserver as Available and the client log records the NRPT (Name Resolution Policy Table) rule as written.