# Runbound
**Drop-in Unbound replacement — REST API, linear scaling, and no restart ever.**
*Built just for fun — because reconfiguring Unbound by hand every week gets old.*
[](LICENSE) [](COMMERCIAL_LICENSE.md)
[](https://github.com/redlemonbe/Runbound/releases/latest)
[](docs/audit.md) [](https://github.com/redlemonbe/Runbound/releases/latest)
[](https://github.com/sponsors/redlemonbe)
---
You run Unbound. It works. But every time you need to add a DNS entry, block a domain, or subscribe to a block list, you edit a config file, reload the daemon, and hope nothing breaks.
**Runbound does the same job — and lets you manage everything via a REST API, live, without restart.**
Your existing `unbound.conf` works as-is. Zero migration.
---
## What you get
| Drop-in Unbound config | ❌ | ✅ | ✅ |
| UDP / TCP / DoT / DoH | ✅ | ✅ | ✅ |
| Add a DNS entry live | ⚠️ nsupdate | ❌ restart | ✅ API |
| Block a domain live | ⚠️ RPZ | ❌ restart | ✅ API |
| Subscribe to block-list feeds | ⚠️ RPZ/manual | ❌ manual | ✅ API |
| Real-time query statistics | ⚠️ XML/JSON channel | ❌ | ✅ API |
| Live query log | ⚠️ via rndc | ❌ | ✅ API |
| SSE live stats stream | ❌ | ❌ | ✅ API |
| Upstream health monitoring | ❌ | ❌ | ✅ API |
| Master/slave replication | ✅ AXFR/IXFR | ❌ | ✅ built-in |
| Automatic TLS (Let's Encrypt) | ❌ external | ❌ external | ✅ built-in ACME |
| Tamper-evident audit log | ❌ | ❌ | ✅ HMAC-SHA256 chain |
| Prometheus metrics | ⚠️ XML/JSON channel | ❌ | ✅ `/metrics` OpenMetrics |
| API key rotation (no restart) | ❌ | ❌ | ✅ `POST /rotate-key` |
| Hot config reload | ✅ rndc reload | ❌ | ✅ API |
| AF/XDP kernel-bypass fast path | ❌ | ❌ | ✅ optional |
| Linear scaling (SO_REUSEPORT, no lock contention) | ❌ | ❌ | ✅ built-in |
| CPU affinity — physical cores only (HT excluded) | ❌ | ❌ | ✅ automatic |
| Adaptive DNS cache (auto-sized from available RAM) | ❌ | ❌ | ✅ built-in |
| Static binary (no dependencies) | ❌ | ❌ | ✅ musl builds |
---
## Up and running in 60 seconds
```bash
# 1 — Download the static binary (no dependencies)
# Replace vX.Y.Z with the latest version tag from the releases page
curl -LO https://github.com/redlemonbe/Runbound/releases/latest/download/runbound-vX.Y.Z-x86_64-linux-musl
chmod +x runbound-vX.Y.Z-x86_64-linux-musl
# 2 — One-liner install (downloads automatically, sets up systemd):
# sudo bash <(curl -fsSL https://github.com/redlemonbe/Runbound/releases/latest/download/install.sh)
# 3 — Or point it at your existing Unbound config
sudo ./runbound-vX.Y.Z-x86_64-linux-musl /etc/unbound/unbound.conf
# 4 — Test it
dig @127.0.0.1 google.com
```
DNS live on **port 53**. REST API live on **port 8081** (localhost only, requires Bearer token). No config change needed.
The REST API port is configurable with `api-port: 9090` in `runbound.conf`. See the [Configuration Reference](docs/configuration.md#api-key-and-port).
> Raspberry Pi or ARM server? Grab `runbound-vX.Y.Z-aarch64-linux-musl` instead.
---
## Manage DNS without touching a file
```bash
API="http://localhost:8081"
TOKEN="your-api-key"
# Add a DNS entry — live, no restart
curl -s -X POST "$API/dns" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"nas.home.","type":"A","value":"192.168.1.10","ttl":300}'
# Block a domain — live
curl -s -X POST "$API/blacklist" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain":"ads.example.com"}'
# Subscribe to URLhaus malware feed — auto-refreshed
curl -s -X POST "$API/feeds" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"urlhaus","url":"https://urlhaus.abuse.ch/downloads/hostfile/"}'
# Live query stats
curl -s "$API/stats" -H "Authorization: Bearer $TOKEN"
```
---
## Performance
### Linear scaling — the key architectural difference
BIND9 and Unbound use shared caches protected by locks.
Beyond 8–16 cores, contention grows and throughput plateaus.
Runbound is built differently:
- **SO_REUSEPORT** — one UDP socket per physical core, kernel-distributed.
Zero userspace lock on the receive path.
- **ArcSwap zone trie** — readers take a lock-free snapshot.
Any number of cores can read simultaneously with no contention.
- **CPU affinity** — each worker thread is pinned to a physical core,
HyperThreading siblings excluded. Enabled automatically at startup:
`CPU affinity enabled — physical cores (HT excluded) cores=N`
- **Adaptive cache** — cache size is computed from `/proc/meminfo` at
startup and adjusts automatically under memory pressure.
`cache size auto-sized from MemAvailable cache_size=N`
Result: Runbound scales **linearly with core count** where
BIND9 and Unbound plateau.
```
QPS
│ Runbound /
│ /
│ /
│ BIND9 / Unbound /
│ ................/
│ _____/ /
│ __/ /
│___/ /
└─────────────────────────────────▶ cores
2 8 16 32 64
```
### Measured throughput
Benchmarks run from a dedicated client machine (never from the DNS server):
| 4-core KVM, 8 GB | dnsmark | ~16 000 q/s | 2 vCPU allocated to Runbound |
| Bare metal 40c, 256 GB | dnsmark | pending | T640 — results coming |
| AF/XDP bare metal | dnsmark | 500k – 1M+ q/s | kernel-bypass, Intel NICs |
> Full methodology: [docs/performance.md](docs/performance.md)
---
## Downloads
| Linux x86_64 | static (musl) — no deps | `runbound-vX.Y.Z-x86_64-linux-musl` |
| Linux x86_64 | dynamic (glibc) | `runbound-vX.Y.Z-x86_64-linux-gnu` |
| Linux ARM64 | static (musl) — Raspberry Pi, servers | `runbound-vX.Y.Z-aarch64-linux-musl` |
| Linux ARM64 | dynamic (glibc) | `runbound-vX.Y.Z-aarch64-linux-gnu` |
All releases: [github.com/redlemonbe/Runbound/releases](https://github.com/redlemonbe/Runbound/releases)
Or build from source: `cargo build --release`
With AF/XDP fast path: `cargo build --release --features xdp`
---
## Example configurations
Ready-to-use configs for common scenarios:
| [examples/home.conf](examples/home.conf) | Raspberry Pi / home lab — replaces Pi-hole |
| [examples/office.conf](examples/office.conf) | SMB office — split-horizon DNS, VPN, corporate zone |
| [examples/server.conf](examples/server.conf) | Public recursive resolver — VPS / datacenter |
| [examples/secure.conf](examples/secure.conf) | Air-gapped / military-grade — strict ACL, no public forwarding |
| [examples/master.conf](examples/master.conf) | Master node — writes + replication to slaves |
| [examples/slave.conf](examples/slave.conf) | Slave replica — read-only, TOFU TLS, auto delta sync |
**Integration example:**
| [examples/postgres_collector.py](examples/postgres_collector.py) | Polls `/stats` + `/logs` and inserts into PostgreSQL — reference for "how do I store DNS data in my own DB?" |
---
## Documentation
| [Home Lab Guide](docs/homelab.md) | Raspberry Pi / home server setup — local names, ad blocking, router config |
| [Quick Start](docs/quick-start.md) | Install, configure, run in 5 minutes |
| [Configuration Reference](docs/configuration.md) | Every directive explained, slave/master sync, Unbound compatibility table |
| [REST API Reference](docs/api.md) | All endpoints with curl examples and JSON responses |
| [High Availability](docs/ha.md) | Master/slave replication, VRRP failover, multi-node setup |
| [Performance Guide](docs/performance.md) | Benchmarks, methodology, how to reproduce |
| [TLS Setup](docs/tls.md) | DoT on port 853 — Let's Encrypt, ACME auto-provisioning, internal CA |
| [AF/XDP Fast Path](docs/xdp.md) | Kernel-bypass networking — 500k+ q/s |
| [Systemd Setup](docs/systemd.md) | Production service, hardened unit file, hot reload |
| [Unbound Migration](docs/unbound-migration.md) | Config compatibility, feature mapping, gotchas |
| [Security Architecture](docs/security.md) | ACL, rate limiting, API auth, audit findings |
| [Security Audit](docs/security-audit.md) | White-box audit findings and remediation log |
| [GDPR / Privacy](docs/gdpr.md) | Data inventory, log retention, IP redaction, right-to-erasure |
---
## Contributing
Pull requests welcome. By submitting a pull request you agree to the [Contributor License Agreement](CLA.md).
1. `cargo clippy --all-targets --features xdp` — zero warnings required
2. `cargo test` — all tests must pass
3. Security fixes: document with a `VUL-NN` tag
---
## Support the project
If Runbound saves you time or infrastructure costs:
[](https://github.com/sponsors/redlemonbe)
**Bitcoin** — `3FP8hkkiu4kwCD1PDFgAv2oq1ZTyXwy3yy`
**Ethereum** — `0xB5eEAf89edA4204Aa9305B068b37A93439cBb680`
---
## License
Runbound is open source under the [GNU AGPL v3](LICENSE).
**What this means:** if you use Runbound as part of a commercial service,
you must publish your modifications under the same license.
**Commercial license:** organizations that need to use Runbound without
AGPL obligations can purchase a commercial license with priority support.
See [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md).
Copyright (C) 2024-2026 RedLemonBe
---
## Development methodology
> How this project was built — and why: [METHODOLOGY.md](METHODOLOGY.md)
Runbound's security posture is reinforced using AI-assisted tooling at every release:
- **Security audit** — white-box code review covering SSRF, injection, timing attacks, DoS vectors, and RFC compliance (see [`docs/security-audit.md`](docs/security-audit.md))
- **Pentest** — black-box API and DNS protocol testing (input validation, amplification, information disclosure, authentication bypass)
- **Performance analysis** — hot-path profiling and allocation review
AI tools are used exclusively as an adversarial review layer. All findings are triaged and patched by the maintainer.
---
*Runbound is not affiliated with the NLnet Labs Unbound project.*