Translation(s): English - French


Introduction

BIND (Berkeley Internet Name Domain) is DNS (Domain Name System) server software, for running a DNS server. BIND 9 refers to the 9.x version series, which was first released in 2000, and which Debian long has and continues to well package and make available. BIND 9 is very capable DNS server software, e.g. not only capable of serving as caching DNS server, recursively resolving DNS queries, serving as primary and/or secondary for DNS zones, able to do DNSSEC, DDNS, well supports running under chroot, etc., it's also highly configurable and has many advanced capabilities and features. Note that BIND 9 isn't the only DNS server software that Debian packages, but BIND 9 is quite popular and has a long history, and has many features and capabilities. For other possible DNS server software, one may want to run, e.g.:
(see also Prompt convention further below)
$ aptitude search '?and(?tag(protocol::dns),?tag(interface::daemon),?tag(network::server))'
and review that output, and possibly also consider data on https://popcon.debian.org/.

Note general intent/plans, at least thus far, on/for this wiki page, is to at least reasonably well cover BIND 9 on Debian - enough to generally get one reasonably well started and cover some relatively common starting configuration scenarios. It's not intended to be a fully comprehensive guide to DNS nor DNS administration, nor even DNS administration on BIND 9. However, the mentioned bind9-doc package does excellently cover DNS administration on BIND 9 in many regards, notably including BIND 9 Administrator Reference Manual(Debian 10-6 (Debian release/version number <--> code name mapping)), so that is at least a quite good start on that. And that doesn't mean we won't also include at least some tips, pointers, suggestions, etc. as relevant and appropriate, and may also include some fair bit of information, notably links to, other relevant information and resources.

Definitions, etc.

Howto

Introductory Notes

We presume a "pure" Debian host system with a fresh "clean" installation of the Debian provided bind9 package.
If/where that's not the case, additional steps and/or variations in steps shown may be needed.

Shall mostly hopefully attempt to keep this current for stable (Debian 13, at the time of this writing), and may attempt to include additional relevant notes for other (most notably older, possibly also newer) as relevant - for at least as far back as we (ought?) maintain this information.

To some fair degree here in our examples, mostly for security, we may be leaning significantly more towards showing/using least privilege principle, most notably for files and permissions/ownerships thereof. That is most important/critical here for any and all private keys. If one wishes to be more permissive, for items other than private keys, one may wish to allow read and related access in the more general case. In all cases, however, most all the files/data referenced here should be protected from general write access. That generally means group bind will need read access, and in some areas/locations/files, write access. Others should not have write access, and must not have read access to private keys.

Our examples may also use SGID on directory, so by default items created in directory will have group ownership matching that of the directory. E.g. this may be most useful in directories where we want group ownership of items thereunder to be bind, even if root creates the files. But SGID on directory, however, doesn't ensure files will be created with the appropriate permissions, but it does generally get us at least one step closer in such locations, to getting files to the desired ownerships/permissions for those locations, and can also help prevent some accidents/issues (e.g. like root creating file and things not working because neither bind user nor group can read). So, in many cases, umask 027 is most protective and sufficient, e.g. with root owning (so bind can't even alter), bind group having read, and others having no access. Generally most permissive that would be appropriate would be 022, however as noted, private keys and such should never be other/world readable. In some cases we may use even much more stringent permissions, e.g. a mount point under chroot, where we want exactly zero access (accident prevention, security, etc.) if/when the mount isn't present on that mount point.

The bash shell - for the most part we typically give shell examples that are minimal POSIX and don't require bash, but there are some exceptions, where the advantages of bash are substantial enough that we use bash specific syntax in some cases. Most notably we may make use of bash's Process Substitution (in the form of <(list) or >(list)) and also Brace expansion, e.g.: where a{d,c,b}e expands into ade ace abe.


