Ctrl K
ComunidadGuías…

config.toml.example

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ferrite configuration example
# Copy to ~/.config/ferrite/config.toml  (macOS and Linux)
# or   /etc/ferrite/config.toml          (system-wide, needs root)
#
# Quick start for local networks:
#
#   ferrite setup
#
# This command detects your gateway, subnet, and local search domain, then
# writes the correct [[zones]] entries automatically.  Edit the rest as needed.

# Override where static web UI files are served from (dev only).
# This is a top-level setting, not part of [api].
# web_dir = "/home/user/ferrite-ui/dist"


# ── DNS server ────────────────────────────────────────────────────────────────

[dns]
# Interface and port to listen on (UDP + TCP).
# Needs root / CAP_NET_BIND_SERVICE for port 53.
bind_addr = "0.0.0.0:53"

# DNS response cache.
cache_size = 10000

# Clamp upstream TTLs to this range (seconds).
min_ttl = 60
max_ttl = 3600

# Optimistic cache: answer from an expired entry at once (with a 30 s TTL) and
# refresh it in the background, so a name a device asked for before never waits
# on the upstream — and keeps resolving while the upstream is down, for up to
# stale_max_age seconds. Off by default.
# serve_stale = true
# stale_max_age = 43200

# Who may use this resolver, and how fast. A client over rate_limit (queries
# per minute; a burst of the whole minute is fine) is answered REFUSED until it
# slows down; 0 = no limit. allowed_clients / denied_clients take IPs and CIDR
# ranges; a client outside them gets no answer at all. Loopback always passes.
# rate_limit = 1000
# allowed_clients = ["192.168.1.0/24", "fd00::/8"]
# denied_clients  = ["192.168.1.66"]

# Keeping devices on this resolver. All three apply without a restart.
#
# Firefox asks use-application-dns.net before switching itself to DNS over
# HTTPS (and past every filter on the network); NXDOMAIN keeps it here. DoH a
# user turned on by hand stays on. On by default.
# firefox_canary = true
#
# Apple devices ask mask.icloud.com before using iCloud Private Relay, which
# takes Safari's lookups around ferrite. NXDOMAIN turns Private Relay off on
# this network. Off by default: it is the device owner's privacy choice.
# block_private_relay = false
#
# Strip Encrypted Client Hello (ECH) from HTTPS/SVCB answers, so browsers keep
# the hostname readable in the TLS handshake. Off by default. (Not strip_ecs.)
# Blocking DoH servers themselves is a job for a list such as HaGeZi's
# doh-vpn-proxy-bypass.
# strip_ech = false

# Domains to suppress from the query log entirely (still resolved normally).
# Exact names or wildcard suffixes. Useful for noisy local/private zones.
# `fe.te` is ferrite's built-in panel domain and is ignored by default.
# log_ignore = ["fe.te", "*.test", "*.local", "*.lan"]


# ── Default upstream resolvers ────────────────────────────────────────────────
# Queries not matched by any [[zones]] entry go here.
# ferrite tries upstreams round-robin, failing over on error.

# Plain UDP/TCP (fastest, no encryption)
[[upstream]]
type    = "plain"
address = "1.1.1.1"
port    = 53

[[upstream]]
type    = "plain"
address = "1.0.0.1"
port    = 53

# DNS-over-HTTPS (recommended — encrypted, no ISP snooping)
# [[upstream]]
# type         = "https"
# url          = "https://cloudflare-dns.com/dns-query"
# bootstrap_ip = "1.1.1.1"   # skip DNS for the resolver's own hostname

# DNS-over-TLS
# [[upstream]]
# type     = "tls"
# address  = "1.1.1.1"
# port     = 853
# tls_name = "cloudflare-dns.com"

# DNS-over-QUIC
# [[upstream]]
# type     = "quic"
# address  = "94.140.14.14"
# port     = 853
# tls_name = "dns.adguard-dns.com"


# ── Zone routing (conditional forwarding) ─────────────────────────────────────
# Route specific DNS zones to a dedicated upstream instead of the default pool.
#
#   ferrite setup   ← auto-detects and writes these for you
#
# Zones are matched by suffix; the longest (most specific) match wins.
# Zone upstreams are plain UDP/TCP only.
#
# Why this matters:
#   PTR queries for 192.168.x.x never resolve on public DNS — only your
#   router knows those names.  Without zones, the client list in the UI
#   shows raw IP addresses instead of hostnames.
#
# Typical home setup — adjust addresses to match your network:

