The header now shows the login page's wordmark letters (without the rule) instead of "Jarl" set in Cinzel, so the bundled Cinzel font, its preload and its licence stanza in the .deb's copyright file are removed: the web UI needs no web font. The README opens with the login page's banner, the logo above the wordmark (artwork/banner.svg). scripts/banner.py builds it from the web UI's logo.js and brand.js, with light and dark colors, and logo.py runs it after tracing the logo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jarl
A Jarl rules a Hold; this one rules your zones.
Jarl manages a BIND 9 (named) server: zones, records of every type, views, ACLs, TSIG keys and any named.conf option. It has a web UI, a REST API and a command line client. All three go through the same validated, audited, all-or-nothing change pipeline, and nothing reaches named before BIND's own checkers have accepted it.
$ jarl record add adbraun.lol nas A 192.168.1.20 --ptr
add nas.adbraun.lol. IN A 192.168.1.20
add 20.1.168.192.in-addr.arpa. IN PTR nas.adbraun.lol.
zone 1.168.192.in-addr.arpa serial 2026100700 -> 2026100701
zone adbraun.lol serial 2026100701 -> 2026100702
applied (changeset 12)
$ jarl record add adbraun.lol www A 192.168.1.99
error: www.adbraun.lol has a CNAME, so it can't have any other records (found A); a CNAME must be the only record at its name (RFC 1034) [zone adbraun.lol]
Features
- Everything in one place: primary, secondary, forward, stub, mirror, hint, redirect and in-view zones, reverse zones from a CIDR, split DNS with views, ACLs, TSIG keys, and every BIND 9.20 option at server, view, zone and top level, with the ARM's description and grammar built in.
- Safe changes. Every change is validated as a whole (CNAME rules,
glue, delegations, RRset TTLs, references, option syntax, open-resolver
and open-transfer lints), checked with
named-checkconf -z, installed atomically, reloaded, and verified withrndc zonestatus. If named doesn't load it, the previous files are put back. - Changesets: apply edits one at a time, or bundle them, review the diff, and apply them together. Every applied changeset can be reverted. Serials are bumped automatically.
- Users and API tokens with granular permissions, e.g. a token that may
only write
_acme-challengeTXT records in one zone. Tokens can be rotated (a new secret, same grants), with an optional grace period. Passwords use argon2id, sessions have CSRF protection, logins are rate limited. - Audit log of every login, change and token, with user, token, IP and outcome.
- No token needed on the server: root's
sudo jarl ...talks to jarld over a local unix socket, so a lost token or password never locks you out. - Query statistics (optional, off by default): queries over time, busiest hours of the week, top names and clients, NXDOMAIN and refused queries, record types, cache hit rate. Shown in the web UI and the CLI, and exportable as JSON. Stored as aggregates in a separate database with retention you set.
- Let's Encrypt DNS-01 built in:
jarl acmeplus certbot, acme.sh and lego hooks, an HTTPS-only endpoint for lego's and Traefik'shttpreqprovider, and CNAME delegation to a dedicated ACME zone. - Adopts an existing server: imports named.conf and zone files (comments included), then switches named over with an automatic fallback.
- One static Go binary for the daemon (web UI embedded, SQLite built in) and
one for the CLI, run by a sandboxed systemd service as an unprivileged
user.
make debbuilds a Debian package, and both come with bash completion.
Quick start
On a Debian/Ubuntu BIND 9.20 server (install.md has the details):
make deb && sudo apt install ./dist/jarl_*.deb # or: make && sudo make install, see install.md
sudo -u jarl jarld init # database + first administrator
sudo -u jarl jarld import # read the current named.conf and zones
sudo jarld adopt # switch named over (keeps a backup, rolls back on failure)
sudo systemctl enable --now jarl
sudo jarl status # root on the server needs no token (local socket)
Then open the web UI at http://127.0.0.1:8053/, or set listen and tls
in /etc/jarl/jarl.yaml to reach it from your management network.
Documentation
| docs/concepts.md | How Jarl works: source of truth, changesets, the apply pipeline, authentication. Read this first |
| docs/install.md | Installing, importing and adopting an existing server, TLS, upgrading |
| docs/configuration.md | /etc/jarl/jarl.yaml reference |
| docs/cli.md | The jarl command line client |
| docs/api.md | REST API reference |
| docs/permissions.md | Users, tokens, grants and roles |
| docs/views.md | Split DNS: adding views to a server that has none |
| docs/acme.md | Let's Encrypt DNS-01 with certbot, acme.sh, lego and Traefik |
| docs/stats.md | Query statistics: enabling, what is collected, retention |
| docs/validation.md | Every check Jarl makes |
| docs/operations.md | Backups, drift, failures, recovery, monitoring |
| docs/development.md | Code layout, tests against real BIND, conventions |
| CHANGELOG.md | Changes per release |
The web UI
The UI is built for getting things done rather than for looks. It has these pages:
- Dashboard: named status, adoption, drift, recent changes, open problems.
- Statistics (when enabled): queries over time, failed answers, a weekday x hour heatmap, top names and clients, and breakdowns. Every chart has a table view.
- Zones: list, create (choose a domain or a network, and the dialog shows the exact zone name it will create and warns about likely mistakes; fields adapt to the zone type), per-zone records with inline editing, type-specific hints and a preview of the exact record that will be added (reverse zones take addresses and suggest host names), settings (SOA, TTL, serial policy, view), options, the generated zone file, and pasting a zone file to merge or replace records.
- Views, ACLs, TSIG keys, Server options: wherever an address match list is entered, the existing ACLs and keys are offered and unknown names are flagged. The options editor knows every BIND 9.20 statement for the scope and shows its description, grammar and a value field that fits it.
- Changes: pending edits with the diff, named-checkconf output and problems, applied as one changeset. History lists applied changesets with their diffs and a revert button.
- Audit, Users, Account (password, API tokens) and Search.
By default, edits are collected as pending changes. Pending rows are shown inline (green = new, yellow = changed, struck out = deleted) until you review and apply them. Switch on "Apply edits immediately" to skip the review step.
Requirements
- BIND 9.20, with
named-checkconf,named-checkzoneandrndc. The option catalog follows 9.20. 9.18 will probably work, because named-checkconf still checks every change, but it is untested. - Linux with systemd for the packaged service. Defaults match Debian/Ubuntu, and other layouts are configurable.
- Go 1.27+ to build.
Status
Version 0.1.0, the first release. It is feature-complete for one server's configuration. DNSSEC signing and managing several servers are not handled yet (concepts.md).
License
Jarl is released under the MIT License.
Credits
The logo is part of Jarl: a bearded Viking jarl in blue and white, whose
crown holds a DNS tree (a root node with its branches). The master is
artwork/logo.png, which scripts/logo.py traces into the vector
assets/logo.svg and assets/logo.js. The "JARL" wordmark is drawn as SVG
paths in assets/brand.js, so the web UI needs no font files. The banner at
the top of this file, artwork/banner.svg, is built from both by
scripts/banner.py.