Skip to content
Projects

razvandimescu/numa has 1,979 GitHub stars and 141 forks. GitHot measured +1 stars in 24 hours and +9 in 7 days. Last checked 2026-11-09 10:18 UTC. Last push 2026-11-03.

How these counts are measured

Portable DNS resolver in Rust: .numa local domains, ad blocking, developer overrides, DoT/DoH/ODoH, DNSSEC

Measured star growth

in 24h
+1
in 7 days
+9
in 15 days
Collecting history
in 1 month
Collecting history

Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.

README.md

Original

Numa

CI crates.io License: MIT

DNS you own. Everywhere you go. · numa.rs

A portable DNS resolver in a single binary. Block ads on any network, name your local services (frontend.numa), override any hostname with auto-revert, and optionally seal every outbound query with ODoH (RFC 9230), so no single party sees both who you are and what you asked. It all runs on your laptop, with no cloud account or Raspberry Pi needed.

Built from scratch in Rust, with no DNS libraries. Numa can also resolve recursively with DNSSEC validation, serve DoT and DoH clients, and run as a public ODoH relay (numa relay).

The DNS wire-protocol parser is hand-written. Most later code was drafted with AI and reviewed by hand.

Numa dashboard

Quick Start

1. Try it in Docker (nothing installed on the host)

docker run -d --name numa -p 127.0.0.1:5553:53/udp -p 127.0.0.1:5553:53/tcp \
  -p 127.0.0.1:5380:5380 ghcr.io/razvandimescu/numa
dig @127.0.0.1 -p 5553 example.com
docker exec numa numa token            # API token for the dashboard login

Open http://localhost:5380 and log in with any username and that token. Port 5553 avoids whatever already holds port 53 on the host. numa.numa does not resolve here, because the host does not use Numa for DNS. Clean up with docker rm -f numa.

2. Run in the foreground (system DNS untouched)

Get the binary:

# macOS
brew install razvandimescu/tap/numa

# Linux
curl -fsSL https://raw.githubusercontent.com/razvandimescu/numa/main/install.sh | sh

# Arch Linux
pacman -S numa

# Windows: download from GitHub Releases
# All platforms
cargo install numa

# Nix
nix profile install github:razvandimescu/numa

On Linux, sudo does not search ~/.cargo/bin, ~/.nix-profile/bin or Linuxbrew. If numa is in one of these, use sudo "$(command -v numa)" wherever this page says sudo numa.

sudo numa                              # Ctrl-C to stop (port 53 requires root/admin)

Test it with dig @127.0.0.1 example.com (@127.0.0.2 on Windows). The dashboard is at http://localhost:5380. By default, other devices on your network can also query Numa. To limit this, set [server] allow_from to the networks you trust.

3. Set as system DNS

Platform Install Upgrade Uninstall
macOS sudo numa install update the binary, then sudo numa install sudo numa uninstall
Linux sudo numa install update the binary, then sudo "$(command -v numa)" install sudo numa uninstall
Windows numa install (admin) + reboot run the new numa.exe install (admin) numa uninstall (admin) + reboot

install registers a service, points system DNS at Numa and trusts its local CA. uninstall reverses all three. To check the install, open http://numa.numa. If the dashboard loads, Numa is your system DNS.

Update the binary the way you installed it (brew upgrade, cargo install numa, or rerun install.sh). Re-running install restarts the service on the new binary and keeps the token and CA.