# [[zones]]
# name     = "1.168.192.in-addr.arpa"   # PTR for 192.168.1.0/24
# upstream = "192.168.1.1:53"

# [[zones]]
# name     = "localdomain"              # hostname.localdomain
# upstream = "192.168.1.1:53"

# [[zones]]
# name     = "lan"                      # hostname.lan
# upstream = "192.168.1.1:53"

# IPv6 ULA reverse zone (replace with your actual prefix):
# [[zones]]
# name     = "b.a.1.1.b.0.0.0.0.4.f.2.0.1.a.2.ip6.arpa"
# upstream = "192.168.1.1:53"

# Other subnets:
# [[zones]]
# name     = "0.10.in-addr.arpa"        # PTR for 10.0.0.0/16
# upstream = "10.0.0.1:53"


# ── Storage ───────────────────────────────────────────────────────────────────
#
# The database holds only what ferrite *observed*: the query log, its rollups,
# and the device inventory learned from PTR/mDNS and the neighbour tables.
# Everything you author — settings, subscriptions, manual block/allow entries,
# custom records, client aliases, tunnels — lives in this file and only here.
# So back up this file and you have backed up the configuration; delete the
# database and you lose history, never setup.
#
# Changes you make in the web UI are written straight back here. The file is
# regenerated on each save, so hand-written comments in the sections the UI
# manages will not survive; the values will.

[storage]
# Where the query log, its rollups and the learned device inventory live.
#
#   "sqlite" (default) — a database file; history survives a restart.
#   "memory"           — RAM only. No database file, no write-ahead log, and no
#                        disk write per query; history is lost on restart.
#                        (The compiled blocklist cache still lives on disk — it
#                        is built from downloaded lists, not from your traffic,
#                        and is written on a refresh, not per query. Move it
#                        with `blocklist_cache_dir` below.)
#
# Pick "memory" when the box boots off something you'd rather not spend writes
# on (an SD card), or when the root filesystem is read-only, or simply when you
# don't want what was resolved to land on a disk at all. The Queries page, the
# dashboard and the per-device stats all keep working either way — the
# in-memory store keeps the same rollups, it just forgets them on restart.
#
# Bounded by construction: the in-memory log is a ring that drops the oldest
# entry rather than growing. `log_max_mb` below sizes it; left at 0 it holds
# about 128k entries (~32 MB).
#
# Changing this needs a restart. Your configuration is in this file either way
# and is never affected.
# backend = "sqlite"

# Where the database file lives. Default: ~/.local/share/ferrite/ferrite.db
# (unused by the "memory" backend).
#
# Settable from the panel too, which lists what this box offers — including any
# RAM-backed mounts (/dev/shm, /run, /tmp on systemd) with their free space. A
# database on one of those costs the disk no writes at all and is cleared by a
# reboot; `memory` above is the same trade without a file in the way, and the
# difference is that a path on tmpfs survives a restart of ferrite itself.
#
# Changing this needs a restart. The path is checked before it is saved — it
# must be absolute, must not be a directory, and ferrite must be able to create
# files along it.
# path = "/var/lib/ferrite/ferrite.db"

# Where the compiled blocklist lives: the merged and per-profile FSTs the
# resolver maps, and the per-list caches a refresh builds them from (`lists/`,
# `allowlists/`). Default: the data directory, ~/.local/share/ferrite.
#
# None of it is your data — it is all rebuilt from the list servers — and it is
# written on a refresh, not per query; with conditional fetches, only when a
# list actually changed. A RAM-backed mount spends no flash on it at all. What
# that costs: the files then occupy RAM that cannot be paged out (roughly the
# size of the lists, tens of MB for a few big ones), and after a reboot every
# list is downloaded again before blocking is back to full strength.
#
# Changing this needs a restart. The old directory is left as it was — delete
# it by hand if you want the space back. Checked before it is saved, like `path`.
# blocklist_cache_dir = "/dev/shm/ferrite"

# Automatically delete query log entries older than this many days.
# Default: 30. A busy household writes on the order of a gigabyte of query log a
# quarter, and the database usually sits on an SD card; a month is longer than
# anyone looks back for. Set it to 0 to keep every entry forever — ferrite then
# warns once at startup if the database has grown large.
# log_retention_days = 30

