Loading repository data…
Loading repository data…
Steph-ux / repository
BIND9 Web UI is a self-hosted DNS control panel that replaces manual config file editing with a full-featured dashboard. Manage zones, DNS records, ACLs, TSIG keys, and firewall rules across local and remote BIND9 servers via SSH. Supports UFW, firewalld, nftables and iptables with auto-detection.
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
On Debian or Ubuntu:
git clone https://github.com/Steph-ux/bind9-web-ui.git && cd bind9-web-ui && sudo bash install.sh
The 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:
This project fills that gap with a full web UI focused on operational safety and day-to-day administration.
A, AAAA, CNAME, MX, TXT, NS, SOA, PTR, SRV, and morerndc operations against the active targetThe application supports two operating modes.
The web UI runs on the same host as BIND9 and interacts with:
rndcnamed-checkconf and named-checkzoneThe 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.keysYour 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:
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
better-sqlite3On Debian or Ubuntu:
sudo apt update
sudo apt install -y build-essential python3
For local mode:
rndc, named-checkconf, and named-checkzoneFor remote mode:
sudo for required BIND operations if the user is not already privilegedOn Debian or Ubuntu:
git clone https://github.com/Steph-ux/bind9-web-ui.git
cd bind9-web-ui
sudo bash install.sh
As a single shell line:
git clone https://github.com/Steph-ux/bind9-web-ui.git && cd bind9-web-ui && sudo bash install.sh
Default install:
/opt/bind9-web-ui/etc/bind9-web-ui.envbind9-web-uihttps://<server-ip>:8443127.0.0.1:3001/root/bind9-web-ui-bootstrap-admin.txtSet the initial admin password:
sudo env DEFAULT_ADMIN_PASSWORD='change-me-now' bash install.sh
Use Apache instead of nginx:
sudo env WEB_SERVER=apache bash install.sh
Use 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.sh
Use 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.sh
Install without nginx/apache:
sudo env WEB_SERVER=none bash install.sh
WEB_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.sh
If the Web UI runs on the same host as BIND9:
sudo env LOCAL_BIND_ACCESS=1 bash install.sh
This 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-ui
npm install
npm run db:push
On Windows:
npm run dev
Then open:
http://localhost:3001
On first startup, a bootstrap admin account is created automatically.
adminDEFAULT_ADMIN_PASSWORD, if setIn 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 build
Start the compiled server:
node dist/index.cjs
Set NODE_ENV=production in your environment or service manager.
Note:
npm start is currently written for Windows-style environment variable syntaxNODE_ENV=production in systemd, Docker, or your shell and launching node dist/index.cjsThe 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:push
Set:
DB_TYPE=postgresql
DATABASE_URL=postgresql://user:password@host:5432/dbname
Then push the schema.
On Windows:
npm run db:push:pg
On Linux:
DB_TYPE=postgresql npx drizzle-kit push --config=drizzle.config.pg.ts
Set:
DB_TYPE=mysql
DATABASE_URL=mysql://user:password@host:3306/dbname
Then push the schema.
On Windows: