Files
jarl/docs/validation.md
T
adamandClaude Opus 5.5 66dcf53684 DNS over TLS and HTTPS set up in one step
jarl encrypted-dns setup (and the Encrypted DNS page) finds or obtains a
certificate for the host name, checks that nothing else listens on the
DoT and DoH ports at the chosen addresses (offering a free DoH port), and
stages named's tls statement and listeners as one change, keeping port 53
where it was when there was no listen-on, plus the host name's records in
the private views (local to the view in a linked zone). Status shows what
to give Android, Linux, Firefox, Chrome and Windows clients. A tls
statement with missing files is refused (tls_file_missing), and a
certificate named uses can't be deleted. docs/encrypted-dns.md, e2e with
real DoT and DoH queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:09:14 +01:00

14 KiB

Validation

Every change is checked twice before named sees it:

  1. Jarl's validator checks the whole resulting configuration: the DNS rules, references between objects, and option syntax against the BIND 9.20 grammar. Its messages name the record, zone or option and explain the rule.
  2. BIND's own checkers: named-checkconf -z on the complete rendered configuration (which also loads every zone) and named-checkzone. Their messages are mapped back to the object that caused them.

Problems have a severity:

  • error blocks the change (HTTP 422 validation_failed, CLI exit code 6).
  • warning is shown but allowed: probably a mistake, or a security risk.
  • info is a hint.

Only problems that a change introduces block it. Problems that already existed are reported separately (existing_problems) and don't stop unrelated changes, though jarl status, jarl check and the dashboard keep showing them.

Records and zones

Code Severity Rule
record_invalid error Record data must parse for its type (addresses, numbers, names, base64/hex fields, ...). TXT/SPF strings are split into 255-byte pieces automatically
record_name_invalid error Owner names must be valid. Host names in A/AAAA/MX owners follow check-names rules (letters, digits, hyphens; _ only where it is normal, e.g. SRV/TXT owners)
record_out_of_zone error The name must be inside the zone (catches www.example.com without the final dot)
record_duplicate error The same record can't exist twice
cname_conflict error A CNAME must be the only record at its name (RFC 1034), so no CNAME next to A, MX, TXT...
disabled_conflict warning A disabled record that couldn't be enabled again as the zone is now (a CNAME now shares its name)
cname_multiple error A name can have only one CNAME
cname_apex error No CNAME at the zone apex (it needs SOA and NS)
cname_loop error CNAMEs that point back to themselves
cname_chain warning CNAME to a CNAME: works, but slows resolution
cname_dangling warning CNAME to a name that doesn't exist in the zone
dname_multiple, below_dname error One DNAME per name, and nothing below a DNAME (RFC 6672)
apex_ns_missing error A zone needs NS records at its apex
ns_no_address error An in-zone name server needs an A or AAAA record (named refuses to load the zone otherwise)
ns_cname, mx_cname, srv_cname error NS, MX and SRV targets must not be CNAMEs (RFC 2181 / RFC 2782)
glue_missing error A delegation to a name server inside the delegated zone needs glue records
target_no_address warning A delegation's name server has no address in the zone
occluded warning Records at or below a delegation point are not served (named answers with a referral)
shadowed_by_zone warning Records hidden because a more specific zone exists in the same view
child_not_delegated info A child zone on this server isn't delegated from its parent, so other resolvers can't find it
ds_not_at_delegation error DS records belong at a delegation point
null_mx_not_alone error A null MX (0 .) must be the only MX
rrset_ttl_mismatch error All records of an RRset need the same TTL (BIND would silently use the first one)
rrset_too_large, too_many_types error BIND 9.20's limits: 100 records per RRset (max-records-per-type), 100 types per name (max-types-per-name)
spf_multiple, dmarc_multiple, dmarc_name warning More than one SPF or DMARC policy (receivers then ignore them), or DMARC outside _dmarc.
wildcard_unusual warning Wildcard NS records
soa_invalid, soa_warning, ttl_invalid error / warning SOA fields and TTLs in range and sensible. One warning covers negative caching longer than 3 hours: resolvers cache "no such name" answers for the smaller of the SOA minimum and the zone's default TTL (RFC 2308), so a new name can stay invisible that long. 1 hour (--minimum 3600) is a good value
zone_name_invalid, zone_type_invalid, zone_duplicate error Valid zone names and types. A zone exists once per view
zone_outside_view error Once views exist, every zone must be in one (named requires this). Create the first view with --adopt-zones to move all zones into it (views.md)
zone_missing_option error Secondary, stub and mirror zones need primaries
zone_file_required / zone_file_invalid / zone_file_managed error Hint zones need a file. Forward zones have none. Primary zone files are Jarl's
zone_inview_invalid, zone_inview_option error in-view zones only inside views, and only with forward/forwarders
zone_inview_disabled error An in-view zone can't share a disabled zone
zone_disabled_signed error A signed zone can't be disabled: turn signing off first
zone_records_unmanaged error Only primary zones can have records in Jarl
zone_serial_policy error date, increment or unixtime
zone_no_forwarders, zone_hint_name, zone_name_slash info / warning Unusual but legal setups

ACLs, keys, views, options

Code Severity Rule
acl_element_invalid error Address match elements: addresses, prefixes (host bits must be zero, like BIND requires), ranges (10.0.10.101-10.0.10.199: both ends full addresses of one family, start not after end), !negation, key NAME, ACL names, { nested lists }, any/none/localhost/localnets. Ranges in options are checked when they're entered, and only options with address match lists take them (not forwarders, say)
range_mapped4 warning An IPv6 range that reaches into ::ffff:0:0/96: named can see IPv4 clients as such addresses, so it may match IPv4 clients too
acl_ref_missing, key_ref_missing error Every ACL or TSIG key that is referenced must exist
acl_cycle error ACLs that include each other
acl_duplicate, acl_name_invalid, acl_empty, acl_shadowed error / warning Unique names, built-in names reserved, names that look like addresses or ranges, elements that can never match (a prefix or range an earlier one already covers)
key_* error / warning Valid key names, a supported algorithm, a base64 secret of a sensible length. hmac-md5/sha1 are flagged as weak. Names starting with jarl-view- are reserved for the view keys (key_name_reserved)
view_duplicate, view_name_invalid, view_acl_invalid error Valid, unique views and match lists
view_unreachable warning A view that comes after one matching every client is never used
option_unknown, option_scope_invalid error The option must exist in BIND 9.20 and be allowed where it is set (server, view, zone of this type, top level)
option_syntax, option_value_invalid error The value must parse (balanced braces, strings, the grammar's value type: yes/no, enums, numbers, durations, sizes, address match lists)
option_duplicate error Options that may appear once, appear once
option_managed error Statements Jarl manages itself can't be raw options: zone, view, acl, key, options, a zone's type/file/in-view/key-directory, a view's match-*, and dnssec-policy in zones, views and server options (set per zone instead; top-level dnssec-policy NAME { } definitions are options)
option_deprecated, option_obsolete, option_unavailable warning Flags from the BIND 9.20 reference
option_experimental info
settings_invalid error Defaults for new zones are valid

DNSSEC

These rules come from dnssec.md. Those that compare the change with the current state are checked when it is applied.

Code Severity Rule
dnssec_zone_type error Only primary zones are signed by Jarl
dnssec_policy_unknown error The zone's policy must be default, insecure or a top-level dnssec-policy statement
dnssec_policy_no_keys error A custom policy with an empty keys { } list (named 9.20 crashes loading it)
dnssec_ttl_too_long error TTLs (and the default TTL) of a signed zone must be at most the policy's max-zone-ttl (a day for the built-in policies); named refuses to load the zone otherwise
dnssec_inline_signing error No inline-signing no on a signed zone
dnssec_off_unsafe error A signed zone can't go straight to unsigned: use the insecure policy first (or wipe, when the parent has no DS)
dnssec_off_too_early, dnssec_off_unknown error Switching off after insecure waits until named is done removing the signatures
dnssec_delete_signed error A signed zone can't be deleted; turn signing off first
dnssec_keys_moved error A signed zone can't move to another view (its keys would change)
dnssec_policy_change warning Changing the policy may replace the keys, and with them the DS at the parent
dnssec_old_keys info Signing a zone whose key directory still has old keys
serial_not_higher error An explicit serial on a signed zone must be above the one named has loaded (automatic serials are raised by themselves)

TLS

Code Severity Check
tls_file_missing error The files a top-level tls statement names (certificate, key, CA) exist: named doesn't load its configuration without them

Linked zones

See views.md. Jarl keeps linked zones in step itself, so these only show when something got past it (a change staged before the zones were linked, or the database edited by hand).

Code Severity Check
linked_zone_type error Only primary zones can be linked
linked_zone_name error Linked zones have the same name
linked_mixed error A name and type in a zone is either shared or local, not both
linked_out_of_sync error Every view that doesn't override a shared name and type has the same records for it. Link the zones again (jarl zone link) to work out what is shared
linked_zone_alone info A zone linked with no other zone

Blocking

From blocking.md.

Code Severity Rule
blocking_rule_duplicate error One rule per domain and view
blocking_response_policy error No response-policy option where Jarl's blocking writes that statement (the view, or the server)
zone_name_reserved error Zone names under jarl.invalid are reserved for blocking's policy zones
blocking_view_missing error A list or rule names a view that doesn't exist
blocking_no_view warning A list or rule applies to no view: none offers recursion, and blocking only changes recursive answers
blocking_no_recursion warning A list or rule names a view that doesn't offer recursion, where it has no effect
blocking_step_missing error A list or client group is at a step that doesn't exist (a list can't be at off)
client_group_member_invalid error Client group members are addresses, networks, or ACLs made only of those
client_group_overlap error The same address or network is in two client groups of a view
client_group_empty warning A client group has no members
blocklist_step_unused warning A list is at a step that no client group reaches, so it blocks nothing

Security lints

These and the exposure checks below only look at views that are reachable from the internet (exposure.md); without views, the open resolver and open transfer checks look at the server. Any of them can be acknowledged (it then shows as info, with who accepted it and why), and a change that newly introduces an open resolver, open transfers, cache snooping, open NOTIFY or an open management interface on a public view must be confirmed (--accept-risk).

Code Severity What it catches
security_open_resolver warning A view (or the server) where recursion is on and reachable by any client. This includes the BIND trap where allow-recursion is unset and inherits allow-query { any; }. Open resolvers are abused for DDoS amplification. Set allow-recursion to your own networks
security_open_transfer warning allow-transfer open to anyone. Restrict it to your secondaries or a TSIG key
dynamic_update_unsupported error allow-update / update-policy on a Jarl zone. named would write to files that Jarl regenerates, losing the updates. Use the API (e.g. jarl acme) instead
exposure_rate_limit warning A public view without response rate limiting (rate-limit)
exposure_rate_limit_log_only info Rate limiting in log-only mode: check what it would limit, then enforce
exposure_minimal_any warning A public view answering ANY queries in full (minimal-any off)
exposure_cache_snooping warning A public view without recursion whose cache anyone may query (allow-query-cache)
exposure_open_notify warning A public view accepting NOTIFY from anyone (allow-notify)
exposure_version, exposure_hostname warning The server is public and shows its BIND version or host name to anyone (version none, hostname none)
exposure_management warning controls or statistics-channels on an address that isn't loopback, open to anyone, while the server is public

What only BIND checks

named-checkconf and named-checkzone run on every change, so anything Jarl's validator doesn't model is still caught before named loads it. Option values Jarl accepts as raw named.conf syntax, such as logging channels and rrset-order, are the main example. Their errors come back as validation_failed (HTTP 422) with the checker's messages in check, each tied to the object it belongs to.

named-checkconf's warnings don't block a change. They are shown with the change, and the dashboard's configuration checks list them for as long as they apply (named_warning; named_error if the running configuration no longer passes, after a hand edit say). Jarl runs named-checkconf on the running configuration for this, without loading zones, when the dashboard asks and the configuration changed, or at most every 10 minutes.