# Cap the query log at this many megabytes, oldest entries first.
# Default: 0 (no size limit — the day count above decides on its own).
#
# Days are the right knob on a disk. Bytes are the right knob when the database
# sits somewhere that cannot absorb a surprise: a RAM-backed path, or a flash
# card with little room left. Set both and whichever trims first wins.
#
# Checked every 60 seconds rather than daily — a size cap exists to stop growth
# before it matters. Only the query log and its rollups are trimmed; the learned
# device inventory is tiny and is left alone.
#
# The first time a cap is switched on, an existing database is rebuilt once so
# it can release space back to the filesystem (SQLite otherwise keeps freed
# pages). That takes a moment and is logged; databases created from here on are
# already set up for it. Allow a few MB of headroom for the write-ahead log,
# which sits beside the database and is not counted in this figure.
# log_max_mb = 0

# Privacy switches — both default to true, both apply without a restart.
#
# Both work the same way: they stop new data being recorded AND stop the old
# data being served. What was written before the switch stays on disk, but
# ferrite will not hand it back while you have asked it not to be keeping any —
# switch the setting on again to read it, or delete it for good with the Clear
# button in the UI (DELETE /api/queries).
#
# log_queries = false stops the per-query log: nothing lands in the `queries`
# table and the Queries page stays empty. Filtering and routing are unaffected.
#
# keep_stats = false stops the aggregates: no top domains, no top clients, no
# 24-hour graph, no per-device counters, and no host names on the Tunnels page
# (per-tunnel byte and connection totals keep counting — they name no host).
# The Clients page goes empty too: it *is* the per-device counters. The
# dashboard falls back to the process-lifetime totals, which carry no domain or
# device identity. Those totals are anonymous, not ephemeral — they are kept in
# the restart snapshot so the dashboard still means something after a reboot.
#
# With both off ferrite records nothing about what was resolved — including the
# warm-restart snapshot, which stops storing cached domains (so a restart starts
# with a cold cache), and the learned device inventory: no IP->MAC bindings and
# no device names are written or read back, so the database stops describing who
# is on the network as well as what they looked up. Switching both off also
# drops the inventory already stored. Manual client aliases are configuration,
# not a record, and are kept. Other existing data is not deleted: use the Clear
# button in the UI, or DELETE /api/queries, which also vacuums the database.
# log_queries = true
# keep_stats = true


# ── API / web UI ──────────────────────────────────────────────────────────────

[api]
bind_addr = "127.0.0.1:8080"

# Optional API key — all /api/* requests must include:
#   Authorization: Bearer <api_key>   or   X-Api-Key: <api_key>
# Leave unset (or set to null via PATCH /api/settings) to disable key auth.
# api_key = "change-me"

# Web UI password (set via `ferrite passwd`, stored as Argon2id hash).
# Leave unset (or set to null via PATCH /api/settings) to disable password auth.
# password_hash = "..."


# ── Blocklist ─────────────────────────────────────────────────────────────────

[blocklist]
# Recent block/allow decisions kept in RAM. Lower this on very small devices;
# higher values reduce FST lookups on noisy networks.
decision_cache_size = 50000

# NOTE: `whitelist` used to live here; it is deprecated in favour of the
# [allowlist] section below. Old configs still load — entries are migrated
# automatically and rewritten in the new shape on the next config save.

# Manual block entries — exact domains and wildcards alike. An exact domain
# also blocks its subdomains; wildcards like "*.doubleclick.net" match all
# subdomains (not the apex itself). Mirrors [allowlist] domains below.
# This is where the UI writes; also manageable via POST /api/blocklist/blacklist.
#
# NOTE: `wildcard_block` was the old name and held wildcards only. Old configs
# still load — entries are migrated into `domains` automatically and rewritten
# in the new shape on the next config save.
# A regex between slashes matches anywhere in the name, case-insensitively —
# write it in single quotes so TOML leaves the backslashes alone.
domains = [
    # "*.doubleclick.net",
    # "ads.example.com",
    # '/^ads?[0-9]+\./',
]

# Services from the built-in catalog, blocked for every device: "youtube",
# "tiktok", "steam", … (the full list: GET /api/services, or the Blocklist
# page). Each service is a set of domains matched like the entries above, so
# an [allowlist] entry still exempts one. Device profiles take their own
# `services = [...]`, and a tunnel rule routes a whole service with
# `service = "youtube"` in place of `pattern`.
services = [
    # "tiktok",
]

# SafeSearch for every device: Google, Bing and DuckDuckGo resolve to the
# engines' safe hosts, so results are filtered whatever the browser says.
# YouTube Restricted Mode: "off", "moderate" or "strict". A device profile has
# the same two settings and can only make them stricter for its devices.
# safe_search = true
# youtube_restricted = "moderate"