Prompt convention:
We generally show
root / superuser / UID 0 prompt as a leading "# "
notably where root (or possibly other, e.g. bind) privileged access is required,
(but don't confuse that with other contexts where # is used to introduce comments), and
we show the prompt of other IDs as leading "$ "
notably where in general no privileged access is required, and typically any logged in user can run the command(s).


Debian release versions - numbers, ranges, comparators
Throughout, we generally use version numbers, rather than code names, notably so we can easily, more concisely, and more intuitively, refer to a range, e.g. 13-6, as opposed to, e.g. trixie-bookworm-bullseye-buster-stretch-jessie-wheezy-squeeze. We'll also use comparators, like <, <=, etc. and those should then generally apply in the strictly mathematical sense. So, e.g. just as 11 is not less than 11, Debian <11 would not apply to any 11 or 11.x, but only 10 (and 10.x) and lower. Likewise Debian >=12 would apply to all 12 and 12.x and up. One can also think logically that effectively experimental > sid/unstable > testing > stable > oldstable > oldoldstable and at any given point in time, the last three of those have an equal (major) version number (e.g. at the time of this writing, stable=13, oldstable=12, oldoldstable=11). More generally when we reference a Debian version, e.g. 9, that generally applies to that major release and all major point releases thereof and everything (available) within that release itself, e.g. 9 and all 9.x versions.
See also: Debian release/version number <--> code name mapping Glossary

Deprecated terminology
In Debian <11, deprecated master(s)/slave(s) terminology is used. More recent and current uses primary/primaries and secondary/secondaries. Some backwards compatibility (e.g. aliases) may still exist, but those may also go away in future. The older may also require the older deprecated terminology/names, e.g. in configuration options. Also, even in some of the newer and still possibly current, not all such name/term references have necessarily been updated.

Debian <8: use the command apt-get rather than apt.

enable-start-reload-stop-disable
We presume the default init system, and in the various subsections, we may only explicitly show enable-start-reload-stop-disable for the current stable. For other releases, make the substitutions as relevant. The same stop/reload method can be used on all init systems and covered releases:
stop:
# rndc stop
reload:
# rndc reload
These are presuming the default init system and for ordinary normal start:
Debian >=11:
# systemctl enable named.service
# systemctl start named.service
# systemctl disable named.service
11> Debian >=8:
# systemctl enable bind9.service
# systemctl start bind9.service
# systemctl disable bind9.service
8> Debian >=7:
# update-rc.d bind9 enable
# /etc/init.d/bind9 start
# update-rc.d bind9 disable
Debian <7:
# update-rc.d named enable
# /etc/init.d/named start
# update-rc.d named disable

BIND 9 Installation

{i} This (BIND 9 Installation) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

If you've not yet installed Debian's BIND9 package, then do so, e.g.:
# apt install bind9
Debian 9 by default enables but may not start the service, so start the service on Debian 9 if it's not already started, e.g.:
# systemctl start bind9.service

And congratulations! Once that's completed, you will generally have, by default, an installed and configured caching-mostly BIND 9 DNS server, and by default it will also be started, and will (re)start upon (re)boot.


{i} If we want ensure we have disabled named from disclosing its version by query,
in file:
/etc/bind/named.conf.options
we add/have within the options section:
        include "/etc/bind/named.conf.options.local";
(that may also be well advised, notably to keep our local options as separate as feasible from package maintainer defaults, to ease updating/merging configurations with major version upgrades)
and then within the file:
/etc/bind/named.conf.options.local
have/add:
        version none;
One may also want to do likewise for
hostname and
server-id, though server-id defaults to none.
See also: the documentation in the bind9-doc package (Debian 10-6)
Then reload the configuration:
# rndc reload
That then prevents disclosing version for such query
and likewise for:
CH authors.bind. TXT
We can check, e.g.:

$ dig @::1 +norecurse +noall +answer +comments CH version.bind. TXT | grep -e '^;.* ANSWER: ' -e '^[^;]'
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
version.bind.           0       CH      TXT     "9.20.11-4-Debian"
$ dig @::1 +norecurse +noall +answer +comments CH version.bind. TXT | grep -e '^;.* ANSWER: ' -e '^[^;]'
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
$ 

We can see in our examples above. The first discloses version by answering with that record. In the second, we can see that answers that it has no such records.


Suggested, also install bind9-dnsutils and bind9-doc, e.g.:
# apt install bind9-dnsutils bind9-doc
(Debian <11 use package dnsutils instead of bind9-dnsutils)
Most notably,
programs such as dig(1) which is highly useful in checking and troubleshooting DNS is contained in the bind9-dnsutils package, and
bind9-doc contains excellent highly detailed BIND 9 documentation and is specific to the corresponding Debian bind9 package version (that documentation can then be found under /usr/share/doc/bind9-doc/).
{i} Note that henceforward in this Howto section, we'll generally presume one has access to these utilities and this documentation.

With dig(1) available, as noted above, we can test to confirm our DNS server is operational.


{i} For quite old no longer supported Debian, e.g.:
Debian <8: All provided root keys are expired, so will generally need to update those. One can obtain current from:
https://data.iana.org/root-anchors/
and write the data in the proper format and add/update in:
/etc/bind/bind.keys
Then reload the configuration:
# rndc reload


{i} For older Debian, and probably most notably for
Debian <oldstable and oldstable when no longer on main support,
bind9 (or dependency) may no longer provide current data for the root (.) zone needed for proper/optimal functioning.
For Debian >=10 this data is found in file:
/usr/share/dns/root.hints
For Debian <10 this data is found in file:
/etc/bind/db.root
Current data may be found here:
https://www.internic.net/domain/named.root
One may use the above to update, but note however per Debian policy and FHS, files under /usr generally should be managed by the installed packages, and not locally modified, so, e.g., in the case of
/usr/share/dns/root.hints
don't modify that file, but rather, modify the bind9 configuration file (e.g. under /etc/bind/) which uses that
/usr/share/dns/root.hints
file, and instead reconfigure that referring file to use a locally provided configuration file, e.g.
/etc/bind/root.hints
and place the updated data in that file.
In the case of, e.g.:
/etc/bind/db.root
that file may be directly modified.
If one wants to compare, looking for potential differences of significance, one may do, e.g.:
$ (f=; for f_ in /usr/share/dns/root.hints /etc/bind/db.root; do [ -f "$f_" ] && { f="$f_"; unset f_; break; }; done; if [ -n "$f" ]; then if type curl >>/dev/null 2>&1; then curl -s 'https://www.internic.net/domain/named.root' | diff -bi "$f" -; else echo 'curl command not found' 1>&1; ! :; fi; else echo 'failed to find file' 1>&2; ! :; fi)
After making such configuration changes, reload the configuration:
# rndc reload


E.g. by default, it will be listening on all local addresses, so:

$ dig @::1 +short +norecurse NS . | sort
a.root-servers.net.
b.root-servers.net.
c.root-servers.net.
d.root-servers.net.
e.root-servers.net.
f.root-servers.net.
g.root-servers.net.
h.root-servers.net.
i.root-servers.net.
j.root-servers.net.
k.root-servers.net.
l.root-servers.net.
m.root-servers.net.
$ dig @::1 +noall +answer wiki.debian.org. A wiki.debian.org. AAAA | sort -u
wiki.debian.org.        3600    IN      CNAME   wilder.debian.org.
wilder.debian.org.      600     IN      A       209.87.16.78
wilder.debian.org.      600     IN      AAAA    2607:f8f0:614:1::1274:78
$

And we can see that our BIND 9 DNS server is properly resolving queries, both ones that are entirely local, and also handling answering recursive queries for us.


{i} Tip: At this point, one may wish to reconfigure one's resolver(s) and the like, most notably so they'll start using this (or these) caching BIND 9 servers(s) as one may now have operational. Of course recommended to be sure to reasonably test them first, e.g. as at least partially covered below, and also likewise test one's resolver(s) before and after making any configuration changes to them. Note also, not intending to cover resolver(s) here, as that also includes scope far beyond that of BIND 9, though Debian does also offer resolver and related package(s) from same upstream (ISC) authors as BIND 9, and are relatively closely related, they are also, however, quite independent, and there are many ways to implement/configure resolver(s), including on Debian and including many ways entirely independent of Debian's packaging of the ISC resolver related package(s).


{i} At this point, by default, locally root, bind, and members of group bind may at least partly control the named server via
rndc(8)
If you wish to change that, you may reconfigure accordingly.
For further information, see the documentation under:
/usr/share/doc/bind9-doc/


{i} It is generally intended that the additional (sub)sections that follow within this Howto section be relatively independent. Most notably so that one can implement any and/or all of them, from none of them, through all of them, or any combination thereof, and all should generally work. Note, however, some may have additional prerequisites, e.g. building upon other subsection(s) to further an example, or may in some cases conflict (same zone can't be both primary and secondary simultaneously on same BIND 9 server).


primary

Implementing this primary subsection is optional.

{i} This (primary) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

If we've completed BIND 9 Installation and we want our BIND 9 server to be primary for one or more zones/domains, we can proceed essentially as shown. In our example case here, setting that up for example domain: example.com


{i} One may wish to store static primary zone files under a separate directory, for, e.g. better organization and potentially better security. If so, would suggest
/etc/bind/primary
and set up as follows:
# (umask 027 && mkdir /etc/bind/primary && chgrp bind /etc/bind/primary && chmod g+s /etc/bind/primary)
And then use that directory for those files. In our examples here, we'll show using the default, but if one uses a different directory, substitute accordingly. In any case, should be somewhere under /etc/bind/ to play nicely with Debian's default configurations and Debian policy and FHS, etc. But see also DNSSEC and chroot, as those adds additional constraints that we must also simultaneously satisfy.


Serial number (& scheme) for zone.
For the serial, we are showing and using unixtime as shown in serial-update-method(Debian 11 10-8 7-6 (dnssec-signzone)) which would be number of seconds since the Unix epoch.
(And presumably per POSIX, not counting any leap second adjustments. And as for Year 2038 problem, since serial is inherently 32 bits, presumably ISC BIND would just update definition of unixtime in that context (and many Unix/Linux etc. systems have already switched from 32-bit to 64-bit time) to be, instead of the (POSIX) seconds since Unix epoch, just the lower 32 bits of same.)
This is as opposed to the RFC 1912 Common DNS Operational and Configuration Errors (SOA records) recommended format:
YYYYMMDDnn
Using unixtime has significant advantages. It's unambiguous to the second, as opposed to entire day or bit more when we consider timezones, allows for up to one change per second (and that single change can be for any and/or all data in the zone) rather than only one hundred per day, without breaking format, it generally works much better with DDNS and/or DNSSEC with in-line signing if we do or may ever implement either or both of those.

Note also one can jump directly from unixtime to YYYYMMDDnn scheme/convention/configuration if one (later) wishes to, but not the other way around, most notably due to impact to secondary(/ies). One also can't arbitrarily jump serial numbers around - that can cause significant to major problems. But if we're starting new, we're relatively free to pick - so long as we do a valid number within range (both unixtime, and YYYYMMDDnn will be within range).
See also:
RFC1912 Common DNS Operational and Configuration Errors (SOA records)
RFC 1982 Serial Number Arithmetic
The GNU date(1) utility can conveniently provide unixtime format:

$ date +%s
1754061178
$

And likewise convert back if one wants to see the more human time, e.g.:

$ date -d @1754061178
Fri Aug  1 15:12:58 GMT 2025
$ date --iso-8601=seconds -d @1754061178
2025-08-01T15:12:58+00:00
$

One can also use GNU date to get the YYYYMMDD portion of YYYYMMDDnn format:

$ date +'%Y%m%d'
20250801
$


Now create primary zone file, we show example.com - use your actual domain, and we use unixtime serial format - use your actual data/scheme. Likewise for the AAAA record, we show ::1, for the BIND9 DNS nameserver to be useful and functional beyond the local host, you'll need to use the applicable IP address(es) for the AAAA and/or A record(s) corresponding to the NS record. Also, hostmaster.example.com. should be or be replaced with valid email address - replacing @ with ., and if there are any . characters before that first @ in the email address, replace those with: \. And end with: . E.g.:
firstname.lastname@example.com
would become:
firstname\.lastname.example.com.
Generally recommended to use appropriate alias/list that will always reach an appropriate DNS administrative contact. Per the RFCs: hostmaster@domain:
"strongly recommended that the well known mailbox name HOSTMASTER always be used <HOSTMASTER@domain>"2142,
"responsible party for the zone"1034, 1033, 1035

We create in our file:
/etc/bind/example.com

$TTL    3600
@       IN      SOA     (
                        ns1.example.com.        ; MNAME
                        hostmaster.example.com. ; RNAME
                        1756451938              ; SERIAL
                        2H                      ; REFRESH
                        1H                      ; RETRY
                        2W                      ; EXPIRY
                        1H                      ; MINIMUM Negative Cache TTL
                        )
        IN      NS      ns1.example.com.
ns1     IN      AAAA    ::1

File permissions and ownership should be 640 root:bind

# ls -ld /etc/bind/example.com
-rw-r----- 1 root bind 551 Aug 29 07:19 /etc/bind/example.com
#

so set accordingly if not already the case, e.g.:
# (d=example.com && cd /etc/bind && chown root:bind "$d" && chmod u=rw,g=r,o= "$d")

And in file:
/etc/bind/named.conf.local
add the following section:

zone "example.com" {
        type primary;
        file "/etc/bind/example.com";
};

Debian <11: use master instead of primary


Note that named has a default location for file, so relative or absolute path can be used for file.
See: directory (Debian 11 10-6)


/!\ {i} allow-transfer(Debian 11 10-6) Note one may be well advised to and/or need to include allow-transfer statement(s) in one's BIND 9 configuration.
Debian >=13 the Debian default is to deny all client IPs to receive zone transfers.
Debian <13 the Debian default is to allow all client IPs to receive zone transfers.
Best practices typically restrict allow-transfer to IP(s) that have legitimate need/reason for such, e.g. to reduce or prevent certain types of potential abuse and/or exfiltration of DNS data, but that doesn't necessarily apply to all environments, e.g. some may have no need/reason to restrict such and may even very much want and intend all IPs to be able to access such.
Note that secondary DNS server(s) for the zone generally require such access, or at least must have some means of getting the zone data, including updates and typically also ability to receive the entire set of zone data. DNS servers that are secondary for the zone may receive such data from primary DNS server(s) for the zone and/or other secondary DNS server(s), as per the relevant configurations of the DNS server(s).
If allow-transfer statements are used, it is suggested that they include localhost notably as a local aid in testing/troubleshooting on/from the DNS server host itself. We may generally presume at least such access is present in our examples.
Context(s) - allow-transfer can be used in different contexts, e.g.
We can set it in options in the BIND 9 DNS server configuration, which then changes the default for that server. For example, let's say in file:
/etc/bind/named.conf.options
we add/have within the options section:
        include "/etc/bind/named.conf.options.local";
(that may also be well advised, notably to keep our local options as separate as feasible from package maintainer defaults, to ease updating/merging configurations with major version upgrades)
and then within that file:
/etc/bind/named.conf.options.local
have/add:
        allow-transfer { localhost; 2001:db8::/120; };
that then changes the default for that DNS server, and allows, by default, only those three IP(s)/netprefix(es) to retrieve the zone (and, e.g. 2001:db8::/120 could cover secondary DNS server(s) and/or clients authorized to retrieve the zone data).
We can add such to zone configuration block, whether it's primary or secondary, and that then overrides and uses that specific setting for that zone. E.g. if we placed:
        allow-transfer { localhost; 2001:db8::/120; };
within our specified zone configuration, that would override and set that specifically for that zone, regardless of allow-transfer settings (or default) elsewhere in our configuration.


If the service is already running, reload the service:
# rndc reload
Otherwise, start the service, e.g.:
# systemctl start named.service


Check whether querying the zone works as expected, and that we're answering authoritatively for the domain, e.g.:

$ dig @::1 +norecurse +noall +answer +comments +multiline example.com. SOA
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 502
;; flags: qr aa ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: db31e2c2df5d0bd10100000068b59fdd4b43b8122c720eca (good)
;; ANSWER SECTION:
example.com.            3600 IN SOA ns1.example.com. hostmaster.example.com. (
                                1756451938 ; serial
                                7200       ; refresh (2 hours)
                                3600       ; retry (1 hour)
                                1209600    ; expire (2 weeks)
                                3600       ; minimum (1 hour)
                                )

$

Note that not only were we able to get our requested data for the domain, without need for the query to be recursive (+norecurse - we requested recursion not be used, so that means we got it from this server itself, without it needing to query any other servers to get our information), but most notably also, see in the comments, the flags: notably aa included in the flags, that means that with the response we got, the server is telling us that it's given us an athoritative answer - that it is the/a final authority on that data, rather than, e.g. it being merely cached from some other server. Even if the server is secondary, rather than primary for the zone, that athoritative answer let's us know it's responding based upon data from full non-expired local non-volatile stored copy of of the zone data, regardless of how it got that full copy of the zone data.


secondary

Implementing this secondary subsection is optional.

{i} This (secondary) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

If we've completed BIND 9 Installation and we want our BIND 9 server to be secondary for one or more zones/domains, we can proceed essentially as shown. In our example case here, setting that up for example domain: example.com
(Note that same domain can't be both primary and secondary on same DNS server (at least within same view), so if one already used example.com in our primary example above, use a different domain here, or change (to a different domain) or undo the domain specific bits that were done from our primary example above.)

And in file:
/etc/bind/named.conf.local
add the following section:

zone "example.com" {
        type secondary;
        file "example.com";
        primaries {
                2001:db8::101;
        };
};

Debian <11: use slave instead of secondary, and masters instead of primaries

See: directory regarding location for file if relative, rather than absolute location, is given.
primaries gives one or more IPs of our primary(/ies) relative to this secondary for that zone.
See: primaries(Debian 11 (masters): 10 9 8 7 6)
The primaries need be configured (or defaulted to) allowing the secondary(/ies) to retrieve the zone data, and should also allow them to query primaries regarding the zone, notably SOA record, so the secondary(/ies) can determine whether or not they have current copy of zone data, and should be able to retrieve updates / current data regarding the zone file data, as needed. For primary(/ies) that are BIND 9 servers, see: allow-transfer

Reload the configuration:
# rndc reload
or start the service if it's not running, e.g.:
# systemctl start named.service

Check that we're authoritative for the zone.

DDNS

Implementing DDNS (Dynamic DNS) is optional.

{i} This (DDNS) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

So, if one wants to implement DDNS, we'll show that here, using example domain: example.com


Relocate zone primary file to /var/lib/bind/ directory:

First, let's relocate our zone primary file from the /etc/bind/ directory to the /var/lib/bind/ directory.

Presuming here one has static primary file, e.g. as our example in primary, and not already doing any type of DDNS for the zone, nor DNSSEC inline-signing nor in any other way that would have the zone not be static.
If it's not static, be sure to take the relevant additional steps, e.g. sync and optionally -clean the zone, and freeze it, or stop named, and copy all relevant files and set their ownerships/permissions appropriately, and after updating configuration for the changed location, thaw the zone or start the named service again if it's not running.

Copy our existing example.com zone file to the new location (because non-static) and appropriately alter ownerships/permissions for bind to manage it:
# (d=example.com && cd /var/lib/bind && cp -p /etc/bind/"$d" "$d" && chown root:bind "$d" && chmod ug=rw,o= "$d")

In the file:
/etc/bind/named.conf.local
edit to update our configuration for example.com, most notably so the file line in our zone section for example.com is changed to look like:
        file "/var/lib/bind/example.com";
Reload the configuration:
# rndc reload
or start the service if it's not running, e.g.:
# systemctl start named.service
If one froze the zone prior to copying its file(s), this would generally be the time to thaw it. And then generally recommended to check that we're authoritative for the zone.


In the file:
/etc/bind/named.conf.local
edit to update our configuration for example.com so it looks like:

zone "example.com" {
        type primary;
        file "/var/lib/bind/example.com";
        serial-update-method unixtime;
        update-policy local;
};

Debian <11: use master instead of primary
Debian <8: don't include serial-update-method configuration line (the serial is simply incremented)
See: serial and also serial-update-method(Debian 11 10-8) to ensure unixtime is what you want for serial-update-method (the default is increment), and that it's compatible with existing serial you have on the zone (presuming you're reconfiguring an existing zone to use DDNS).

Presuming here that you have existing primary zone file for example.com, and that it's static, so one's not already using DDNS for that zone, and likewise not using in-line signing with DNSSEC for that zone. If one is, will need to freeze and sync the zone as appropriate (and thaw after), and/or, as relevant, copy not only primary example.com zone file, but also signed version and relevant state file(s), or stop and restart named.

Reload the configuration:
# rndc reload
or start the service if it's not running, e.g.:
# systemctl start named.service
If one froze the zone prior to copying its file(s), this would generally be the time to thaw it.

Check that we're authoritative for the zone.

Presuming the zone is looking good at this point, we can get rid of the older no longer needed copy of our primary example.com zone file from its former location:
# rm /etc/bind/example.com

Then check that we can alter DNS data for the zone via DDNS.
We'll check that a domain doesn't exist (NXDOMAIN), add a record for it, check that it's there, then remove it, and then check that it's again not present:

$ dig @::1 +norecurse ddns-tmp-test.example.com. TXT | grep -F -e NXDOMAIN
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 58566
$
# printf 'update add ddns-tmp-test.example.com. 30 IN TXT "test - ignore"\nsend\n' | nsupdate -l
#
$ dig @::1 +norecurse +noall +answer ddns-tmp-test.example.com. TXT
ddns-tmp-test.example.com. 30   IN      TXT     "test - ignore"
$
# printf 'update delete ddns-tmp-test.example.com.\nsend\n' | nsupdate -l
#
$ dig @::1 +norecurse ddns-tmp-test.example.com. TXT | grep -F -e NXDOMAIN
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 45966
$

DNSSEC

Implementing DNSSEC is optional.

{i} This (DNSSEC) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

{i} Note however, these days, most resolvers are DNSSEC aware, and will properly use and honor DNSSEC if it's present. DNSSEC is also highly backwards compatible, so, for the most part, no major downsides to implementing DNSSEC for DNS server primary zone(s), and secondaries and caches generally follow right along, with no explicit action needed on those, and DNSSEC well helps secure one of DNS's most significant and otherwise inherent security issues. Note also, as with DNS in general, proper care needs be taken with the administration thereof, and in the case of DNSSEC, there are a few additional things to watch out for, but with reasonable care and attention, that should generally be a non-issue.

Approach used here. We leverage the available DNSSEC signing automation of BIND 9, as it relieves the administrator of most of the hassle, hazards, and pitfalls of manually maintaining DNSSEC and associated Resource Records (RRs), at least once the initial configuration has been completed.

So, if one wants to implement DNSSEC, we'll show that here, using example domain: example.com


We want our zone primary file in /var/lib/bind/ directory, and with appropriate ownerships/permissions, and likewise any related (e.g. journal) files as appropriate. If one hasn't already done that, as was done in DDNS subsection, do that now per
Relocate zone primary file to /var/lib/bind/ directory: portion within DDNS subsection, then continue below.


/!\ Debian <8 does not support inline-signing
so that requires DDNS for the zone for our approach here.
That also means that RRs for DNSSEC that go in that zone will be written to that zone file like most any other DDNS changes to the zone data.
For Debian <8, if one has not yet implemented DDNS for the zone, do so as above in the DDNS subsection, then continue below.


{i} Key algorithms and bits. At least as of 2025-08-01:

domain key algorithm bits
.      KSK  8        2048
.      ZSK  8        2048
org.   KSK  8        2048
org.   ZSK  8        1024
net.   KSK 13         512
net.   ZSK 13         512
com.   KSK 13         512
com.   ZSK 13         512
...
algorithms:
8 RSASHA256
13 ECDSAP256SHA256

Domain Name System Security (DNSSEC) Algorithm Numbers
Note that when setting up keys, notably algorithm and number of bits, one would typically want to use same algorithm and number of bits for keys as is present on up the parent chain. Notably as for same key algorithm, more bits would add overhead (CPU and data transmission size), without significantly increasing strength, and decreasing bits would weaken chain, so optimal is generally to match. Note also that generally one can't do apples-to-apples comparison between different algorithms, as same bits on different algorithms may be of differing strengths, so for any given bit size, algorithms may vary in their cryptographic strength and overhead (CPU load and/or transmission size).


{i} It is suggested to store keys in a directory where they're be less likely get removed when that would be problematic, and additionally, separate directory can be used for better organization and potentially better security. Debian defaults to its setting of directory which is /var/cache/bind but here we suggest using /var/lib/bind or a directory thereunder, instead. Here we'll show and use /var/lib/bind/keys directory. Accordingly, would suggest
/var/lib/bind/keys
be set up as follows:
# (umask 007 && mkdir /var/lib/bind/keys && chgrp bind /var/lib/bind/keys && chmod g+s /var/lib/bind/keys)
and also, in file:
/etc/bind/named.conf.options
add/have within the options section:
        include "/etc/bind/named.conf.options.local";
(that may also be well advised, notably to keep our local options as separate as feasible from package maintainer defaults, to ease updating/merging configurations with major version upgrades)
and then within that file:
/etc/bind/named.conf.options.local
have/add:
        key-directory "/var/lib/bind/keys";
And then use that directory for those files. We'll show that in our examples, but if one uses a different directory, substitute accordingly. In any case, suggesting should be somewhere under /var/lib/bind to play nicely with Debian's default configurations and Debian policy and FHS, etc. Note that /var/cache/bind should be reserved for less important cached data, so if contents there are removed it's easily replaced or recreated, e.g. copies of secondary zone data. For more important data, like keys we've DNSSEC signed our zone data with, or primary zones that aren't static (DDNS and/or DNSSEC in-line signing being used), those are more appropriately stored somewhere under the /var/lib/bind directory.
Documentation: key-directory (Debian 11 10-6)


In our example with example.com, we suitably match (at least at the time of this writing) to parent com domain on key algorithm, etc.
Debian >= 12 we can use
$ named -C
and see that it matches.
Debian 11 dnssec-policy default is documented here: dnssec-policy Statement Definition and Usage
Debian 10-7: we explicitly create our keys:


Debian <11: doesn't have dnssec-policy so we'll use auto-dnssec, and for that we also need to create our keys.
Create our initial keys and set ownerships/permissions so group bind can read the keys:
# (d=example.com key_directory='/var/lib/bind/keys' && umask 077 && cd "$key_directory" && dnssec-keygen -a ECDSAP256SHA256 -b 512 -f KSK "$d" && dnssec-keygen -a ECDSAP256SHA256 -b 512 "$d" && chgrp bind K"$d".+* && chmod g=r,o= K"$d".+*)


{i} Probably (mostly) applies only to older, e.g.
Debian <9 or possibly also with certain older or more limited hardware, but if capabilities that depend upon available entropy, such as the key generation procedure above are unacceptably slow or effectively stall,
see: Entropy


In the file:
/etc/bind/named.conf.local
edit to update our configuration for example.com, so it has these options with these corresponding settings:

        inline-signing yes;
        dnssec-policy default;
        serial-update-method unixtime;

Debian <11: instead of dnssec-policy use auto-dnssec:
        auto-dnssec maintain;
Debian <8: omit from the above, the configuration options
inline-signing and serial-update-method.
Debian >=11: one may also create and use a custom dnssec-policy if desired.
/!\ Note on dnssec-policy: as soon as one sets/changes configuration thereof and reloads or starts named, those changes take effect immediately! That can cause one to instantly break one's DNSSEC! E.g., if setting/changing and activating it, sets a policy that conflicts with the key(s) in use corresponding to DS record(s), those corresponding key(s) are dropped from use, and that will instantly break one's DNSSEC for the domain. Even if new keys have corresponding DS record(s), things may still be broken, at least for a while, due to TTLs and caching. So, highly advised when setting/changing and activating dnssec-policy where DNSSEC is already in use for domain, do so with extreme care, and generally recommended to first well test (e.g. on some test domain). Note that this may also apply with major version upgrades, most notably if one is using
        dnssec-policy default;
and the default changes between versions.
Debian >= 12 can use
$ named -C
to inspect dnssec-policy default,
Debian 11 dnssec-policy default is documented here: dnssec-policy Statement Definition and Usage
Note also dnssec-policy default may use csk (is the case for at least Debian 13-11), and one may want to instead have separate ksk and zsk, notably so one can rotate zsk without need to change DS. As for things that may aid updating DS records, and notably in other administrative control, e.g. via registrar, have a look at RFCs 7344 and 8078.

reload the configuration:
# rndc reload
Debian <7 may need to also issue command for initial zone signing, e.g.:
# rndc sign example.com

Verify that the zone works and is properly signed and ready for DS # (d=example.com key_directory='/var/lib/bind/keys'; k=$(printf '%05d' "$(dig @::1 +norecurse "$d". DNSKEY | dnssec-dsfromkey -f <(cat) "$d" | awk '{print $4;}' | sort -u)"); delv @::1 -a <(sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^\([^ ]*\) \([^ ]* [^ ]* [^ ]* \)/\1 initial-key \2"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trust-anchors {\n        &\n};/' "$key_directory"/K"$d".+013+"$k".key) +root="$d" "$d". SOA +multiline)
You should see a first output line of:
; fully validated
followed by SOA record data and then signature related data.
If you get the error "delv: No trusted keys were loaded" can verify the zone with:
# (d=example.com key_directory='/var/lib/bind/keys'; k=$(printf '%05d' "$(dig @::1 +norecurse "$d". DNSKEY | dnssec-dsfromkey -f <(cat) "$d" | awk '{print $4;}' | sort -u)"); delv @::1 -a <(sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^\([^ ]*\) \([^ ]* [^ ]* [^ ]* \)/\1 initial-key \2"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trust-anchors {\n        &\n};/' "$key_directory"/K"$d".+013+"$k".key) +root="$d" "$d". SOA +multiline)
One can also do likewise pulling the DNSKEY data from DNS - but be sure to do so only over sufficiently secure trusted network communications channel (e.g. ::1):
$ (d=example.com; delv @::1 -a <(dig @::1 +noall +answer "$d". DNSKEY | sed -e '/^;/d;s/[ \t]\{1,\}/ /g;s/ [0-9]\{1,\} IN DNSKEY / IN DNSKEY /;s/ IN DNSKEY / /;s/^\([^ ]*\) \([^ ]* [^ ]* [^ ]* \)/\1 initial-key \2"/;:s;/"[^ ]*$/b t;s/\("[^ ]*\) /\1/;b s;:t;s/$/";/;H;$!d;x;s/^\n//;s/.*/trust-anchors {\n        &\n};/') +root="$d" "$d". SOA +multiline)

/!\ Once the above has been validated, before proceeding further, be sure to securely SAVE BACKUP COPIES of the keys! The keys are in
/var/lib/bind/keys/ in our example configuration. Once the DS record(s) is(/are) set up in the delegating parent domain/zone, if keys are lost, DNS(SEC) will be failed for a substantial period of time, and one cannot be assured of fully and immediately correcting such situation without those same keys. Any resolvers, etc. that do DNSSEC validation may continue to reject (SERVFAIL) DNS data from the domain until the DNSSEC issue is corrected, and including any relevant (negative) caching on data/keys seen, including earlier.

/!\ It is CRUCIAL that the above validation checks be working properly before proceeding further, and one should likewise have the keys securely backed up.

So, here's the part where we go fully "live" with DNSSEC. Similar to NS records for nameserver delegation, there are DS records for delegation of digital signatures/signing.

So, one needs to have the relevant DS record(s) added to the delegating parent/upstream zone. Again, be sure the validation steps noted further above have first been successfully completed before proceeding. If you proceed with the next steps without first having the above working properly, you'll significantly break your DNS, and it may take a substantial period of time to fully rectify that. (Essentially if DS data is set up in delegating parent/upstream zone, and you're not properly functional and ready for that yet, essentially you're saying that you're using DNSSEC and not to trust the data if it's not properly set up and signed with the key data provided ... and then you'd not have proper key data set up on your zone, so any DNS that honored DNSSEC would reject your DNS data, and that may persist some fair while due to key lifetimes, DNS TTLs, expiration, etc.)

So, next we obtain the needed DS data from our signed zone:
$ (d=example.com; dig @::1 +norecurse "$d". DNSKEY | dnssec-dsfromkey -f <(cat) "$d" | awk '{if($2 ~ /^IN$/ && $3 ~ /^DS$/ && $6 ~ /^1$/)next;else print;}')


{i} Note in the above we use awk(1) to filter out SHA-1 digest DS record(s). Per RFC 4509 section 3:


That gets one the DS record(s) that need(s) to be added to upstream/parent delegating zone to make the DNSSEC effective from the delegating authority(/ies). The precise procedure on adding those to the delegating upstream/parent will depend what exactly is there. It might be as simple as directly adding the relevant data to the DNS nameserver. For some registrars, it may be matter of doing copy/paste of the relevant data into some specific web form. Some registrars will fetch the DNSKEY data from your zone, determine the DS data from that, display it and ask for confirmation to add it (in which case, do be sure to check that it matches properly as expected). Others may have you input key data from the DNSKEY record(s). Some may possibly be able to utilize CDS and/or CDNSKEY records to update DS record(s)
(see also: RFCs 7344 and 8078). So, precise procedures will vary by nameserver(s) and/or registrar, etc., but by whatever the appropriate means are, one gets the relevant DS record(s) into the delegating authority parent zone. So, DNSKEY in the child zone (dnssec-policy or auto-dnssec should take care of that for you), and DS goes only in the parent, not also in the child (so in that regard it's not quite so analogous to NS records). Once DS is in the parent zone, DNSSEC becomes live (possibly subject to relevant TTLs and SOA MINIMUM and respectively relevant caching and "negative" caching).

Not covered here - alternative trust anchors - how to have DNSSEC authority for nameservers, resolvers, etc. come from alternative source (e.g. local) directly (or indirectly), rather than directly or indirectly from the Internet root (.) DNS nameservers.

chroot

Implementing chroot is optional.

{i} This (chroot) subsection has been tested and validated on Debian 13 through 6, and also through sid/unstable+experimental as of 2025-08-31.

{i} Note: The approach we use here uses symbolic link(s) and bind (as in mount(8) fstype, not BIND) mounts. This is done in a manner which not only sets named to run in chroot, but also allows things that generally interact with named from outside the chroot to be able to do so without needing to reconfigure them for named in chroot, and lets us easily reconfigure named to run with or without chroot with only one simple configuration file change and a restart of named (that can also be useful for, e.g. chroot troubleshooting).

Create relevant directories and files and set relevant ownerships/permissions:

# (umask 027 && mkdir /var/lib/named{,/{dev,etc,usr{,/share},var{,/cache}}})
# (umask 0777 && cd /var/lib/named && > dev/null && > dev/random && mkdir {usr/share/dns,var/cache/bind})
# (cd /var/lib/named && chgrp bind . dev etc usr{,/share} var{,/cache})

Additionally:
Debian >=7:

# (umask 027 && mkdir /var/lib/named/run{,/systemd})
# ln -s ../run /var/lib/named/var/run
# (umask 0777 && cd /var/lib/named && > run/systemd/notify && mkdir run/named)
# (cd /var/lib/named && chgrp bind run{,/systemd})

Debian <7:

# (umask 027 && mkdir /var/lib/named/var/run)
# (umask 0777 && cd /var/lib/named && mkdir var/run/named)
# (cd /var/lib/named && chgrp bind var/run)

If one is or will be using DDNS or DNSSEC for primary zone(s) on this server, additionally:

# (umask 027 && mkdir /var/lib/named/var/lib)
# (umask 0777 && cd /var/lib/named && mkdir var/lib/bind)
# (cd /var/lib/named && chgrp bind var/lib)

And in file:
/etc/fstab
add the following lines:

/dev/null /var/lib/named/dev/null none bind 0 0
/dev/random /var/lib/named/dev/random none bind 0 0
/usr/share/dns /var/lib/named/usr/share/dns none bind 0 0
/var/cache/bind /var/lib/named/var/cache/bind none bind 0 0

Additionally:
Debian >=7:

/run/named /var/lib/named/run/named none bind 0 0
/run/systemd/notify /var/lib/named/run/systemd/notify none bind 0 0

Debian <7:

/var/run/named /var/lib/named/var/run/named none bind 0 0

If one is or will be using DDNS or DNSSEC for primary zone(s) on this server also add the line:
/var/lib/bind /var/lib/named/var/lib/bind none bind 0 0
For hosts that aren't using systemd for init don't include the line:
/run/systemd/notify /var/lib/named/run/systemd/notify none bind 0 0
or include the nofail option.
Debian <10 don't include the line:
/usr/share/dns /var/lib/named/usr/share/dns none bind 0 0
or include the nofail option.

If one is using systemd (Debian's default for Debian >=8):

# systemctl daemon-reload

Mount our newly configured bind mounts:
# mount -a

At this point, if the service is running, stop it:
# rndc stop

Copy our existing /etc/bind content, being careful to preserve ownerships and permissions, into our chroot location, e.g.:

# (cd / && tar -cf - etc/bind | (cd /var/lib/named && tar -xf -))

/!\ Check to be fully sure one has successfully copied all that content, before proceeding to the next step.
Then remove /etc/bind and replace it with a symbolic link:
# rm -rf /etc/bind && ln -s ../var/lib/named/etc/bind /etc/bind
{i} Note that alternatively, one could empty the /etc/bind directory, set its ownerships and permissions to root:root 000, and set up and activate bind mount of /var/lib/named/etc/bind atop the /etc/bind directory. I think however the symbolic link approach makes it more clear to DNS administrators, systems administrators, and others, what the situation is and apparent intent, whereas a bind mount there may be more confusing and prone to error or misinterpretation, but bind mount there does/would have the modest advantage that then /etc/bind itself would still be a directory, rather than symbolic link to directory.

/!\ Note also, the above is quite minimal. One may need or want additional content accessible under the chroot for named to access it, e.g. for certain desired or required functionality. One can also, e.g. review the file:
/etc/apparmor.d/usr.sbin.named
as a guide for what one may potentially want to add under the chroot.


AppArmor (enabled by default in Debian >=10),
add to file:
/etc/apparmor.d/local/usr.sbin.named
the following lines:

  # ####################################################################
  # For our /var/lib/named chroot based upon
  # /etc/apparmor.d/usr.sbin.named
  # /etc/bind should be read-only for bind
  # /var/lib/bind is for dynamically updated zone (and journal) files.
  # /var/cache/bind is for slave/stub data, since we're not the origin of it.
  # See /usr/share/doc/bind9/README.Debian.gz
  /var/lib/named/etc/bind/** r,
  /var/lib/named/var/lib/bind/** rw,
  /var/lib/named/var/lib/bind/ rw,
  /var/lib/named/var/cache/bind/** lrw,
  /var/lib/named/var/cache/bind/ rw,

  # Database file used by allow-new-zones
  /var/lib/named/var/cache/bind/_default.nzd-lock rwk,

  # gssapi
  /var/lib/named/etc/krb5.keytab kr,
  /var/lib/named/etc/bind/krb5.keytab kr,

  # ssl
  /var/lib/named/etc/ssl/*.cnf r,
  /var/lib/named/etc/ssl/*.conf r,

  # root hints from dns-data-root
  /var/lib/named/usr/share/dns/root.* r,

  # GeoIP data files for GeoIP ACLs
  /var/lib/named/usr/share/GeoIP/** r,

  # dnscvsutil package
  /var/lib/named/var/lib/dnscvsutil/compiled/** rw,

  # Allow changing worker thread names
  owner /var/lib/named@{PROC}/@{pid}/task/@{tid}/comm rw,

  # named need to check if hugepages is available
  /var/lib/named/sys/kernel/mm/transparent_hugepage/enabled r,

  /var/lib/named@{PROC}/net/if_inet6 r,
  /var/lib/named@{PROC}/*/net/if_inet6 r,
  /var/lib/named@{PROC}/sys/net/ipv4/ip_local_port_range r,
  /var/lib/named/usr/sbin/named mr,
  /var/lib/named/{,var/}run/named/named.pid w,
  /var/lib/named/{,var/}run/named/session.key w,
  # support for resolvconf
  /var/lib/named/{,var/}run/named/named.options r,

  # some people like to put logs in /var/log/named/ instead of having
  # syslog do the heavy lifting.
  /var/lib/named/var/log/named/** rw,
  /var/lib/named/var/log/named/ rw,

  # gssapi
  /var/lib/named/var/lib/sss/pubconf/krb5.include.d/** r,
  /var/lib/named/var/lib/sss/pubconf/krb5.include.d/ r,
  /var/lib/named/var/lib/sss/mc/initgroups r,
  /var/lib/named/etc/gss/mech.d/ r,

  # ldap
  /var/lib/named/etc/ldap/ldap.conf r,
  /var/lib/named/{,var/}run/slapd-*.socket rw,

  # dynamic updates
  /var/lib/named/var/tmp/DNS_* rw,

  # dyndb backends
  /var/lib/named/usr/lib/bind/*.so rm,

  # Samba DLZ
  /var/lib/named/{usr/,}lib/@{multiarch}/samba/bind9/*.so rm,
  /var/lib/named/{usr/,}lib/@{multiarch}/samba/gensec/*.so rm,
  /var/lib/named/{usr/,}lib/@{multiarch}/samba/ldb/*.so rm,
  /var/lib/named/{usr/,}lib/@{multiarch}/ldb/modules/ldb/*.so rm,
  /var/lib/named/var/lib/samba/bind-dns/dns.keytab rk,
  /var/lib/named/var/lib/samba/bind-dns/named.conf r,
  /var/lib/named/var/lib/samba/bind-dns/dns/** rwk,
  /var/lib/named/var/lib/samba/private/dns.keytab rk,
  /var/lib/named/var/lib/samba/private/named.conf r,
  /var/lib/named/var/lib/samba/private/dns/** rwk,
  /var/lib/named/etc/samba/smb.conf r,
  /var/lib/named/dev/urandom rwmk,
  owner /var/lib/named/var/tmp/krb5_* rwk,

  # systemd sd_notify
  /var/lib/named/run/systemd/notify w,

  # Site-specific additions and overrides. See local/README for details.
  # ####################################################################
  # Additional for same chroot, based upon AppArmor denied to named,
  # that would otherwise generally be correspondingly allowed outside
  # chroot:
  /var/lib/named/ r,
  /var/lib/named/dev/null r,
  # ####################################################################


{i} May also be able to generate that content with a bit of code, e.g.:

$ perl -e 'my $chroot=q(/var/lib/named); $_=$chroot; s;/{2,};/;g; s;([^/])/+$;$1;; $chroot=$_; {$/=undef; $_=<>;}; s;\A.*?/usr/sbin/named.*?\{\n*(.*)\}.*?\z;$1;s; s/^[ \t]*(?:\#include|capability)[ \t].*\n//gm; s;^([ \t]*(?:owner[ \t]+)?)([/@]);$1$chroot$2;gm; s/\A\n+//; s/\n{2,}\z/\n/; $_ = join("\n",q(  # ####################################################################),"  # For our $chroot chroot based upon",q(  # /etc/apparmor.d/usr.sbin.named),q(),) . $_ . join("\n",q(  # ####################################################################),q(  # Additional for same chroot, based upon AppArmor denied to named,),q(  # that would otherwise generally be correspondingly allowed outside),q(  # chroot:),"  $chroot/ r,","  $chroot/dev/null r,",q(  # ####################################################################),q(),); print;' < /etc/apparmor.d/usr.sbin.named

Be sure to review, as one may need/wish to make changes.


or alternatively for a much more minimal set, add only the below lines to that file:

  /var/lib/named/etc/bind/** r,
  /var/lib/named/usr/share/dns/root.* r,
  /var/lib/named/{,var/}run/named/named.pid w,
  /var/lib/named/{,var/}run/named/session.key w,

And as is commonly the case, trade-off/compromise between convenience and security. The much more complete, generally avoid AppArmor complaining about denied access. The quite minimal, more secure, but may generate lots of diagnostics of denied access, and mostly normal and to be expected, to not only what isn't under chroot, but may not even exist outside of chroot - and named may mostly just be (re)checking if those things are there or not.

In either case, reload the relevant AppArmor configuration:
# apparmor_parser -r /etc/apparmor.d/usr.sbin.named


Edit the file:
/etc/default/named
Debian <11 use file:
/etc/default/bind9
Notably changing the OPTIONS setting, so it looks like:
OPTIONS="-u bind -t /var/lib/named"

Then start the service, e.g.:
# systemctl start named.service

And of course check to confirm the service is working as expected.
If we earlier (or now) set up zones as authoritative, either primary, or secondary, like in either or both of our corresponding example subsections above, we could also now (re)check those as in our earlier example above, e.g.:
Check that we're authoritative for the zone(s).

{i} Invoking named in foreground and adding the -f and -g options, in addition to those configured in /etc/default/named (/etc/default/bind9 for Debian <11) (e.g. -u bind -t /var/lib/named), can be quite useful in diagnosing chroot issues. named(8)'s debug options and the like can also be useful. Also looking at the AppArmor diagnostics on what named attempts and is denied can be useful. One can also review the file:
/etc/apparmor.d/usr.sbin.named
as a guide for what one may potentially want to add to the file:
/etc/apparmor.d/local/usr.sbin.named
If need be, even strace(8) can be quite useful in diagnosing/troubleshooting chroot issues.


Links and Resources


This versions table is intended to mostly only cover
last version for any given Debian release (may be at least somewhat of a moving target for releases that are not EOL/archived), and
key changes/differences as relevant to this documentation.

version

key changes/differences
as relevant to this documentation

Debian

ISC

Debian

bind9

experimental

1:9.21.14-1

unstable

1:9.20.15-2

testing

1:9.20.15-2

13.2

1:9.20.15-1~deb13u1

allow-transfer default none

9.20.0

allow-transfer default none

12.12

1:9.18.41-1~deb12u1

allow-transfer default any

11.11

1:9.16.50-1~deb11u4

systemctl named.service bind9-dnsutils

9.15.6

dnssec-policy

9.13.0

primary/primaries/secondary/secondaries

10.13

1:9.11.5.P4+dfsg-5.1+deb10u11

systemctl bind9.service dnsutils

9.13

1:9.10.3.dfsg.P4-12.3+deb9u12

8.11

1:9.9.5.dfsg-9+deb8u19

systemctl bind9.service

7.11

1:9.8.4.dfsg.P1-6+nmu2+deb7u10

update-rc.d bind9

6.0.10

1:9.7.3.dfsg-1~squeeze19

update-rc.d named

9.7.0

auto-dnssec

5.0.10

1:9.6.ESV.R4+dfsg-0+lenny4

4.0.6

1:9.3.4-2etch6

3.1.16

1:9.2.4-1sarge3

3.0.2

1:9.2.1-2.woody.2


Testing tools

BIND 9 Documentation

BIND9 documentation:


CategorySoftware CategoryNetwork