• v0.0.25
    Gates / container (push) Successful in 10s
    CI / gates (push) Successful in 23m56s
    Release / guard (push) Successful in 1m47s
    Gates / frontend (push) Successful in 2m14s
    Gates / test (push) Successful in 2m40s
    Gates / test-aarch64 (push) Successful in 5m39s
    Gates / package (push) Successful in 47s
    Gates / container (push) Successful in 11s
    Release / gates (push) Successful in 9m20s
    Gates / frontend (push) Successful in 2m12s
    Release / publish (push) Successful in 10m46s
    Gates / test (push) Successful in 2m32s
    Gates / test-aarch64 (push) Successful in 6m35s
    Gates / package (push) Successful in 4m31s
    Stable

    mokhtar released this 2026-10-22 19:34:55 +00:00 | 0 commits to master since this release

    Fixed

    • The DNS cache's memory is bounded in bytes, not only in entries. Each cached answer is copied at whatever size the upstream sent, so 10000 entries could in principle hold 10000 large answers. The answers now share a budget of 256 bytes per cache.size entry, about 2.4 MiB at the default, and when a new answer does not fit, answers nobody has asked for lately make room for it. An answer larger than the whole budget is not cached. /metrics reports nxdns_cache_response_bytes against nxdns_cache_max_bytes, and counts nxdns_cache_oversized_total and nxdns_cache_copy_failures_total.
    • The DNS cache no longer evicts an answer while it has free room. A new answer replaced an older one whenever the eviction scan met it first, even with most of the cache empty. A copy of an answer that failed for lack of memory was also dropped without a trace; it is now counted, and a failed replacement keeps the answer already cached.
    • A refused query history says what to do. When nxdns refuses to open querylog.db, because a newer release wrote it or it cannot be converted, the message now gives the remedy: run the newer release again, put back the querylog.db.pre-migrate-* copy, or move the file and its -wal and -shm files aside to start an empty history. It used to point at a documentation page.
    • A refused start no longer gives advice that does not apply. After a query history refusal, nxdns run also printed "run nxdns check" and "load one with nxdns import <file>", though the configuration was fine. The import hint now appears only when the database has no usable upstream, and neither hint follows a database refusal.

    Changed

    • Blocklists take less memory, and a refresh peaks lower. A list that names a domain both on its own and with its subdomains, as Adblock-style lists do for every entry, now stores that name once instead of twice. A reload reads one list's compiled files at a time instead of all of them at once, and the startup pass no longer rebuilds the blocklists when no list was due for a refresh.
    • The query-history buffer defaults to 1024 queries instead of 10000, under 2 MiB instead of about 17. It only holds queries that arrive while a write is in progress.
    • A live query stream takes memory only while someone watches it. Each of the 32 possible streams reserved its buffer at startup, about 3.6 MiB in all. A stream that cannot get its buffer is refused with its own message and counted in nxdns_live_stream_allocation_failures_total.
    • All DNS listeners share one budget of 64 queries at a time, about 4.6 MiB, where each UDP and TCP listener used to reserve room for 64 of its own, about 27 MiB across the four. The Pi's busiest moment over a day was 27 queries at once. A query beyond the budget waits for a free one instead of failing: over UDP it waits in the system's socket buffer, over TCP, DoT and DoH for at most the connection's idle time. /metrics counts the waits as nxdns_dns_query_permit_waits_total, their time as nxdns_dns_query_permit_wait_seconds_total and stream waits that ran out as nxdns_dns_query_permit_wait_timeouts_total, shows nxdns_dns_query_permits_waiting, and reports what the system holds and drops for the UDP/53 sockets as nxdns_udp_server_receive_queue_bytes and nxdns_udp_server_kernel_drops_total. TCP, DoT and DoH each accept 16 connections at a time instead of 64, and the DoT and DoH listeners now report rejected_at_capacity_total too.
    • A query waiting for its turn at an upstream holds one thread instead of three, so a burst grows the server's thread pool less, and transparent huge pages are switched off for the process, so a thread's stack no longer holds 2 MiB on systems that enable them for everything. Work that could not get a thread used to fail as a generic error or not be counted at all; /metrics now counts it as nxdns_concurrency_unavailable_total, by where it happened.
    • Each database connection keeps a page cache sized for its job instead of SQLite's 2 MiB default on all seven: 256 KiB for the configuration database and for retention, 1 MiB for the query-history writer and reader.
    • The documentation is written for people running nxdns. It starts from Get started, which downloads a release instead of building from source, and drops the API reference, the troubleshooting page and the performance pages. It is published at https://nxdns.sh/docs/, and INSTALL.md in the tarball links there.

    Removed

    • nxdns_udp_server_dropped_no_slot_total, because a UDP query is no longer dropped for lack of room: it waits. A query still waiting when the listener shuts down is counted in the new nxdns_udp_server_dropped_at_shutdown_total.

    Artifacts

    b49d3f980d198627344aa621c1aacd28b94ad2ce5005013337fe764141b162fd  nxdns-0.0.25-x86_64-linux-musl.tar.gz
    ccada2e757115fee6afbdac845abf89ba36155924963083d3898f45acb4a77f3  nxdns-0.0.25-aarch64-linux-musl.tar.gz
    8cc3f364a797022e86e78e3877de851f8110cd087244790026bddbd806d1e6b6  IMAGE-DIGEST.txt
    
    git.mial.net/mokhtar/nxdns:0.0.25@sha256:c225e4604ad3edaade1346060679659ed081ccd3cfee50e93b5b1026d2d60035
    

    Compare v0.0.24...v0.0.25

    Commits since v0.0.24
    27e1e49 platform: the huge pages test skips where the kernel or emulator has no such switch
    98283e1 ci: install the pinned node from its tarball instead of trusting the runner image
    f834418 build: bump version to 0.0.25
    90b022f changelog: date the 0.0.25 section
    91ed5ae server: a query that finds every permit taken waits for one instead of failing, the budget is 64, permit and upstream turns wait on a futex with one thread each, the thread pool has no task limit, and transparent huge pages are off for the process
    63cc5f8 server: every dns listener shares one budget of 32 queries, stream listeners take 16 connections, the task pool is capped from those bounds, and work it cannot start is counted by site
    e4e405f cache: a byte budget of 256 bytes per entry bounds the cached answers, evicting by clock, and a full or failed copy is counted instead of swallowed
    d951d03 querylog: the writer queue defaults to 1024 entries, and a live stream allocates its ring on subscribe instead of at boot
    0cafd77 filter: a reload builds the snapshot one source at a time, and the startup pass reloads only when a source changed or was not enforced
    c2eac8d storage: each runtime database connection keeps a page cache sized for its role, read back at open
    8f27945 ci: time limits on the test, test-aarch64, package and container jobs
    5a51e2d tests: a failed server check fails the test instead of hanging it, and the tls idle tests leave the handshake room
    4916b01 filter: store each blocklist name once, flagged exact and subdomains, instead of in two sets per source
    2a06239 ci: use the runner image's node instead of setup-node, which downloaded it again on every run
    7e06642 ci: build nxdns.sh and deploy it as a cloudflare worker with static assets
    93e2683 website: nxdns.sh on astro and starlight, with the docs and 0.0.24 screenshots
    c21e5a9 docs: rewrite for people running nxdns; query-history refusals name their remedy and a refused start gives no wrong hints
    fbc8fd7 specs: memory budget drops the removed performance page and keeps stack_size, a thp artifact explained the 2 mib stacks
    
    Downloads