runbound 0.3.1

RFC-compliant DNS recursive resolver — drop-in replacement for Unbound, with integrated REST API
# TLS Setup (DNS-over-TLS)

Runbound supports **DNS-over-TLS (DoT)** on port **853** when a certificate and
private key are provided. Clients that support DoT (Android 9+, systemd-resolved,
Unbound, Pi-hole, etc.) will encrypt all DNS traffic.

---

## Option A — Let's Encrypt (public server)

Requires a publicly reachable server with a domain name pointing to it.

```bash
# Install certbot
apt-get install -y certbot

# Issue certificate (stop runbound briefly if it binds port 80)
certbot certonly --standalone -d dns.example.com

# Paths to use in runbound.conf:
# tls-service-pem: /etc/letsencrypt/live/dns.example.com/fullchain.pem
# tls-service-key: /etc/letsencrypt/live/dns.example.com/privkey.pem
```

Add to `/etc/runbound/runbound.conf`:

```
server:
    tls-service-pem: /etc/letsencrypt/live/dns.example.com/fullchain.pem
    tls-service-key: /etc/letsencrypt/live/dns.example.com/privkey.pem
```

**Auto-renewal:** certbot installs a systemd timer. After renewal, reload Runbound:

```bash
# /etc/letsencrypt/renewal-hooks/deploy/runbound.sh
#!/bin/sh
systemctl reload runbound
```

```bash
chmod +x /etc/letsencrypt/renewal-hooks/deploy/runbound.sh
```

---

## Option B — Self-signed certificate (internal / air-gapped)

For internal networks where clients trust your own CA.

```bash
# Generate a private key and self-signed certificate (10-year validity)
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout /etc/runbound/key.pem \
  -out /etc/runbound/cert.pem \
  -days 3650 \
  -subj "/CN=dns.internal"

# Lock down permissions
chmod 640 /etc/runbound/key.pem /etc/runbound/cert.pem
chown runbound:runbound /etc/runbound/key.pem /etc/runbound/cert.pem
```

Add to `runbound.conf`:

```
server:
    tls-service-pem: /etc/runbound/cert.pem
    tls-service-key: /etc/runbound/key.pem
```

Clients must be configured to trust your CA or the self-signed certificate.

---

## Option C — Internal CA (enterprise)

```bash
# 1. Generate CA key and certificate
openssl genrsa -out /etc/runbound/ca.key 4096
openssl req -x509 -new -nodes -key /etc/runbound/ca.key \
  -sha256 -days 3650 -out /etc/runbound/ca.crt \
  -subj "/CN=Internal CA"

# 2. Generate server key and CSR
openssl genrsa -out /etc/runbound/key.pem 4096
openssl req -new -key /etc/runbound/key.pem \
  -out /etc/runbound/server.csr \
  -subj "/CN=dns.corp.example.com"

# 3. Sign with your CA
openssl x509 -req -in /etc/runbound/server.csr \
  -CA /etc/runbound/ca.crt -CAkey /etc/runbound/ca.key \
  -CAcreateserial -out /etc/runbound/cert.pem \
  -days 825 -sha256

# 4. Distribute ca.crt to all clients
```

---

## Option D — Built-in ACME (automatic, zero-maintenance)

Runbound can provision and renew its own Let's Encrypt certificate with no external
tools. Port 80 must be publicly reachable and the domain must point to this server.

```
server:
    acme-email:  admin@example.com
    acme-domain: dns.example.com

    # Point TLS to the auto-managed files:
    tls-service-pem: /etc/runbound/acme/cert.pem
    tls-service-key: /etc/runbound/acme/key.pem
```

Runbound handles the full ACME HTTP-01 flow at startup and checks for renewal every
6 hours (renews when ≤ 30 days remain). After renewal, restart Runbound to load the
new certificate.

**Automatic restart on renewal** (systemd):

```ini
# /etc/systemd/system/runbound.service
[Service]
...
ExecStartPost=/bin/sh -c 'while inotifywait -e close_write /etc/runbound/acme/cert.pem 2>/dev/null; do systemctl reload-or-restart runbound; done &'
```

Or use a dedicated deploy hook:

```bash
# /etc/runbound/renew-hook.sh — called by Runbound after each renewal
#!/bin/sh
systemctl restart runbound
```

See [configuration.md](configuration.md#acme--lets-encrypt-automatic-tls) for the full
list of `acme-*` directives.

---

## Verify DoT is working

```bash
# Using kdig (from knot-dnsutils)
kdig @127.0.0.1 +tls google.com

# Using openssl
openssl s_client -connect 127.0.0.1:853 -servername dns.example.com
```

---

## Client configuration

**Android 9+ (Private DNS):** Settings → Network → Private DNS → enter `dns.example.com`.

**systemd-resolved (`/etc/systemd/resolved.conf`):**
```ini
[Resolve]
DNS=192.168.1.5
DNSOverTLS=yes
```

**Unbound (as a client forwarding to Runbound):**
```
forward-zone:
    name: "."
    forward-addr: 192.168.1.5@853
    forward-tls-upstream: yes
```

**Pi-hole:** DNS settings → Custom upstream → `192.168.1.5#853`.