setting up bind to work with nsupdate (SERVFAIL)

I had pretty much exactly the same issue on an Ubuntu server and it turned out to be two problems:

(1) apparmor

I don't know if the same is true for Debian, but on Ubuntu bind9 is run with apparmor enabled. This means it is only allowed to write to certain places. The places are listed in /etc/apparmor.d/usr.sbin.named, and it is generally advisable to stay within these directories. You can install the apparmor-utils package and (temporarily) disable apparmor for bind to see if this causes your issue:

sudo aa-status

should show /usr/sbin/named in the list of enforced profiles. Then run

sudo aa-complain /usr/sbin/named

to put it into complain mode.

(2) zone file

Almost no manual/tutorial mentions this, but bind9 expects an (pre-)existing zone file to work properly. The end of file error could be caused by the fact that zone file doesn't exist yet (/etc/bind/primary/example.com and /etc/bind/primary/sub.example.com in your example). You can simply create one like this:

echo "; DO NOT EDIT MANUALLY - use the \"nsupdate\" utility to prevent data loss
;
\$ORIGIN example.com.
\$TTL 86400  ; 1 day
@    IN SOA  ns1.example.com. hostmaster.example.com. (
       2009074711 ; serial
       7200       ; refresh (2 hours)
       300        ; retry (5 minutes)
       604800     ; expire (1 week)
       60         ; minimum (1 minute)
       )
   IN  NS  ns1.example.com.
ns1    IN  A  <IP of your bind server>" | sudo -u bind tee /etc/bind/primary/example.com

I was having very similar issues until I changed the location of where I stored my zone files.

Bind had permission to write to /var/cache/bind, but your zone files are stored in /etc/bind/.... Bind does not currently have permission to write to files in /etc/bind/..., so you would need to update Bind's permissions or store the zone files in a directory where Bind has the proper permissions. I will cover the simple steps to move the zone files to the directory recommended for dynamic zones (/var/lib/bind/).

  1. Move the zone files with mv (likely needs to be executed as root)

    mv /etc/bind/primary/example.com /var/lib/bind/primary/
    mv /etc/bind/primary/sub.example.com /var/lib/bind/primary/
    
  2. Update the file path in your named.conf configurations. In your case this means updating /etc/bind/named.conf.local

    zone "example.com" {
      type master;
      file "/var/lib/bind/primary/example.com";  //this line changed
      //other stuff removed for clarity
    };
    
    zone "sub.example.com" {
      type master;
      file "/var/lib/bind/primary/sub.example.com";  //this line changed
      //other stuff removed for clarity
    };
    
  3. Restart Bind with service bind9 restart


See the section in nsupdate

   With the -k option, nsupdate reads the shared secret from the file
   keyfile. Keyfiles may be in two formats: a single file containing a
   named.conf-format key statement, which may be generated automatically
   by ddns-confgen, or a pair of files whose names are of the format
   K{name}.+157.+{random}.key and K{name}.+157.+{random}.private, which
   can be generated by dnssec-keygen. The -k may also be used to specify a
   SIG(0) key used to authenticate Dynamic DNS update requests. In this
   case, the key specified is not an HMAC-MD5 key.

So if you were to reformat it into the

key sub.example.com. {
        algorithm HMAC-MD5;
        secret "xxxx xxxx";
};

form and leave that in a file it will work, or alternatively -k K{name}.+157.+{random}.*