Pihole, unbound and no more ad-flag

After updating Pihole to the latest versions of FTL, Web and Core, unbound no longer works as intended.
I don’t have the skills to judge to what extent there is a connection between the update and unbound.

I can only describe what the effects are for me.

After updating my “master” Pihole, access to the Internet no longer worked. I could no longer access any websites.
Switching from 127.0.0.1#5335 to the standard Googel DNS server showed that it must have something to do with unbound.

In the end it was due to an entry in the unbound configuration (dietpi.conf).
Under auth-zone there was an entry module-config: “ validator-iterator”, which now posed a problem. When I deactivated this entry and restarted unbound, everything seemed to work normally again.

Then I tested the resolution with dig dnssec.works @127.0.0.1 -p 5335 and was surprised that the ad-flag was no longer present.

root@pihole:~# dig dnssec.works @127.0.0.1 -p 5335

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> dnssec.works @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35468
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dnssec.works.                  IN      A

;; ANSWER SECTION:
dnssec.works.           30      IN      A       46.23.92.212

;; Query time: 0 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1) (UDP)
;; WHEN: Sat May 31 18:53:13 CEST 2025
;; MSG SIZE  rcvd: 57

So the output would be correct:

; <<>> DiG 9.16.44-Debian <<>> dnssec.works @127.0.0.1 -p 5335
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6033
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dnssec.works.			IN	A

;; ANSWER SECTION:
dnssec.works.		21418	IN	A	5.45.107.88

;; Query time: 0 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1)
;; WHEN: Tue Oct 10 12:43:44 CEST 2023
;; MSG SIZE  rcvd: 57

Now I checked my Backup Pihole systems and lo and behold, they all showed the same behavior.

But I have to say that the dietpi.conf for the Backup Pihole is slightly different, if not original, i.e. not edited after installation.

Unbound is available in version 1.7.1.

I did a little research and read that there are currently minor problems with 1.7.1. Can anyone confirm this?

This was a chance discovery for me after updating Pihole, only because the master Pihole no longer worked correctly due to the aforementioned entry.

I am not at all familiar with the matter, perhaps someone has the opportunity to check whether this is really the case.

Maybe similar to this one? dietpi-software: Unbound: disable ECS by default · MichaIng/DietPi@778859f · GitHub

I think that’s going in the right direction, but I don’t understand enough of it.

I would like to share my dietpi.config here, and ask you to take a look at it.

As I said, the configuration worked fine until recently.

# https://nlnetlabs.nl/documentation/unbound/unbound.conf/
server:
        # Do not daemonize, to allow proper systemd service control and status estimation.
        do-daemonize: no

        # A single thread is pretty sufficient for home or small office instances.
        num-threads: 4

        # Anzahl der Slabs im RRset-Cache. Slabs reduzieren die Sperrkonflikte zwischen den
        # Threads. Muss auf eine Potenz von 2 in der Nähe von num-threads gesetzt werden.
        msg-cache-slabs: 8
        rrset-cache-slabs: 8
        infra-cache-slabs: 8
        key-cache-slabs: 8

        # Logging: For the sake of privacy and performance, keep logging at a minimum!
        # - Verbosity 2 and up practically contains query and reply logs.
        log-time-ascii: yes
        verbosity: 0
        # Minimize logs
        # Do not print one line per query to the log
        log-queries: no
        # Do not print one line per reply to the log
        log-replies: no
        # Do not print log lines that say why queries return SERVFAIL to clients
        log-servfail: no
        # Do not print log lines to inform about local zone actions
        log-local-actions: no
        # - If required, uncomment to log to a file, else logs are available via "journalctl -u unbound".
        logfile: "/var/log/unbound.log"

        # Set interface to "0.0.0.0" to make Unbound listen on all network interfaces.
        # Set it to "127.0.0.1" to listen on requests from the same machine only, useful in combination with Pi-hole.
        interface: 127.0.0.1
        # Default DNS port is "53". When used with Pi-hole, set this to e.g. "5335", since "5353" is used by mDNS already.
        port: 5335

        # Control IP ranges which should be able to use this Unbound instance.
        # The DietPi defaults permit access from official local network IP ranges only, hence requests from www are denied.
        access-control: 0.0.0.0/0 refuse
        access-control: 10.0.0.0/8 allow
        access-control: 127.0.0.1/8 allow
        access-control: 172.16.0.0/12 allow
        access-control: 192.168.0.0/16 allow
        access-control: ::/0 refuse
        access-control: ::1/128 allow
        access-control: fd00::/8 allow
        access-control: fe80::/10 allow

       # Private IP ranges, which shall never be returned or forwarded as public DNS response.
        # NB: 127.0.0.1/8 is sometimes used by adblock lists, hence DietPi by default allows those as response.
        private-address: 10.0.0.0/8
        private-address: 172.16.0.0/12
        private-address: 192.168.0.0/16
        private-address: 169.254.0.0/16
        private-address: fd00::/8
        private-address: fe80::/10

        # Define protocols for connections to and from Unbound.
        # NB: Disabling IPv6 does not disable IPv6 IP resolving, which depends on the clients request.
        do-udp: yes
        do-tcp: yes
        do-ip4: yes
        do-ip6: no

        # DNS root server information file. Updated monthly via cron job: /etc/cron.monthly/dietpi-unbound
        root-hints: "/var/lib/unbound/root.hints"
        tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"

        # Maximum number of queries per second
        ratelimit: 1000

        # Defend against and print warning when reaching unwanted reply limit.
        unwanted-reply-threshold: 100000

        # Set EDNS reassembly buffer size to match new upstream default, as of DNS Flag Day 2020 recommendation.
        edns-buffer-size: 1232

        # Increase incoming and outgoing query buffer size to cover traffic peaks.
        so-rcvbuf: 4m
        so-sndbuf: 4m

        # Faster UDP with multithreading (only on Linux).
        so-reuseport: yes

        # Hardening
        harden-glue: yes
        harden-dnssec-stripped: yes
        harden-algo-downgrade: yes
        harden-large-queries: yes
        harden-short-bufsize: yes

        # Privacy
        use-caps-for-id: yes # Spoof protection by randomising capitalisation
        rrset-roundrobin: yes
        qname-minimisation: yes
        minimal-responses: yes
        hide-identity: yes
        identity: "Server" # Purposefully a dummy identity name
        hide-version: yes
        
        # Caching
        cache-min-ttl: 3600
        cache-max-ttl: 86400
        serve-expired: yes
        serve-expired-ttl: 86400
        serve-expired-client-timeout: 1800
        neg-cache-size: 4M
        prefetch: yes
        prefetch-key: yes
        aggressive-nsec: yes
        msg-cache-size: 50m
        rrset-cache-size: 100m

        # mehr ausgehende Verbindungen
        # hängt von der Anzahl der Kerne ab: 1024/Kerne - 50
        outgoing-range: 206

        # Deny queries of type ANY with an empty response.
        # Works only on version 1.8 and above
        deny-any: yes