On Linux, install reconfigures systemd-resolved with a drop-in file. uninstall removes it. Stop any other process that holds port 53 (dnsmasq, including NetworkManager's) before you install.

Logging in

Over loopback (localhost, 127.0.0.1, numa.numa), the dashboard and API need no login. From any other address, including Docker port mapping and this machine's LAN address, they ask for the API token. Numa creates the token on first start. Print it with sudo numa token (an administrator shell on Windows). Log in with any username and that token. To set your own token, use [server] api_token or NUMA_API_TOKEN.

Removing every trace

uninstall restores DNS but keeps the data directory, so a reinstall keeps the same token and CA. To remove everything, run uninstall first, then delete:

Platform Left behind
macOS the binary, /usr/local/var/numa, /usr/local/var/log/numa.log
Linux the binary and any copy at /usr/local/bin/numa, /var/lib/numa
Windows the binary, %PROGRAMDATA%\numa
iPhone the "Numa DNS" profile (Settings → General → VPN & Device Management)

Also delete ~/.config/numa if you created a user config. If you set [server] data_dir, delete that path too.

How It Compares

Pi-hole AdGuard Home Unbound Numa
Local service proxy + auto TLS No No No .numa domains, HTTPS, WebSocket
LAN service discovery No No No mDNS (numa lan on)
Overrides with auto-revert No No No REST API, scriptable
Recursive resolver No No Yes Yes
DNSSEC validation Yes Relies on upstream Yes Yes (recursive mode)
Ad blocking Yes Yes No Yes (Hagezi Pro default, custom lists)
Per-client rules Groups Yes Views / tags By CIDR ([[client_policy]]), config file only
Web admin UI Full Full No Dashboard
Encrypted upstream Needs cloudflared DoH, DoT, DoQ, DNSCrypt DoT DoH, DoT, ODoH
Encrypted clients (DoT listener) Needs stunnel sidecar Yes Yes Yes (RFC 7858)
DoH server endpoint No Yes Yes Yes (RFC 8484)
Parallel upstream queries dnsmasq all-servers Parallel mode No Request hedging (UDP, DoH, DoT)
Serve-stale + prefetch No Optimistic cache Yes (RFC 8767, prefetch) Yes (RFC 8767, prefetch)
Conditional forwarding Yes Yes Yes Yes (per-suffix rules)
Maturity Mature Mature Mature New

What Numa doesn't do. No DHCP, no clustering, no config sync between instances. Most settings live in the config file, not the dashboard.

Configuration

Settings live in one TOML file. numa config path prints which file your install uses. numa config edit opens it. After a change, run sudo numa service restart. numa -c FILE runs Numa, or these commands, on a file you choose. numa.toml documents every option inline.

On Windows, Numa listens on 127.0.0.2:53, because the DNS Client service (Dnscache) owns 127.0.0.1:53. Keep 127.0.0.2 when you edit bind_addr.

Local Services

Name your dev services instead of remembering port numbers:

curl -X POST localhost:5380/services -H 'Content-Type: application/json' \
  -d '{"name":"frontend","target_port":5173}'

Once Numa is your system DNS (numa install), https://frontend.numa works in your browser, with a valid certificate and WebSocket passthrough for HMR. No mkcert, no nginx, no /etc/hosts.

You can also add path-based routing (app.numa/api → :5001) or define services in numa.toml. Firefox keeps its own certificate store and ignores the system one. To use .numa HTTPS in Firefox, trust the Numa CA there manually.

Ad Blocking & Privacy

Numa blocks ads and trackers with Hagezi Pro, refreshed daily.

Resolution modes, set with [upstream] mode:

  • forward (default): forwards to your existing system DNS, with caching and ad blocking on top. Captive portals, VPNs and corporate DNS keep working.
  • recursive: resolves from the root servers. No single resolver sees all your queries, but the queries are not encrypted, so your network and ISP can still read them. To validate DNSSEC, run numa dnssec on.
  • auto: probes the root servers at startup and resolves recursively if they answer. If not, it forwards over DoH to Quad9 (https://9.9.9.9/dns-query), which then sees your queries. If Quad9 fails, Numa retries with Cloudflare (https://1.1.1.1/dns-query). To pick a different provider, use forward with your own [upstream].
  • odoh: seals each query for the target with HPKE and sends it through a relay (recipe). The relay sees your IP and ciphertext. The target sees the question and the relay's IP. A relay that redirects the query hands the new destination something it cannot decrypt. Numa refuses a relay and target that share a registrable domain. ODoH does not hide the connection you open afterwards: your ISP still sees the destination IP and, without ECH, the hostname in the TLS handshake.

DNS-over-TLS listener (RFC 7858). Numa accepts encrypted queries on port 853 from clients such as iOS Private DNS, systemd-resolved and stubby. Two certificate modes:

  • Self-signed (default): Numa creates a local CA. numa install adds it to the system trust store on macOS, Linux (Debian/Ubuntu, Fedora/RHEL/SUSE, Arch) and Windows. numa uninstall removes it.
  • Bring your own cert: point [dot] cert_path and key_path at a publicly trusted certificate, for example from Let's Encrypt through a DNS-01 challenge. Clients then connect without trust-store setup.

Phone setup. Point your iPhone at Numa. Set [mobile] enabled = true in numa.toml and restart Numa. With the phone on the same network, run:

numa setup-phone

It prints a QR code and the install steps. When you finish them, your phone sends DNS to Numa over TLS.

LAN Discovery

Run Numa on several machines. They find each other through mDNS:

Machine A (192.168.1.5)              Machine B (192.168.1.20)
┌──────────────────────┐             ┌──────────────────────┐
│ Numa                 │    mDNS     │ Numa                 │
│  - api (port 8000)   │◄───────────►│  - grafana (3000)    │
│  - frontend (5173)   │  discovery  │                      │
└──────────────────────┘             └──────────────────────┘

From Machine B, curl http://api.numa reaches port 8000 on Machine A. Turn this on with numa lan on.

Network-wide. Point other devices' DNS at one Numa instance. They get ad blocking and .numa names without installing anything. Setup: network-wide recipe.

Docker

# Host networking (Linux, recommended)
docker run -d --name numa --network host -v numa-data:/var/lib/numa ghcr.io/razvandimescu/numa

# Port mapping (Docker Desktop on macOS and Windows)
docker run -d --name numa -p 53:53/udp -p 53:53/tcp -p 5380:5380 -v numa-data:/var/lib/numa ghcr.io/razvandimescu/numa

The dashboard is at http://localhost:5380. With port mapping, or from another device, log in with any username and the token from docker exec numa numa token. The numa-data volume keeps the token and the local CA when you recreate the container. The image binds the API and proxy to 0.0.0.0 by default. To use your own config, mount it:

docker run -d --name numa --network host -v numa-data:/var/lib/numa \
  -v /path/to/numa.toml:/root/.config/numa/numa.toml \
  ghcr.io/razvandimescu/numa

Images: linux/amd64 and linux/arm64.

Compose recipes:

Running Numa as Your Primary DNS

If Numa stops. launchd (macOS) and systemd (Linux) restart Numa when it exits. Until it restarts, DNS lookups fail if Numa is your only resolver.

If Numa runs but cannot reach its upstreams, it serves cached answers for up to an hour past their TTL (RFC 8767). Names it has not cached fail.

Privileges. The systemd unit runs unprivileged (DynamicUser=yes, only CAP_NET_BIND_SERVICE). The launchd daemon runs as root.

Testing. CI fuzzes the DNS parser on every pull request that touches it. Longer fuzzing runs weekly (fuzz.yml). CI also runs cargo audit on dependencies. It installs, reinstalls and uninstalls Numa on macOS and Linux.

Resolver hardening. In recursive mode, Numa drops answer records outside the zone being queried. It does not query nameservers at private or loopback addresses. It caps the upstream queries and referral depth of each lookup. Replies over plain UDP must match the query's random transaction ID and question. Numa refuses ANY queries. To report a vulnerability privately, see SECURITY.md.

Performance

Cached queries take 0.1 ms, the same as Unbound and AdGuard Home. Request hedging reduces tail latency. In the April 2026 cold recursive benchmark, p99 was 538 ms for Numa and 748 ms for Unbound. Benchmarks →

Learn More

License

MIT

Explore related projects