Self-hosted DNS control panel for BIND9.
BIND9 Web UI helps DNS operators manage BIND9 without hand-editing every zone and configuration file. It supports local management and remote management over SSH, with a dashboard for zones, records, ACLs, TSIG keys, RPZ, replication, backups, user access, and operational visibility.
Copyright (c) 2025 Stephane ASSOGBA
- One-command install
- Production install
- Install options
- Install on the BIND server
- Manual development start
- Environment variables
- Remote BIND permissions
- Recommended production setup
- Contributing
On Debian or Ubuntu:
git clone https://github.com/Steph-ux/bind9-web-ui.git && cd bind9-web-ui && sudo bash install.shThe installer can deploy behind nginx, Apache, or no reverse proxy, and can run on the same host as BIND9 or on a separate management server.
BIND9 is one of the most capable DNS servers available, but it still lacks an official management interface. In practice that means:
- manual edits in multiple configuration files
- easy-to-miss syntax mistakes
- weak visibility across zones, access control, and runtime state
- awkward remote administration when BIND runs on another host
This project fills that gap with a full web UI focused on operational safety and day-to-day administration.
- DNS administrators who run BIND9 in production or labs
- Teams that want a self-hosted DNS management interface
- Operators managing several BIND9 servers from one UI
- Environments that need local control instead of a SaaS DNS platform
- Create, edit, and delete DNS zones
- Manage records for
A,AAAA,CNAME,MX,TXT,NS,SOA,PTR,SRV, and more - Generate and parse BIND zone files automatically
- Create reverse DNS and PTR records automatically when appropriate
- Import existing zones and managed includes from an existing BIND installation
- Manage ACLs through dedicated BIND include files
- Manage TSIG keys through dedicated include files
- Configure zone transfer authorization more safely
- Manage Response Policy Zone entries from the UI
- Import RPZ data from text, zone-file style content, or remote URLs
- Handle large blocklists with pagination and batch processing
- Keep RPZ data synchronized with BIND-managed files
- Add multiple BIND servers over SSH
- Detect platform details and common BIND paths automatically
- Switch the active target between local mode and remote mode
- Run configuration reads, writes, validation, and
rndcoperations against the active target
- Register secondary or replication targets
- Maintain zone-to-server bindings
- Run sync operations and conflict detection
- Send BIND notify actions where applicable
- Dashboard with server, DNS, and operational summaries
- Status view for service health and runtime information
- Real-time logs
- Backups and restore flows
- Configuration snapshots
- User management and API tokens
- Session-based authentication
- Role-based access control for admin, operator, and viewer roles
- Forced password rotation for bootstrap users
- Validation and sanitization across BIND-facing inputs
- CSRF-aware authenticated mutations
- SSRF protections for remote fetch and notification features
The application supports two operating modes.
The web UI runs on the same host as BIND9 and interacts with:
- BIND configuration files
- zone files
rndc- validation binaries such as
named-checkconfandnamed-checkzone
The web UI runs on a separate host and connects to a BIND server over SSH. The application executes commands and manages files remotely through the active SSH connection.
This is the mode used when the UI is deployed on a central management server and your BIND servers live elsewhere.
This project does not try to rewrite arbitrary BIND layouts blindly. A few conventions matter if you want the UI to manage everything cleanly.
ACLs and TSIG keys are managed through dedicated include files:
named.conf.aclsnamed.conf.keys
Your main BIND configuration should include them, for example:
include "/etc/bind/named.conf.acls";
include "/etc/bind/named.conf.keys";
If an ACL exists only inside named.conf.options, BIND will use it, but the ACL page in the UI will not import it as a managed ACL until it is moved to named.conf.acls.
The UI can import and reflect existing BIND configuration, but if you modify files manually outside the UI, the safest workflow is:
- make the manual BIND change
- validate it on the server
- use the relevant sync or refresh flow in the UI
Record-level edits are first-class in the UI. Some deeper structural zone changes are still safer to perform directly in BIND and then sync back into the application.
| Layer | Technologies |
|---|---|
| Frontend | React 19, Vite 7, TypeScript, Tailwind CSS 4, shadcn/ui, Wouter |
| Backend | Node.js, Express 5, TypeScript |
| Data | SQLite by default, with PostgreSQL and MySQL support via Drizzle ORM |
| Realtime | WebSocket (ws) |
| Remote access | ssh2 |
| Validation | Zod, drizzle-zod |
Bind-Config/
├── client/ # React frontend
├── server/ # Express backend and BIND services
├── shared/ # Shared schemas and types
├── data/ # SQLite database and local data
├── script/ # Build scripts
├── DESIGN.md # Active design system direction
└── README.md
- Node.js 20+ recommended
- npm
- build tools required by native dependencies such as
better-sqlite3
On Debian or Ubuntu:
sudo apt update
sudo apt install -y build-essential python3For local mode:
- a host with BIND9 installed
- access to BIND config and zone directories
rndc,named-checkconf, andnamed-checkzone
For remote mode:
- SSH access to the target server
- a user with sufficient file and command permissions
- passwordless
sudofor required BIND operations if the user is not already privileged
On Debian or Ubuntu:
git clone https://github.com/Steph-ux/bind9-web-ui.git
cd bind9-web-ui
sudo bash install.shAs a single shell line:
git clone https://github.com/Steph-ux/bind9-web-ui.git && cd bind9-web-ui && sudo bash install.shDefault install:
- app installed in
/opt/bind9-web-ui - env file in
/etc/bind9-web-ui.env - systemd service
bind9-web-ui - nginx reverse proxy on
https://<server-ip>:8443 - Node.js listens locally on
127.0.0.1:3001 - generated admin password stored in
/root/bind9-web-ui-bootstrap-admin.txt
Set the initial admin password:
sudo env DEFAULT_ADMIN_PASSWORD='change-me-now' bash install.shUse Apache instead of nginx:
sudo env WEB_SERVER=apache bash install.shUse nginx on ports 80/443:
sudo env WEB_SERVER=nginx WEB_HTTP_PORT=80 WEB_HTTPS_PORT=443 SERVER_NAME=dns.example.com bash install.shUse Apache on ports 80/443:
sudo env WEB_SERVER=apache WEB_HTTP_PORT=80 WEB_HTTPS_PORT=443 SERVER_NAME=dns.example.com bash install.shInstall without nginx/apache:
sudo env WEB_SERVER=none bash install.shWEB_SERVER can be nginx, apache, or none. If WEB_SERVER=none, put your own TLS reverse proxy in front of the Node.js service.
To replace the default web site on the server:
sudo env DISABLE_DEFAULT_SITE=1 WEB_HTTP_PORT=80 WEB_HTTPS_PORT=443 SERVER_NAME=dns.example.com bash install.shIf the Web UI runs on the same host as BIND9:
sudo env LOCAL_BIND_ACCESS=1 bash install.shThis lets systemd write to the configured BIND directories and adds the service user to the BIND group when it exists. If BIND files are root-only, local writes may still fail; use an SSH connection to localhost with sudo, or adjust permissions deliberately.
git clone https://github.com/Steph-ux/bind9-web-ui.git
cd bind9-web-uinpm installnpm run db:pushOn Windows:
npm run devThen open:
http://localhost:3001
On first startup, a bootstrap admin account is created automatically.
- Username:
admin - Password: the value of
DEFAULT_ADMIN_PASSWORD, if set - Otherwise: a generated bootstrap password is created
In production, the bootstrap password is not written to logs. Set DEFAULT_ADMIN_PASSWORD before first startup if you want a known initial password.
Build the application:
npm run buildStart the compiled server:
node dist/index.cjsSet NODE_ENV=production in your environment or service manager.
Note:
npm startis currently written for Windows-style environment variable syntax- for Linux services, prefer setting
NODE_ENV=productionin systemd, Docker, or your shell and launchingnode dist/index.cjs
The repository already includes an .env.example.
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind address for the Node.js server |
PORT |
3001 |
HTTP port for the application |
NODE_ENV |
development |
Runtime mode |
SESSION_SECRET |
random in development | Mandatory in production |
DB_TYPE |
sqlite |
sqlite, postgresql, or mysql |
DATABASE_URL |
data/bind9admin.db |
SQLite path or database connection URL |
BACKUP_DIR |
data/backups |
Runtime backup directory |
BIND9_CONF_DIR |
/etc/bind |
Local-mode BIND config directory |
BIND9_ZONE_DIR |
/var/cache/bind |
Local-mode zone directory |
RNDC_BIN |
rndc |
Path to the rndc binary |
NAMED_CHECKCONF |
named-checkconf |
Path to the config validation binary |
DEFAULT_ADMIN_PASSWORD |
unset | Optional initial bootstrap admin password |
SQLite is the default and easiest option. PostgreSQL and MySQL are also supported.
No extra setup is required beyond:
npm run db:pushSet:
DB_TYPE=postgresql
DATABASE_URL=postgresql://user:password@host:5432/dbnameThen push the schema.
On Windows:
npm run db:push:pgOn Linux:
DB_TYPE=postgresql npx drizzle-kit push --config=drizzle.config.pg.tsSet:
DB_TYPE=mysql
DATABASE_URL=mysql://user:password@host:3306/dbnameThen push the schema.
On Windows:
npm run db:push:mysqlOn Linux:
DB_TYPE=mysql npx drizzle-kit push --config=drizzle.config.mysql.ts| Script | Purpose |
|---|---|
npm run dev |
Start the server in development mode |
npm run dev:client |
Start only the Vite client |
npm run build |
Build the client and server for production |
npm start |
Windows-oriented production start shortcut |
npm run check |
Run TypeScript checks |
npm run db:push |
Push the SQLite schema |
npm run db:push:pg |
Push the PostgreSQL schema |
npm run db:push:mysql |
Push the MySQL schema |
For remote SSH management, the SSH user may need passwordless sudo for BIND commands and safe file operations. A typical example looks like this:
<user> ALL=(ALL) NOPASSWD: /usr/sbin/rndc, /usr/bin/named-checkconf, /usr/bin/named-checkzone, /bin/cp, /usr/bin/cpAdjust this to your platform and operational policy.
If you also use firewall management through the UI, you may need additional sudo permissions for tools such as:
ufwnftiptablesfirewall-cmdsystemctl
When a record is created or updated from the Web UI, the zone file must be valid and BIND must reload the affected zone before clients can resolve the new data.
If the zone file contains the new record but DNS queries still return NXDOMAIN or an older SOA serial, BIND is still serving the previous in-memory zone. Validate the zone and reload it:
sudo named-checkconf
sudo named-checkzone intra.isoceltelecom.com /var/cache/bind/zones/forward/intra.isoceltelecom.com
sudo rndc reload intra.isoceltelecom.comFor deployments where the Web UI writes BIND zone files directly, use an automatic reload guard so changes are applied only after validation succeeds:
# /etc/systemd/system/bind-zones-auto-reload.service
[Unit]
Description=Validate and reload BIND zones after Web UI changes
After=named.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/bind-zones-reload-if-valid.sh# /etc/systemd/system/bind-zones-auto-reload.path
[Unit]
Description=Watch BIND zone files for Web UI changes
[Path]
PathChanged=/var/cache/bind/zones/forward/intra.isoceltelecom.com
PathChanged=/var/cache/bind/zones/reverse/intra.isoceltelecom.com
Unit=bind-zones-auto-reload.service
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now bind-zones-auto-reload.pathThe reload script should run named-checkconf, validate every managed zone with named-checkzone, and then call rndc reload <zone>. This prevents the UI from reporting a saved record while BIND continues serving stale data.
- Passwords are hashed using Node.js crypto primitives
- Sessions are hardened with strict cookie settings in production
- Login throttling and IP ban logic are built in
- Mutating routes require authenticated, same-site-safe flows
- API tokens support scoped access
- BIND-facing input is validated and sanitized
- Remote fetch and notification targets are checked to reduce SSRF risk
- Sensitive fields are masked in API responses where appropriate
- The UI applies supported changes automatically to the active BIND target
- Configuration writes are validated before reload or reconfigure actions are considered successful
- If an operation fails, the UI is expected to surface the error instead of reporting a false success
- Backups are stored under the application data directory
- Real-time logs and status views are available even when some local-only BIND metrics are not
- run the web UI behind HTTPS
- set a strong
SESSION_SECRET - set
DEFAULT_ADMIN_PASSWORDonly for initial bootstrap, then rotate it - prefer SSH key authentication over password authentication
- restrict SSH access tightly
- keep remote sudo permissions minimal
- use PostgreSQL or MySQL if you need a database backend beyond local SQLite
- monitor the host and back up the application data directory
Contributions are welcome. Start with CONTRIBUTING.md, run npm run check before opening a pull request, and include enough context for DNS/BIND-related changes to be reviewed safely.
Do not post secrets, real zone exports, private keys, or production screenshots in public issues. See SECURITY.md for reporting guidance.
MIT License. See LICENSE.