Many sysadmins edit zone files manually. Some script it. Both need a way to increment the SOA serial when a change is made. Here is a sh-compatible, fully POSIX, shellcheck-clean script to do that atomically. You should modify it to suit your own needs.
This example shows additionally how to incorporate it in a Let's Encrypt hook using NSD as your primary name server.
#!/bin/sh
# Increment standard SOA serial
# Also optionally add Let's Encrypt record as part of a hook.
# Note: the first 10-digit number in the zone will be overwritten (y3k protection :P)
# To guarantee safety, the SOA record must be the first record in the zone file.
set -e
zonefile=/etc/nsd/alxu.ca.zone
exec 9<"$zonefile"
flock -n 9
oldzone="$(cat <&9)"
# Y Y Y Y M M D D N N
serialglob="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"
preserial="${oldzone%%$serialglob*}"
postserial="${oldzone#*$serialglob}"
tmp="${oldzone#$preserial}"
serial="${tmp%$postserial}"
case "$serial" in
$serialglob) ;;
*) echo 'zone file not in correct format, aborting'; exit 1
esac
serd="${serial%[0-9][0-9]}"
sern="${serial#[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]}"
nowd="$(date +%Y%m%d)"
if [ "$serd" = "$nowd" ]; then
if [ "$sern" = 99 ]; then
echo 'serial number is already 99, cannot increment, aborting' >&2
exit 1
fi
newser="$serd$(printf "%02d" $((sern+1)))"
else
newser="${nowd}00"
fi
echo "Editing zone file" >&2
# Let's Encrypt
printf '%s\n_acme-challenge.%s. 60 IN TXT %s\n' "$preserial$newser$postserial" "$1" "$2" > "$zonefile.new"
mv "$zonefile.new" "$zonefile"
# just output the zone
#printf '%s\n' "$preserial$newser$postserial" > "$zonefile.new"
#mv "$zonefile.new" "$zonefile"
printf "Reloading nsd: " >&2
nsd-control reload
echo "Waiting for axfr" >&2
# wait for nsd axfr, journalctl is dumb
sh -s << 'EOF' || true
journalctl -f -u nsd -n 0 | grep --line-buffered 'axfr for' | while read -r _; do
kill $$
# hopefully journalctl doesn't stick around too long
exit
done
EOF