remote-control:
        control-enable: yes

# get data for all TLDs by IXFR (or AXFR) from root servers
# these are the only servers that answer an IXFR query
auth-zone:
        name: "."
        primary: 199.9.14.201       # b.root-servers.net
        primary: 192.33.4.12        # c.root-servers.net
        primary: 192.112.36.4       # g.root-servers.net
        primary: 2001:500:200::b    # b.root-servers.net
        primary: 2001:500:2::c      # c.root-servers.net
        primary: 2001:500:12::d0d   # g.root-servers.net
        fallback-enabled: yes
        for-downstream: no
        for-upstream: yes
        zonefile: /var/lib/unbound/root.zone
        module-config: "validator iterator"

Did you have done any changes to to unbound.conf?

I would say no!

# Unbound configuration file for Debian.
#
# See the unbound.conf(5) man page.
#
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
# reference config file.
#
# The following line includes additional configuration files from the
# /etc/unbound/unbound.conf.d directory.
include-toplevel: "/etc/unbound/unbound.conf.d/*.conf"

But maybe the ad-flag hasn’t been working for a while, because I don’t test it all the time. However, the ad-flag is important because, as I understand it, it ensures that unbound considers the response to be authentic and/or that the DNSSEC validation is OK.

you added own configuration to dietpi.conf correct? because I see some German comments in your file.

Due to your manuell changes on our config, we placed module-config: "validator iterator" on an incorrect location during the last update.

Now it is located within a block called auth-zone: at the end of the file. But it would need to be moved into the server block above.

This is how it should looks like

	# Caching
	cache-min-ttl: 300
	cache-max-ttl: 86400
	serve-expired: yes
	neg-cache-size: 4M
	prefetch: yes
	prefetch-key: yes
	msg-cache-size: 50m
	rrset-cache-size: 100m
	module-config: "validator iterator"

Ok, thanks, I have made the change.
Unbound now works again with “my” dietpi.conf file.

But it did not solve the problem with the missing ad-flag. It still exists, just like with other pihole/unbound installations under dietpi.
Could it have something to do with unbound 1.7.1?
I know that it still worked during a test this year.

We adjusted the way this config file patch is applied, so it works now also with custom config sections at the bottom.

OP, despite not having the same issue, out of curiosity I tried the same test, used dig for the same domain you used, and got the same results;

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> dnssec.works
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42654
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;dnssec.works.                  IN      A

;; ANSWER SECTION:
dnssec.works.           2833    IN      A       46.23.92.212

;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Sun Jun 01 03:44:15 CEST 2025
;; MSG SIZE  rcvd: 57

Note the distinctly missing ad-flag.
HOWEVER;

when I resort to dig sigok.ippacket.stream to use the test domain I usually use… the ad-flag is where it should be;

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> sigok.ippacket.stream
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4494
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;sigok.ippacket.stream.         IN      A

;; ANSWER SECTION:
sigok.ippacket.stream.  3600    IN      CNAME   sigok.rsa2048-sha256.ippacket.stream.
sigok.rsa2048-sha256.ippacket.stream. 230 IN A  195.201.14.36

;; Query time: 79 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Sun Jun 01 03:40:23 CEST 2025
;; MSG SIZE  rcvd: 101

Perhaps, and this is just speculation obviously, because I am an absolute novice on this level of understanding network topology, but perhaps the issue of the missing ad-flag does not lie with unbound, but with the domain used to test against?

Might be possible

For me, it is exactly as you describe.
But it’s strange why it worked for many years with exactly the same command without any problems.
Then it’s probably due to the servers and not the pihole/unbound construct.

The reason is pretty clear and described on the GitHub link shared above.

On the last release we added a new value which would need to go into server section of configuration file. However we did not expect users to add own configuration into our file. Now the added value was in an incorrect section leading Unbound to fail.

1 Like