# Numa: a portable DNS resolver with .numa local domains and ad blocking

> Numa is a single-binary DNS resolver written in Rust that serves .numa service names, blocks ads through Hagezi Pro lists, and can run as an ODoH client or relay. This review covers what it does, how to install it, and where it falls short.

**razvandimescu/numa** — Portable DNS resolver in Rust — .numa local domains, ad blocking, developer overrides

- Repository: https://github.com/razvandimescu/numa
- Stars: 1,522 · Forks: 107
- Language: Rust
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/razvandimescu-numa

## What Numa solves for developers who keep typing port numbers

Two problems get tangled together on a developer laptop. The first is that local services are addressed by port, so you memorize 5173 for the frontend and 8000 for the API, and those numbers change when a port is taken. The second is that ad and tracker blocking usually means running a separate appliance: a Raspberry Pi with Pi-hole, or a container that only protects one network. Numa targets both from one process. The README describes it as a portable DNS resolver in a single binary, and the repository ships a Cargo.toml with a name of numa and a version of 0.23.0 under the MIT license.

The intended user is a developer who moves between networks. The README states that blocking works on any network, including coffee shops, hotels and airports, because the resolver travels with the laptop rather than sitting on the LAN. Local service naming is the second half: instead of editing /etc/hosts, you register a name and a target port, and the resolver plus proxy handles the rest. That combination is the reason to look at this project rather than a plain blocklist resolver.

## How resolution, ad blocking and the local proxy fit together

The Cargo.toml shows the shape of the system. Tokio provides the async runtime, axum serves the HTTP API and dashboard, rustls and tokio-rustls handle TLS, and odoh-rs implements Oblivious DNS over HTTPS. There is no DNS library in the dependency list, which matches the README's claim that the resolver is built from scratch in Rust. The practical consequence is that packet parsing, caching and DNSSEC validation are project code rather than a dependency you can swap out.

Three resolution modes are documented. The default, forward, proxies to whatever DNS the system already uses, so captive portals, VPNs and corporate resolvers keep working while caching and blocking sit on top. The recursive mode resolves from root nameservers directly and can enable DNSSEC chain validation through a [dnssec] section with enabled = true. The auto mode probes root servers at startup and falls back to encrypted DoH if they are unreachable. Ad and tracker blocking uses the Hagezi Pro list, refreshed daily according to the README.

The local service path is separate from resolution. A service registered through the API gets a name under .numa, and the proxy terminates TLS for it. The README claims a valid certificate and WebSocket passthrough for hot module reloading, which matters because a dev server that cannot do WebSockets is not usable for most frontend work. On the encrypted-client side, a DNS-over-TLS listener on port 853 accepts queries from strict clients, with ALPN "dot" advertised and enforced. The README states that a handshake with mismatched ALPN is rejected as a cross-protocol confusion defense, which is a deliberate hardening choice rather than a default inherited from a TLS library.

## Installing Numa and registering your first local service

The README lists several install paths. On macOS there is a Homebrew tap, on Linux an install script, on Arch a package, on Windows a download from GitHub Releases, and cargo install numa works everywhere. Docker and Nix are also listed. Pick one and run it; the commands below are copied from the README.

```bash
brew install razvandimescu/tap/numa
```

For Linux, the documented one-liner pipes a script from the repository into a shell:

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

Running the resolver on the standard DNS port needs elevated privileges, which the README states plainly:

```bash
sudo numa
```

With the process running, the dashboard is reachable at http://numa.numa or http://localhost:5380. The first real use is registering a service. The README gives this example, which posts a name and a target port to the local API:

```bash
curl -X POST localhost:5380/services \
  -d '{"name":"frontend","target_port":5173}'
```

After that, the README says https://frontend.numa resolves in the browser with a valid certificate. To make the machine use Numa as its system resolver, macOS and Linux take sudo numa install and sudo numa uninstall; Windows uses numa install from an administrator shell followed by a reboot, and numa uninstall to reverse it. The README notes that on Windows the built-in Dnscache service owns 127.0.0.1:53, so Numa binds 127.0.0.2:53 and installs an NRPT rule, which means bind_addr and api_bind_addr must be edited against 127.0.0.2 rather than 127.0.0.1.

## Certificate handling is where the setup cost actually lives

The default DoT mode generates a local CA, and numa install adds it to the system trust store on macOS, Linux and Windows. That is convenient until a client keeps its own trust store. The README calls out Firefox specifically: it uses its own NSS store and ignores the system one, so the CA has to be trusted there manually if you want HTTPS for .numa services in that browser. This is a real friction point, not a footnote, because the promise of a green lock depends on trust-store work that varies per client.

The alternative is to bring your own certificate by pointing [dot] cert_path and key_path at a publicly trusted certificate, for example one issued through a DNS-01 challenge. The README says clients then connect without any trust-store setup. That path removes the local CA problem but requires a domain pointing at the Numa instance and a certificate lifecycle you now own. Phone setup follows the same pattern: numa setup-phone prints a QR code, you install the profile and toggle certificate trust, and the README states this requires [mobile] enabled = true in numa.toml. If that key is not set, the feature is off, and the README does not describe what error the command produces in that case.

## Where Numa is the wrong choice

Running a resolver on port 53 requires root or administrator privileges, and the README's own instructions use sudo. If you cannot grant that on the host, the system DNS integration path is closed to you; the Docker route still needs port 53 bound, either through host networking on Linux or explicit UDP and TCP port mapping on Docker Desktop for macOS and Windows. In container terms the image exposes 53/udp, 80/tcp, 443/tcp, 853/tcp and 5380/tcp.

