Add configurable post-update journal synchronization via rndc sync -clean. Separate RFC 2136 update settings from local RNDC control settings. Run synchronization only after a successful nsupdate request and only when DYNDNS_RNDC_SYNC_CLEAN is enabled. Treat RNDC synchronization failures as warnings because the DNS update has already been accepted and named retains the authoritative state in its journal.
dynDNS
Lightweight dynDNS backend for BIND-based dynamic DNS updates.
This repository contains only the backend components of the dynDNS service: the main dyndns.sh script and a vars.example configuration template. It is designed as a backend component behind a web frontend or API that manages member accounts and IP assignments.
Outline
- Features
- Installation
- MariaDB Initialization
- Database Schema
- Configuration
- Directory Layout
- CRON Job Example
- License
- Authors
- Project Home
Features
- Dynamic DNS backend script for BIND-based environments
- Database-driven record management
- A and AAAA record handling
- Wildcard CNAME generation for managed subdomains
nsupdateintegration with TSIG authentication- DNSSEC-aware update workflow
- Simple configuration through a separate vars file
- Designed for self-hosted Linux environments
Installation
- Clone the repository.
- Copy
vars.exampleto a site-specific vars file. - Adjust paths, database settings, BIND zone settings and TSIG key references.
- Make
dyndns.shexecutable. - Run the script manually once before automation.
Example:
sudo groupadd --system dyndns
sudo install -d -m 0750 -o root -g dyndns /opt/dyndns
sudo git clone https://dev.town-square.de/cb601/dyndns.git /opt/dyndns
cd /opt/dyndns
sudo cp vars.example vars
sudo chown root:dyndns /opt/dyndns/dyndns.sh /opt/dyndns/vars
sudo chmod 0750 /opt/dyndns/dyndns.sh
sudo chmod 0640 /opt/dyndns/vars
sudo /opt/dyndns/dyndns.sh help
The dyndns.sh script is owned by root and readable by the dyndns group only. This keeps the contents of the vars file (including database credentials) restricted to the backend.
MariaDB Initialization
Before running the dynDNS backend for the first time, create the application database and a dedicated database user. Using a dedicated user with privileges limited to the dynDNS database is the recommended setup.
Connect to MariaDB as an administrative user:
mariadb -u root -p
Alternatively, when using the system root account and a configured /root/.my.cnf:
sudo mariadb
Create the database and grant access to a dedicated backend user:
CREATE DATABASE dyndns;
CREATE USER 'dyndnsuser'@'localhost' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
CREATE USER 'dyndnsuser'@'::1' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
CREATE USER 'dyndnsuser'@'127.0.0.1' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'localhost';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'::1';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'127.0.0.1';
FLUSH PRIVILEGES;
This setup allows local access for the dedicated dynDNS user from localhost, 127.0.0.1 and ::1 and keeps the dynDNS backend user restricted to the dyndns database and local connections only.
After database initialization, adjust the matching settings in your vars file:
DYNDNS_SQL_HOST="localhost"
DYNDNS_SQL_DATABASE="dyndns"
DYNDNS_SQL_USER="dyndnsuser"
DYNDNS_SQL_PASS="<REPLACE_WITH_STRONG_PASSWORD>"
Replace <REPLACE_WITH_STRONG_PASSWORD> with a site‑specific, strong password.
A first manual connection test is recommended before enabling cron-based automation, so database connectivity and privileges can be verified early.
Database Schema
The dynDNS backend expects a MariaDB schema with three core tables:
membersfor account and hostname ownership dataconnectionsfor the currently assigned IP address per memberlogin_attemptsfor tracking login attempts
The following definitions reflect the currently used database layout.
Table: members
Stores dynDNS account identity and hostname ownership data.
CREATE TABLE `members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(63) NOT NULL,
`domain` varchar(253) NOT NULL,
`email` varchar(254) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`,`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Field overview:
id: Internal primary keyusername: Host-specific account name, usually the subdomain partdomain: Managed DNS zone nameemail: Contact address of the memberpassword: Stored password hashsalt: Per-user salt used for password hashingtimestamp: Record timestamp stored as unsigned Unix time (seconds since epoch)UNIQUE (username, domain): Ensures that a hostname can only exist once per zone
Table: connections
Stores the currently active IP address assignment for a dynDNS member.
CREATE TABLE `connections` (
`id` int(11) NOT NULL,
`IP` varchar(45) NOT NULL,
`IPv6_flag` tinyint(1) unsigned NOT NULL DEFAULT 0,
`IP_locked` tinyint(1) unsigned NOT NULL DEFAULT 0,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_connections_members`
FOREIGN KEY (`id`) REFERENCES `members` (`id`)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Field overview:
id: Shared primary key and foreign key referencingmembers.idIP: Current IP address in text form;varchar(45)supports both IPv4 and IPv6IPv6_flag:0for A records,1for AAAA recordsIP_locked: Prevents automated DNS updates when settimestamp: Record timestamp stored as unsigned Unix time (seconds since epoch)
Table: login_attempts
Stores the login attempt counter for a member.
CREATE TABLE `login_attempts` (
`id` int(11) NOT NULL,
`count` smallint(5) unsigned NOT NULL DEFAULT 0,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_login_attempts_members`
FOREIGN KEY (`id`) REFERENCES `members` (`id`)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Field overview:
id: Shared primary key and foreign key referencingmembers.idcount: Number of tracked login attemptstimestamp: Record timestamp stored as unsigned Unix time (seconds since epoch)
Referential Integrity
The schema uses explicit foreign key constraints from connections.id and login_attempts.id to members.id. Both child tables use ON DELETE CASCADE, so dependent records are removed automatically when a member is deleted. This ensures database-level referential integrity and matches the intended one-to-one relationship design.
When deleting a member, the application only needs to remove the parent row from members; dependent rows in connections and login_attempts are removed automatically by the database.
Notes
- The schema uses
utf8mb4withutf8mb4_unicode_ci. usernameusesvarchar(63), which matches the maximum length of a single DNS label.domainusesvarchar(253), which matches the typical maximum length of a FQDN without trailing root dot.- IP addresses are stored as text in
varchar(45), which is sufficient for IPv4 and IPv6 storage.
Configuration
The backend is configured through a separate configuration file derived from vars.example.
Typical settings include:
- Base paths and log paths
- Database connection settings
- DNS zone name and zone file paths
- BIND server endpoint
- TSIG key file used for
nsupdateandrndc - Default TTL values
Recommended workflow:
- Start from
vars.example. - Create an environment-specific copy, for example
vars. - Keep secrets and local overrides out of version control.
- Validate database access and BIND update permissions before enabling automation.
Directory Layout
.
├── dyndns.sh
├── vars.example
├── README.md
├── Changelog.md
├── LICENSE
└── .gitignore
CRON Job Example
The following example assumes the job is installed in the crontab of a dedicated service user or root, with sufficient read access to the vars file and execute permissions on /opt/dyndns/dyndns.sh.
*/5 * * * * /opt/dyndns/dyndns.sh update-zone >/dev/null 2>&1
Running the job every 5 minutes is a reasonable default for typical dynDNS use cases.
If a non-default vars file is used:
*/5 * * * * /opt/dyndns/dyndns.sh --vars /opt/dyndns/vars update-zone >/dev/null 2>&1
License
See LICENSE for details.
Authors
CB-601 - the open tec Elevator
- Stephan Düsterhaupt
- Ivo Noack aka Insonic
Project Home
Project Home: https://dev.town-square.de/cb601/dyndns