How can unbound also query local DNS servers?

Dear community I hope you can help me with a question about setting up unbound to query local dns servers too? It is very remotely related to the switch from Opnsense to Nethsecurity because unbound was the local DNS server of choice on the Opnsense setup. Hence the category chat.

Rspamd setup of my (non ns8) mail-server was working nicely with the unbound-Opnsense setup. Nethsecurity uses Dnsmasq;
However the DNS intensive rspamd works better/faster with unbound. So I setup unbound on my mail instance as the DNS resolver, and it works great.
Except is does not resolve hosts on the lan.. If I forward all DNS request to Nethsecurity it does resolve hosts on the lan, however rspamd starts to fill up logs related to DNS queries.

So back to my question:
How can I set up unbound as a DNS resolver which (besides root DNS servers) also queries local DNS servers without forwarding all DNS request to the local (Nethsecurity) DNS server?

BTW:
With at the time of writing unreleased fix (already inuse by me) for redirection of all DNS queries to nethsecurity’s Dnsmasq; I needed to add a (dns) filter-bypass in Nethsecurity so the unbound resolver on my mail-server behind the Nethsec firewall could work.
cc/@stephdl : I wonder if NS8 adds this filter-bypass if it is behind a nethsec firewall?

1 Like

Basically, this is a routing issue:
I have a scenario with three separate networks connected via a WG tunnel (Site links).
When a client somewhere in one of the three networks needs to resolve a DNS query, a decision must first be made as to which DNS server should handle the resolution. To handle this, I have an AdGuard server in each network acting as the primary DNS server, which specifies a dedicated upstream server for each subdomain

[/sub1.mydomain1.de/]172.17.12.82
[/sub2.mydomain1.de/]172.17.12.82

In the sub1.mydomain1.de and sub2.mydomain1.de networks, there are respective back-referencing entries for the upstream servers

[/sub1.mydomain2.de/]192.168.3.14
[/sub3.mydomain3.de/]172.17.21.62

I can’t say right now how to solve this with Unbound; it’s just something to think about.

1 Like

Thanks, yes this is what I try to accomplish.

Just can’t figure out how this can be configured in unbound, it’s the concept of Local Zones, Forwarding, and Stubs in unbound’s documentation I can’t get my head arround.

…just an idea from Claude, not a final solution:

Forward zones are the direct counterpart to AdGuard’s upstream rules [/domain/]server.

Mapping AdGuard → Unbound

AdGuard entry:

[/sub1.mydomain1.de/]172.17.12.82

Unbound equivalent:

yaml

forward-zone:
    name: "sub1.mydomain1.de."
    forward-addr: 172.17.12.82

Example configuration for the resolver in the sub1.mydomain1.de network

/etc/unbound/unbound.conf.d/forwarding.conf:

yaml

server:
    interface: 0.0.0.0
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow

    # Internal zones: allow private addresses in answers (DNS rebind protection)
    private-domain: "sub1.mydomain1.de"
    private-domain: "sub2.mydomain1.de"
    private-domain: "sub1.mydomain2.de"
    private-domain: "sub3.mydomain3.de"

    # Internal zones without DNSSEC validation (otherwise SERVFAIL with split DNS)
    domain-insecure: "sub1.mydomain1.de"
    domain-insecure: "sub2.mydomain1.de"
    domain-insecure: "sub1.mydomain2.de"
    domain-insecure: "sub3.mydomain3.de"

forward-zone:
    name: "sub1.mydomain2.de."
    forward-addr: 192.168.3.14

forward-zone:
    name: "sub3.mydomain3.de."
    forward-addr: 172.17.21.62

# Everything else: external upstreams
forward-zone:
    name: "."
    forward-addr: 9.9.9.9
    forward-addr: 1.1.1.1

Check and reload:

bash

unbound-checkconf
systemctl reload unbound

Test:

bash

dig @127.0.0.1 host.sub1.mydomain2.de
unbound-control list_forwards

Key differences from AdGuard

Topic AdGuard Unbound
Zone assignment Path prefix, suffix match Zone, longest match (most specific zone wins)
Multiple upstreams Multiple servers per rule possible Multiple forward-addr entries, selection via RTT band
Rebind protection Not active by default private-address/private-domain apply
DNSSEC Optional With the validator module: domain-insecure required
Filtering/web UI Yes No

Pitfalls

  1. Longest match: If sub1.mydomain1.de exists locally as a local-zone/local-data, but mydomain1.de is forwarded, the more specific zone wins. This works like in AdGuard, but a local-zone takes precedence over a forward-zone of the same name.
  2. Avoid loops: If two sites forward to each other for the same zone, a forwarding loop results. For each zone, only one site may answer authoritatively or forward to the peer, not both.
  3. Recursion: Unbound must be allowed to answer queries from the peer resolver. The tunnel subnets of the remote sites belong in access-control.
  4. DNSSEC / rebind: Without domain-insecure and private-domain, internal zones often return SERVFAIL or empty answers.
  5. Source IP: Over WireGuard, the query arrives with the tunnel or LAN IP. If you set outgoing-interface, the address must match your AllowedIPs configuration.

Optional: network-specific answers (views)

If the same name should return different answers depending on the source network, Unbound offers views (in AdGuard this can only be approximated with client-specific settings):

yaml

server:
    access-control-view: 172.17.12.0/24 view_site1

view:
    name: "view_site1"
    local-zone: "app.mydomain1.de." transparent
    local-data: "app.mydomain1.de. IN A 172.17.12.50"

Conclusion

The concept transfers 1:1. The main effort is not the forward zones but domain-insecure/private-domain and cleanly avoiding loops. If you don’t need AdGuard’s filtering, Unbound is even leaner and more deterministic for pure zone forwarding.

2 Likes

Thank you!

I made 2 configuration errors, translated to your example:

I missed the . (dot) at the end of (local) forward-zone (toplevel domain de needs a dot (de.)

forward-zone:
    name: "sub1.mydomain2.de."
    forward-addr: 192.168.3.14

Missed that internal zones need to be configured without DNSSEC validation:

server:
   ....
    # Internal zones without DNSSEC validation (otherwise SERVFAIL with split DNS)
    domain-insecure: "sub1.mydomain2.de"
    domain-insecure: "sub3.mydomain3.de"

I did not adopt this:

# Everything else: external upstreams
forward-zone:
    name: "."
    forward-addr: 9.9.9.9
    forward-addr: 1.1.1.1

To my understanding the whole idea is to use Unboud as a recursive DNS resolver is that it finds the Authoritative DNS server for a domain by itself without forwarding the requested to a public DNS server. This to avoid rate limits.. Then again my knowledge is limited.

However I am sorted out now!

1 Like