Windows is the roughest platform. The 127.0.0.2 binding and NRPT rule mean configuration examples written for 127.0.0.1 do not apply, and the install requires a reboot. Anyone copying a bind_addr snippet from a Linux guide will get a resolver that does not answer.

There is also a scope question. Numa is a resolver and a local proxy, not a full network-wide filtering appliance with per-client policy, group management and scheduled rules. The README's hub mode, where one instance binds 0.0.0.0:53 and other devices point at it, gets you network-wide blocking, but the comparison table in the README positions Numa against Pi-hole, AdGuard Home and Unbound on the axes of local service proxying and LAN discovery rather than on policy features. If your requirement is per-device filtering rules, this is not the tool the README describes.

Finally, the ODoH relay story is framed as ecosystem building. The README states that the curated DNSCrypt list currently has one surviving relay and that every Numa deploy expands the ecosystem. That is honest, but it also means the anonymity properties of ODoH depend on relay diversity that does not exist yet at scale.

## Numa against Pi-hole, AdGuard Home and Unbound

The README's own comparison table puts Numa against three established projects and marks the differences: local service proxy with automatic TLS, LAN service discovery, and developer overrides are listed as absent in Pi-hole, AdGuard Home and Unbound, and present in Numa. Taken at face value, the distinction is that the others are DNS filtering or resolution servers, while Numa also terminates HTTPS for names it invents.

The practical difference is what you install. Pi-hole and AdGuard Home are typically deployed as network appliances with a web UI and a long history of blocklist management; Unbound is a validating recursive resolver with a configuration file and no blocking story of its own. Numa folds a forwarder, an optional recursive resolver with DNSSEC, a blocker, a reverse proxy with certificates, and an mDNS discovery layer into one binary. That reduces the number of moving parts, and it also means a bug in any one of those layers is a bug in the process answering your DNS.

The repository supports that reading. The Makefile defines targets for cargo clippy with -D warnings, cargo audit, cargo tarpaulin coverage and cargo fuzz with a pinned nightly toolchain, and the tree contains fuzz/, benches/ and tests/ directories. A project that ships a fuzzing harness for packet parsing is treating the parser as attack surface. The README does not publish benchmark numbers, so there is no basis here for a performance comparison against Unbound or AdGuard Home.

## Licence, maintenance and what upgrading involves

Numa is MIT licensed, stated in the README badge and in the Cargo.toml license field. MIT is permissive: you can use, modify and redistribute it, including in closed products, provided the copyright notice and licence text are preserved. That is a summary of the licence's usual terms, not legal advice; read the LICENSE file in the repository for the operative text. Because the project embeds a bundled Mozilla root store through webpki-root-certs, and generates certificates with rcgen, there is no separate CA bundle or certificate tooling licence to reconcile.

The last push to the default branch was on 2026-09-09, and the most recent release listed is v0.23.0 from 2026-08-20, following v0.22.0 in July and v0.21.0 in June. The release cadence in that window is roughly monthly. The repository is not archived. That is the extent of what the README and repository layout support; there is no published compatibility policy, so the upgrade cost is whatever the release notes for each version say, and those are not included here.

Operationally, upgrades are cheap to install and expensive to get wrong. The binary is self-contained, the Docker image is multi-arch for linux/amd64 and linux/arm64, and the config lives in numa.toml, which the Dockerfile places at /root/.config/numa/numa.toml. The README does not document rollback, so if a new version changes behaviour you should keep the previous binary or image tag before replacing it.

## Conclusion

Adopt Numa if you want ad blocking plus HTTPS local service names from one binary and you are comfortable running a resolver on port 53 with elevated privileges. Skip it if you need a long-established project with a large plugin ecosystem, or if you cannot grant root on the host. Before installing, verify that your platform's install path exists in the README table, that you understand the Windows 127.0.0.2 binding, and that the DNSSEC and DoT options you intend to enable are documented in numa.toml.

## FAQ

### What is Numa and what does it do?

Numa is a portable DNS resolver written in Rust and shipped as a single binary. It blocks ads and trackers using the Hagezi Pro list, resolves names under .numa for local services, and can act as an ODoH client or relay.

### How do I install Numa on macOS or Linux?

On macOS the README gives brew install razvandimescu/tap/numa. On Linux it gives a curl command that pipes install.sh from the repository into sh, and cargo install numa works on all platforms.

### Does Numa need to run as root?

The README's quick start uses sudo numa because binding port 53 requires root or administrator privileges. The Docker path avoids sudo on the host but still needs port 53 bound, either through host networking or explicit port mapping.

### How is Numa different from Pi-hole or AdGuard Home?

The README's comparison table lists local service proxying with automatic TLS, LAN service discovery and developer overrides as features of Numa that it marks as absent in Pi-hole, AdGuard Home and Unbound. Numa also ships as one binary rather than an appliance image plus a separate proxy.

## Sources

- [Issues](https://github.com/razvandimescu/numa/issues)
- [License: MIT](https://github.com/razvandimescu/numa/blob/main/LICENSE)
- [razvandimescu/numa on GitHub](https://github.com/razvandimescu/numa)
- [README](https://github.com/razvandimescu/numa/blob/main/README.md)
- [Releases](https://github.com/razvandimescu/numa/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/razvandimescu-numa