# What a blocked name is answered with:
#   "nxdomain"  (default) no such name — apps give up at once
#   "nodata"    the name exists, without this record type
#   "null_ip"   0.0.0.0 for A, :: for AAAA
#   "custom_ip" block_ipv4 / block_ipv6 — a block page on your network
#   "refused"   the server won't answer
# Other record types get no records in the address modes.
# block_response = "null_ip"
# block_ipv4 = "192.168.1.5"
# block_ipv6 = "fd00::5"

# Remote blocklist subscriptions (hosts or Adblock format, auto-detected).
[[blocklist.lists]]
name    = "StevenBlack"
url     = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
enabled = true

# [[blocklist.lists]]
# name    = "AdGuard Base"
# url     = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
# enabled = true

# [[blocklist.lists]]
# name    = "Local custom"
# url     = "file:///etc/ferrite/custom.hosts"
# enabled = true


# ── Allowlist ─────────────────────────────────────────────────────────────────
# Domains never blocked, even when a blocklist contains them. Mirrors the
# [blocklist] structure: manual entries plus remote subscriptions. Only
# consulted while blocking is enabled — an allowlist doesn't resolve anything
# by itself, it only exempts domains from blocking.

[allowlist]
# Manual entries. An exact domain also exempts its subdomains; wildcards like
# "*.example.com" match all subdomains (not the apex itself).
# Also manageable at runtime via POST /api/blocklist/whitelist.
domains = [
    # "safe.example.com",
    # "*.internal.corp",
]

# Remote allowlist subscriptions — refreshed together with the blocklists.
# Formats: hosts, plain domain-per-line, or Adblock (there the "@@||domain^"
# exception rules are the allow entries). Useful for community-maintained
# false-positive lists. Also manageable at runtime via the /api/allowlists
# endpoints.

# [[allowlist.lists]]
# name    = "anudeepND Whitelist"
# url     = "https://raw.githubusercontent.com/anudeepND/whitelist/master/domains/whitelist.txt"
# enabled = true


# ── Custom DNS records ────────────────────────────────────────────────────────
# Local A/AAAA/CNAME overrides — bypass blocklist and upstream entirely.
# Also manageable at runtime via POST /api/custom-records.

# [[custom_records]]
# domain = "router.lan"
# type   = "A"
# value  = "192.168.1.1"
# ttl    = 300

# [[custom_records]]
# domain = "nas.lan"
# type   = "AAAA"
# value  = "fd00::1"
# ttl    = 300

# [[custom_records]]
# domain = "*.home.lan"
# type   = "A"
# value  = "192.168.1.100"
# ttl    = 300


# ── Client aliases ────────────────────────────────────────────────────────────
# What you decided to call a device, overriding the hostname ferrite learns for
# it. Give exactly one of `mac` or `ip`: a MAC follows the device across DHCP
# leases and address rotation, so prefer it when you know it.
#
# These are configuration, not a record — they are kept even with both privacy
# switches off, and they are unaffected by clearing the query log. The names
# ferrite *learns* (PTR/mDNS) are the record, and those live in the database.
# Also manageable at runtime via POST /api/clients/aliases.

# [[client_aliases]]
# mac  = "aa:bb:cc:dd:ee:ff"
# name = "Kitchen TV"

# [[client_aliases]]
# ip   = "192.168.1.5"
# name = "NAS"


# ── Notifications ─────────────────────────────────────────────────────────────
# One webhook, sent an event for each kind switched on. `format` shapes the
# body for the receiver: "json" (the default — the event's fields, for Home
# Assistant or a script), "ntfy", "discord", "slack" (also Mattermost and
# Rocket.Chat), "telegram" or "gotify". Configurable from Settings →
# Notifications, which can also send a test event.
#
# [notifications]
# webhook       = "https://ntfy.sh/my-ferrite"
# format        = "ntfy"
# tunnel_alerts = true    # a tunnel down for over a minute, and its recovery
# new_device    = true    # a device the box has never seen joins the network
#
# Telegram: the bot's sendMessage URL and the chat to post to.
# webhook = "https://api.telegram.org/bot<token>/sendMessage"
# format  = "telegram"
# chat_id = "123456789"

# ── Following a primary (two resolvers that answer alike) ────────────────────
# This box takes the chosen parts of another ferrite's configuration every
# `interval` seconds, with that box's API key, and restarts for about a second
# when something changed. Sections: filtering, records, devices, dns, tunnels
# (tunnels only if this box routes too — one WireGuard key on two boxes makes
# both tunnels flap). Edit on the primary; changes made here to those sections
# are replaced on the next sync.
#
# [sync]
# primary  = "http://192.168.1.10"
# api_key  = "<the primary's API key>"
# interval = 300
# sections = ["filtering", "records", "devices", "dns"]

