numa.toml
Rama main · réplica de lectura del repositorio original.
[server]
bind_addr = "0.0.0.0:53" # or a list: ["10.0.0.1:53", "127.0.0.1:53"]
api_port = 5380
# api_bind_addr = "127.0.0.1" # default; set to "0.0.0.0" for LAN dashboard access
# api_token = "…" # loopback is exempt, every other client needs this.
# Minted on first start (stored in data_dir)
# unless set here or via NUMA_API_TOKEN.
# `numa token` prints the one in effect.
# data_dir = "/var/lib/numa" # where numa stores TLS CA and cert material
# Defaults: /var/lib/numa on linux (FHS),
# /usr/local/var/numa on macos (homebrew prefix),
# %PROGRAMDATA%\numa on windows. Override for
# containerized deploys or tests that can't
# write to the system path.
# filter_aaaa = true # on IPv4-only networks, answer AAAA queries with
# NODATA (NOERROR + empty answer) so Happy Eyeballs
# clients don't wait on a v6 attempt that can't
# succeed. Also strips `ipv6hint` from HTTPS/SVCB
# records (RFC 9460) so modern browsers (Chrome
# ≥103, Firefox, Safari) don't bypass the AAAA
# filter via SVCB hints. Local zones, overrides,
# and the .numa proxy are NOT filtered — you can
# still configure v6 records for local services.
# Default: false.
# ── Client-IP allowlist ────────────────────────────────────────────
# Required on WAN-facing binds, or numa becomes an open recursive
# resolver. Loopback always allowed. Denied peers: silent UDP drop,
# TLS close pre-handshake, DoH 403.
#
# allow_from = ["10.0.0.0/8", "192.168.0.0/16", "fd00::/8"]
# ── Resolution ceiling ─────────────────────────────────────────────
# Cache misses running at once, across UDP, TCP, DoT and DoH together.
# One charge per query; cached, local and coalesced answers are free, so
# this bounds only what a flood of novel names can conjure. Over the
# ceiling UDP is dropped silently and stream transports get SERVFAIL.
# Watch `queries.refused` and `resolutions` in /stats before changing it.
#
# max_concurrent_resolutions = 512 # default
# ── Upstream ───────────────────────────────────────────────────────
# Where numa sends the queries it can't answer itself. Uncomment one
# [upstream] block: forward (the default), recursive, odoh or auto.
# Forward: relay to upstream servers. Without `address`, numa follows
# your system DNS as networks change, or uses Quad9 over DoH (then
# Cloudflare if Quad9 fails) if it finds none.
# [upstream]
# mode = "forward"
# address = "9.9.9.9" # single upstream (plain UDP)
# address = ["192.168.1.1", "9.9.9.9:5353"] # multiple upstreams, asked in order; a failed one is asked last
# address = "https://dns.quad9.net/dns-query" # DNS-over-HTTPS (encrypted)
# address = "tls://9.9.9.9#dns.quad9.net" # DNS-over-TLS (encrypted, port 853)
# fallback = ["8.8.8.8", "1.1.1.1"] # tried only when all primaries fail
# port = 53 # default port for addresses without :port
# timeout_ms = 5000 # default
# hedge_ms = 0 # request hedging delay (ms). Default: 0 (off).
# # Set to e.g. 10 to fire a parallel upstream
# # request after 10ms of silence — rescues packet
# # loss (UDP), dispatch spikes (DoH), TLS stalls
# # (DoT). Doubles the upstream query count, so
# # leave off for quota'd providers (NextDNS,
# # Control D).
# Recursive: resolve from the root servers, with no upstream provider.
# `address`, `port`, `timeout_ms` and `hedge_ms` only affect forwarding,
# and `fallback` is not tried when recursion fails. For DNSSEC
# validation, also set [dnssec] enabled = true.
# [upstream]
# mode = "recursive"
# root_hints = [ # default: the 13 root servers
# "198.41.0.4", # a.root-servers.net (Verisign)
# "199.9.14.201", # b.root-servers.net (USC-ISI)
# "192.33.4.12", # c.root-servers.net (Cogent)
# "199.7.91.13", # d.root-servers.net (UMD)
# "192.203.230.10", # e.root-servers.net (NASA)
# "192.5.5.241", # f.root-servers.net (ISC)
# "192.112.36.4", # g.root-servers.net (US DoD)
# "198.97.190.53", # h.root-servers.net (US Army)
# "192.36.148.17", # i.root-servers.net (Netnod)
# "192.58.128.30", # j.root-servers.net (Verisign)
# "193.0.14.129", # k.root-servers.net (RIPE NCC)
# "199.7.83.42", # l.root-servers.net (ICANN)
# "202.12.27.33", # m.root-servers.net (WIDE)
# ]
# prime_tlds = [ # TLDs to pre-warm on startup (default list)
# "com", "net", "org", "info", # gTLDs
# "io", "dev", "app", "xyz", "me",
# "eu", "uk", "de", "fr", "nl", # EU + European ccTLDs
# "it", "es", "pl", "se", "no",
# "dk", "fi", "at", "be", "ie",
# "pt", "cz", "ro", "gr", "hu",
# "bg", "hr", "sk", "si", "lt",
# "lv", "ee", "ch", "is",
# "co", "br", "au", "ca", "jp", # other major ccTLDs
# ]
# ODoH (Oblivious DNS-over-HTTPS, RFC 9230). The relay sees your IP but
# not the question; the target sees the question but not your IP. Numa
# refuses same-operator relay+target configs by default (eTLD+1 check).
# [upstream]
# mode = "odoh"
# relay = "https://odoh-relay.numa.rs/relay"
# target = "https://odoh.cloudflare-dns.com/dns-query"
# strict = true # default: refuse to downgrade to `fallback`
# # on relay failure. Set false to allow a
# # non-oblivious fallback path.
# relay_ip = "178.104.229.30" # optional: pin IPs so numa doesn't leak the
# target_ip = "104.16.249.249" # relay/target hostnames via the bootstrap
# # resolver on cold boot when numa is its
# # own system DNS. See
# # recipes/odoh-upstream.md.
# Auto: probe the root servers at startup. Resolve recursively if they
# answer, otherwise forward to Quad9 over DoH (then Cloudflare if Quad9
# fails) until the next restart.
# [upstream]
# mode = "auto"
# numa also builds rules from the system DNS config: search domains on Linux,
# split-DNS resolvers on macOS. It skips `local`, which is mDNS (RFC 6762).
# A [[forwarding]] rule with suffix = "local" forwards it anyway.
#
# [[forwarding]] # per-suffix conditional forwarding rules
# suffix = "168.192.in-addr.arpa" # single suffix → one upstream
# upstream = "100.90.1.63:5361"
#
# [[forwarding]]
# suffix = ["home.local", "home.arpa"] # multiple suffixes → same upstream
# upstream = "10.0.0.1" # port 53 default
#
# [[forwarding]] # DoT upstream: tls://IP[:port]#hostname
# suffix = ["google.com", "goog"] # hostname is the TLS SNI / cert name
# upstream = "tls://9.9.9.9#dns.quad9.net" # port 853 default
#
# [[forwarding]] # DoH upstream: full https:// URL
# suffix = "example.corp"
# upstream = "https://dns.quad9.net/dns-query"
#
# [[forwarding]] # array of upstreams → asked in order,
# suffix = ["google.com", "goog"] # a failed one is asked last
# upstream = [
# "tls://9.9.9.9#dns.quad9.net",
# "tls://149.112.112.112#dns.quad9.net",
# ]
# [blocking]
# enabled = true # set to false to disable ad blocking
# refresh_hours = 24 # a refresh that leaves nothing loaded retries in
# # an hour instead of waiting the full cycle.
# The last good copy of each remote list is kept under <data_dir>/blocklists so
# a restart while the upstream is down still blocks. It never expires: age is
# reported in the dashboard, but an old list always beats no list.
# Every entry blocks the domain and all its subdomains, whatever the list
# format — hosts lines, bare domains, adblock syntax, remote or local.
# lists = [
# "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/pro-onlydomains.txt",
# "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/nsfw-onlydomains.txt", # adult-content (opt-in)
# "file:///etc/numa/local-blocklist.txt", # local file: hosts lines or bare domains
# "/etc/numa/extra-blocks.txt", # bare absolute path also works
# ]
# Where raw.githubusercontent.com is unreachable, the jsDelivr mirror of the
# same file works: https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/wildcard/pro-onlydomains.txt
# allowlist = [ # never blocked by the lists above, subdomains included
# "example.com",
# "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/whitelist-referral-onlydomains.txt",
# "/etc/numa/allow.txt", # URLs and paths refresh like the lists above
# ]
# ── Per-client domain policies ─────────────────────────────────────
# Block or allow specific domains for specific clients, on top of the
# global blocklist above. `block`/`allow` use the same adblock syntax
# as global lists. Loopback always bypasses. Rules layer in order: the
# first rule matching the client that has an explicit block/allow for
# the name wins; within a rule, allow beats block.
#
# [[client_policy]]
# from = ["192.168.1.0/24"] # clients this rule applies to (CIDRs/IPs)
# exclude = ["192.168.1.254"] # carve hosts out of the range (e.g. your own device)
# block = ["youtube.com", "*.tiktok.com"]
# allow = ["safe.example.com"] # exempt a name the global list would block
#
# [[client_policy]]
# from = ["10.0.0.50"] # a single host: stricter rules just for it
# block = ["reddit.com"]
# filter_aaaa = true # override global [server].filter_aaaa for these clients (alone makes a rule valid)
[cache]
max_entries = 100000
min_ttl = 60
max_ttl = 86400
# warm = ["google.com", "github.com"] # resolve at startup, refresh before TTL expiry
[proxy]
enabled = true
port = 80
tls_port = 443
tld = "numa"
# authoritative = true # false when tld is a real domain: other names under it resolve as usual
# bind_addr = "127.0.0.1" # default; set to "0.0.0.0" for LAN access to .numa services
# ── Bring-your-own TLS cert ────────────────────────────────────────
# Skip per-device CA install. `tld` must be a domain you own. Renew
# externally; restart to pick up.
#
# cert_path = "/etc/letsencrypt/live/example.com/fullchain.pem"
# key_path = "/etc/letsencrypt/live/example.com/privkey.pem"
# ── PROXY protocol v2 ──────────────────────────────────────────────
# Preserve the real client IP behind an L4 front-end (dnsdist,
# HAProxy, AWS NLB). Non-empty `from` is required-mode: connections
# outside the allowlist, or missing the header, are dropped.
#
# [proxy.proxy_protocol]
# from = ["10.0.0.0/8", "127.0.0.1"] # trusted L4 front-ends
# header_timeout_ms = 5000 # kill stalled-header connections
# Pre-configured services (numa.numa is always added automatically)
# [[services]]
# name = "frontend"
# target_port = 5173
#
# [[services]]
# name = "api"
# target_port = 8000
# target_host = "192.168.1.50" # optional, defaults to localhost
# Many records: use an inline array, see recipes/local-dns-records.md.
# Example zone records:
# [[zones]]
# domain = "dimescu.ro"
# record_type = "A"
# value = "3.120.139.105"
# ttl = 30
# [[zones]]
# domain = "test.local"
# record_type = "A"
# value = "127.0.0.1"
# ttl = 60
# Reverse-DNS (PTR) for LAN hosts — makes tcpdump/access logs readable
# [[zones]]
# domain = "1.0.168.192.in-addr.arpa" # 192.168.0.1 reversed + .in-addr.arpa
# record_type = "PTR"
# value = "router.lan"
# ttl = 300
# Wildcard zones — one rule covers an entire subtree. Leftmost label only
# (RFC 4592). Exact entries always win; the wildcard does NOT match its parent
# (e.g. `*.pool.ntp.org` matches `time2.pool.ntp.org` but not `pool.ntp.org`).
# [[zones]]
# domain = "*.pool.ntp.org"
# record_type = "CNAME"
# value = "time.onsite"
# ttl = 60
# DNSSEC signature validation. Needs recursive resolution: forward and
# odoh modes ignore it.
# [dnssec]
# enabled = false # opt-in: verify chain of trust from root KSK
# strict = false # true = SERVFAIL on bogus signatures
# DNS-over-TLS listener (RFC 7858) — encrypted DNS on port 853
# [dot]
# enabled = true # on by default; set false to disable
# port = 853 # standard DoT port
# bind_addr = "0.0.0.0" # IPv4 or IPv6; unspecified binds all interfaces
# cert_path = "/etc/numa/dot.crt" # PEM cert; omit to use self-signed (proxy CA if available)
# key_path = "/etc/numa/dot.key" # PEM private key; must be set together with cert_path
# ── PROXY v2 on DoT ────────────────────────────────────────────────
# Same semantics as [proxy.proxy_protocol] above.
#
# [dot.proxy_protocol]
# from = ["10.0.0.0/8"]
# header_timeout_ms = 5000
# LAN service discovery via mDNS (disabled by default — no network traffic unless enabled)
# [lan]
# enabled = true # discover other Numa instances via mDNS (_numa._tcp.local)
# broadcast_interval_secs = 30
# peer_timeout_secs = 90
# ── Mobile API ─────────────────────────────────────────────────────
# Read-only HTTP for the companion apps (discovery, /ca.pem,
# /mobileconfig, QR onboarding via `numa setup-phone`). Binds to the
# LAN by default. Don't enable on untrusted LANs (office Wi-Fi,
# cafés) — an attacker on the same network can shadow you via mDNS
# and serve their profile instead.
[mobile]
enabled = true # opt-in to the mobile API listener
# port = 8765 # default; matches Discovery.swift defaultAPIPort
# bind_addr = "0.0.0.0" # default; set to "127.0.0.1" for localhost-only