Install with Nix
Add nxdns to a NixOS machine as a flake input pinned to a release, and run it with the flake’s services.nxdns module. At the end the machine answers DNS on port 53 from a configuration file that was checked when the system was built.
The flake does not build nxdns from source. Its package fetches the release tarball for x86_64-linux or aarch64-linux and pins its SHA-256, so a changed byte fails the build.
1. Add the input
In your flake:
inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nxdns = { url = "git+https://git.mial.net/mokhtar/nxdns.git?ref=refs/tags/v0.0.25"; inputs.nixpkgs.follows = "nixpkgs"; };};Pin a tag, not a branch: a tag never moves under you. The follows line saves fetching and evaluating a second nixpkgs. Then write the lock entry:
nix flake lock2. Enable the service
Write the configuration next to your NixOS configuration, for example as nxdns-config.zon. The smallest one that starts is a group named default and one upstream resolver:
.{ .groups = .{ .{ .name = "default" } }, .upstreams = .{ .{ .url = "https://cloudflare-dns.com/dns-query" } },}Import the module and enable it:
{ inputs, ... }:{ imports = [ inputs.nxdns.nixosModules.default ];
services.nxdns = { enable = true; configFile = ./nxdns-config.zon; openFirewall = true; };}Then run nixos-rebuild switch.
| Option | Default | What it does |
|---|---|---|
enable | false | Runs nxdns as nxdns.service, as the nxdns system user. |
package | the release of the pinned tag | The nxdns release to run. |
configFile | none, required | The configuration, installed as /etc/nxdns/config.zon. |
openFirewall | false | Opens port 53 over UDP and TCP. The web interface (8080) and the DoH and DoT ports stay closed; open them yourself if other machines should reach them. |
configFile is copied into the Nix store, which every local user can read. Do not put a plaintext web.password in it. Use the web.password_hash from an export instead, as Set up admin authentication shows.
What the module does:
- The build checks the configuration. The pinned nxdns loads
configFileinto a throwaway database during the build, so a file nxdns would reject failsnixos-rebuildwith nxdns’s own message, for exampleFAIL rules[0].pattern: '(' is not a valid regex pattern, and nothing is deployed. The build has no network, so it cannot check that the upstreams answer or load DoH and DoT certificates; a missing certificate still stops the service when it starts. - nxdns runs in file mode. Every start applies the file, and the web interface shows the configuration read-only. To change anything, change the file and switch. A switch that changes only the configuration restarts the service.
- The unit is the packaged one, with its sandboxing. The module only replaces its command line;
systemctl cat nxdns.serviceshows both.
The module does not touch the machine’s own resolver. Point your network at nxdns by setting the DNS server your router hands out to this machine’s address, and leave networking.nameservers on another resolver, so the machine can still resolve names while nxdns is down.
The package alone is inputs.nxdns.packages.${pkgs.stdenv.hostPlatform.system}.default, for running nxdns some other way. It installs bin/nxdns and lib/systemd/system/nxdns.service, and sets meta.mainProgram.
3. Upgrade
Read the release notes, change the tag in the input URL, then:
nix flake lock --update-input nxdnsnixos-rebuild switchRolling back to an older generation puts an older nxdns in front of a database the newer one may already have upgraded. The older binary then refuses to start; Upgrade explains the way back.
Let Renovate bump the tag
Renovate’s built-in nix manager cannot do this: it only moves flake.lock along the ref an input already follows, and a tag ref never moves. A regex custom manager that reads the tag from the input URL, with the gitea-tags datasource, does:
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"], "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/^flake\\.nix$/"], "matchStrings": ["git\\+https://git\\.mial\\.net/mokhtar/nxdns\\.git\\?ref=refs/tags/(?<currentValue>v\\d+\\.\\d+\\.\\d+)"], "depNameTemplate": "mokhtar/nxdns", "datasourceTemplate": "gitea-tags", "registryUrlTemplate": "https://git.mial.net", "versioningTemplate": "semver" } ]}Its pull request changes the tag in flake.nix only. The same pull request also needs a refreshed flake.lock: run nix flake update nxdns as a postUpgradeTasks command (self-hosted Renovate, with allowedCommands), or have CI commit the lock on the Renovate branch.
A tag can exist for a short while before its release files are uploaded. A pin made by hand in that window fails the build with a 404 from the download URL; build again once the release is published.