# ── MCP (AI assistants) ───────────────────────────────────────────────────────
# /api/mcp lets an assistant such as Claude Code read what ferrite is doing,
# using the API key. Off by default; allow_changes also lets it block, allow
# and pause.
#
# [mcp]
# enabled       = true
# allow_changes = false

# ── DHCP (hand out addresses, with ferrite as everyone's DNS) ────────────────
# For routers that won't let you change the DNS they hand out. A network takes
# one DHCP server: switch off the router's when you switch this on. Blank
# values are detected — this box's address, the router, the subnet. Every
# lease names its device in ferrite and answers as <name>.<domain>. Applies
# without a restart; never synced to a secondary.
#
# [dhcp]
# enabled     = true
# pool_start  = "192.168.1.100"
# pool_end    = "192.168.1.199"
# subnet_mask = "255.255.255.0"
# gateway     = "192.168.1.1"     # blank: the router this box uses
# dns         = []                # blank: this box
# domain      = "lan"
# lease_secs  = 86400
#
# [[dhcp.reservations]]
# mac  = "aa:bb:cc:dd:ee:ff"
# ip   = "192.168.1.20"
# name = "nas"

# ── IPv6 DNS announcements ────────────────────────────────────────────────────
# Router advertisements naming this box the DNS server, so IPv6 devices don't
# go around it through the router's IPv6 DNS. DNS only: ferrite never offers
# itself as the router. Needs CAP_NET_RAW. Blank values are detected.
#
# [ra]
# enabled        = true
# interface      = "eth0"
# rdnss          = ["fd00::10"]
# rdnss_lifetime = 1800
# interval_secs  = 200

# ── Encrypted DNS for devices (DNS over TLS / DNS over HTTPS) ─────────────────
# Android's "Private DNS" speaks DNS over TLS on 853; iOS profiles and
# browsers speak DNS over HTTPS. A device can name itself with a client ID —
# pixel.dns.example.com (TLS) or https://dns.example.com/dns-query/pixel
# (HTTPS) — and is then that device for profiles, pauses and the log, from
# wherever it connects. At home, ferrite answers hostname and *.hostname with
# its own address, so devices reach it directly. Applies without a restart.
#
# The certificate must cover hostname and *.hostname (for IDs over TLS).
# With certificate = "letsencrypt" ferrite gets it and renews it itself,
# proving the name through Cloudflare's API — no port has to be open for
# that. The token needs the "Edit zone DNS" permission for the zone.
# With certificate = "files" it reads cert_file/key_file and picks renewed
# files up within ten minutes.
# With certificate = "relay" there's nothing to own: the box is named
# <id>.<zone> under its relay's zone (see [relay]), which fills in hostname,
# and gets its Let's Encrypt certificate with the relay answering the
# challenge. The key never leaves the box.
#
# DNS over HTTPS can share 443 with Tunnels: connections asking for
# hostname are answered here, everything else is routed as before.
#
# [secure_dns]
# enabled     = true
# hostname    = "dns.example.com"
# certificate = "letsencrypt"   # or "files", or "relay"
# dot_port    = 853
# doh_port    = 443             # 0 = off
# http3       = true            # DoH over HTTP/3 too, on UDP doh_port
# devices     = ["pixel", "iphone"]   # answered away from home
# A reverse proxy passing DNS over HTTPS on (Nginx Proxy Manager…): its
# address, so the client it names in X-Real-IP counts, not the proxy.
# DNS over TLS has to reach ferrite directly.
# proxies     = ["192.168.1.5"]
# Who may set a device up from http://<panel>/setup, at home and without the
# password: "off", "named" (devices you've named in Clients; the default) or
# "anyone". The device is added to `devices` and the webhook is told.
# self_setup  = "named"
#
# ── Relay: devices away from home reach this box through a relay ──────────────
# The box keeps a QUIC connection out to a relay (ferrite-relay), which passes
# on devices' encrypted connections to the box's names, never opening them:
# no port forwarded on the router. The relay is known by its key.
# [relay]
# enabled = true
# server  = "relay.example.net:4433"
# pin     = "sha256:…"            # `ferrite-relay pin` prints it
#
# [secure_dns.acme]
# cloudflare_token = "…"
# email            = "you@example.com"   # optional
# staging          = false               # true = test certificates
#
# or, with certificate = "files":
# cert_file = "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
# key_file  = "/etc/letsencrypt/live/dns.example.com/privkey.pem"