You brought up WireGuard, the tunnel passes traffic, and the internal name still does not resolve. The next move is usually to hunt for a DNS setting in the WireGuard configuration, as though a peer could push a resolver the way some other VPNs do. It cannot. The handshake carries keys, interface addresses and allowed IPs, and it has no field for DNS servers or search domains. Resolver policy lives on the client, and that is where the fix belongs.

The tunnel and the resolver are separate contracts

WireGuard is deliberately small. It moves packets for the addresses you list and it authenticates peers; everything above that layer is your design. OpenVPN can push a dhcp-option DNS because its control channel carries configuration, and that difference trips people who move between the two.

So a working tunnel tells you nothing about name resolution. The client can have a perfect route to 10.90.0.1 and still send its queries to a public resolver, which is why the symptom looks like "the VPN broke DNS" when in fact DNS was never pointed at the VPN.

Serve the zone without becoming a recursive resolver

The server side is a small authoritative zone, not a general resolver. In this example the gateway is 10.90.0.1, clients are 10.90.0.0/24, and the application you care about answers on 10.60.0.20.

Merge these settings into the existing options block rather than creating a second one:

options {
    listen-on { 127.0.0.1; 10.90.0.1; };
    listen-on-v6 { none; };
    allow-query { 127.0.0.1; 10.90.0.0/24; };
    allow-transfer { none; };
    recursion no;
};

Then declare the zone and its file:

zone "corp.example" {
    type primary;
    file "/etc/bind/db.corp.example";
};
$TTL 300
@ IN SOA ns.corp.example. hostmaster.corp.example. ( 1 3600 600 604800 300 )
@      IN NS ns.corp.example.
ns     IN A  10.90.0.1
portal IN A  10.60.0.20

Validate before you reload, because a syntax error at reload time takes the whole server down for every zone it serves:

named-checkconf
named-checkzone corp.example /etc/bind/db.corp.example

Open UDP and TCP 53 to the VPN interface only. Publishing a private authority on the public interface invites both queries and enumeration, and allow-transfer should stay closed unless a secondary genuinely needs it.

Point the client at it, per link

Assume NetworkManager owns the connection and systemd-resolved is the resolver. Read the existing policy first, especially any root routing domain, because a ~. entry will outrank what you are about to add.

nmcli connection modify wg-office +ipv4.dns 10.90.0.1
nmcli connection modify wg-office +ipv4.dns-search '~corp.example'

The tilde is the important character. It marks the domain as route-only: names under corp.example go to this link's DNS server, and the domain is not appended as a short-name search suffix. Without it, typing portal starts resolving portal.corp.example, which is convenient for you and surprising for everyone else. Keep the tilde inside the quotes, because an unquoted one is a shell expansion waiting to point at your home directory.

Apply the change through the manager that owns the interface, then reconnect the connection. Do not start a second wg-quick instance for the same interface; two owners will fight over routes and one of them will win at the worst moment.

Verify the path before the name

Query the name from the client and confirm both the answer and the server that produced it:

resolvectl query portal.corp.example

If that returns 10.60.0.20, the DNS contract is done. If it times out, check the route to 10.90.0.1 first, then the firewall rule, then allow-query on the server. Name resolution failures in this setup are almost always reachability or an ACL, not the zone file. Work outward from the client, because the server cannot tell you why a query never arrived.

Remember that the application still needs its own route and its own service permission. A resolved name that points at an unreachable address is a different failure wearing the same clothes.

What is still not automatic

Applications with their own resolver logic ignore the system resolver entirely, and no amount of nmcli changes that. Deployments that validate DNSSEC need an explicit trust anchor design for the private zone; the answer is not a blanket instruction to disable validation. Test with the same client software your users run, not with a tool that happens to use the system resolver. The gap between those two is the whole bug report.

Fleets can automate this client policy through their provisioning system, but the policy remains a separate object with an owner. The BIND 9 reference covers the listener and query controls used above, and it is the right place to confirm behaviour for your installed version.

Next step: add the zone's serial bump and the check commands to your change procedure, so a record edit cannot reach production unchecked.

Winndannde jowitiiɗe heen