Valkyrian Labs logo

Configuration

Every key of ~/.forti/hole/config.json with its default, and the files forti-hole keeps next to it.

Configuration

~/.forti/hole/config.json ($FORTI_HOME/hole/config.json). Unknown keys are refused, with the key and its location in the message. forti-hole setup writes the example below (share/forti-hole/config.example.json).

Top level

KeyDefaultMeaning
format1Configuration format
enabledfalseNothing is written to the FortiGate until true
profile""The forti profile; empty: the default profile
vdom""A VDOM; empty: the profile's
prefix"fh-"Name prefix of every object forti-hole owns (2–20 characters, ending in -)
tiersTier names, in order
sourcesThe lists (Lists, tiers and profiles)
profiles{"name": {"tiers": [...]}}; each is the DNS filter profile <prefix><name>
allow, allow_regex, block[]Exceptions and extra entries
encoding"strict"strict or compact (Capacity)
reference{"url", "format": "tranco"}: real names for compact

sources[]

KeyMeaning
id1–48 characters of [a-z0-9._-], unique
tierOne of tiers
formatadblock, hosts, domains, wildcard, subtree-domains
urlhttps://… or file:///…
variants, variant, autoInstead of url: named URLs, the one to use (auto or a name), and the auto order
enabledtrue

budget

KeyDefaultMeaning
entry_headroom_percent10Kept free of the device's domain-entry limit
memory_headroom_percent15Kept free of device memory after the change
fallback_limit1000000The entry limit when the device does not report one
bytes_per_linelearnedDevice memory per feed line; leave unset to use the measured value
on_overrun"refuse"refuse (nothing written, exit 9) or warn

feeds, sync, fetch, dnsfilter

KeyDefaultMeaning
feeds.max_entries131072Lines per device feed
feeds.category_min, category_max192, 221The remote categories forti-hole may use
sync.max_shrink_percent20A list this much smaller than its last good copy is rejected
sync.chunk_entries50000Entries per delta request
sync.load_timeout_seconds120Waiting for FortiOS to load a feed or apply a push
sync.verify_samples, verify_page3, 500Pages read back per changed feed, and their size
fetch.timeout_seconds120Per download
fetch.max_bytes128 MiBLargest list accepted
fetch.parallel6Simultaneous downloads
fetch.ca_bundleExtra trusted CAs (PEM); verification stays on
dnsfilter.manage_profilestrueCreate and maintain the DNS filter profiles
dnsfilter.block_action"block"block (NXDOMAIN), redirect (block portal) or block-sevrfail
dnsfilter.error_allowtrueftgd-dns options error-allow: resolve normally when FortiGuard rating is unavailable
dnsfilter.logtrueLog blocked queries

The example

1{2  "format": 1,3  "enabled": false,4  "profile": "",5  "prefix": "fh-",6  "tiers": ["base", "plus", "ultimate", "threat"],7  "sources": [8    {"id": "stevenblack", "tier": "base", "format": "hosts", "url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"},9    {"id": "hagezi-pro", "tier": "base", "format": "adblock", "url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt"},10    {"id": "hagezi-pro-plus", "tier": "plus", "format": "adblock", "url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.plus.txt"},11    {"id": "hagezi-ultimate", "tier": "ultimate", "format": "adblock", "url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/ultimate.txt"},12    {"id": "hagezi-tif", "tier": "threat", "format": "adblock",13     "variants": {"full": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/tif.txt",14                  "medium": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/tif.medium.txt",15                  "mini": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/tif.mini.txt"},16     "variant": "auto", "auto": ["medium", "mini"]}17  ],18  "profiles": {19    "light": {"tiers": ["base", "threat"]},20    "standard": {"tiers": ["base", "plus", "threat"]},21    "strict": {"tiers": ["base", "plus", "ultimate", "threat"]}22  },23  "encoding": "strict",24  "budget": {"entry_headroom_percent": 10, "memory_headroom_percent": 15, "on_overrun": "refuse"},25  "dnsfilter": {"manage_profiles": true, "block_action": "block", "error_allow": true, "log": true}26}

Files

Path (under ~/.forti/hole/)What
config.jsonYours
state.jsonLast confirmed device state per feed, categories, list cache metadata, memory measurements
feeds/<feed>.txtExactly what each feed was last confirmed to hold
cache/<list>.listThe last good copy of every list
journal.jsonPresent only while a sync writes, or after one stopped part-way
history.jsonlOne line per sync

Every file is 0600 in a 0700 directory and written atomically. Credentials stay in forti's secret store. Plans and the audit log are the engine's (~/.forti/state/).