Guide
Block, allow, and explain
Add the blocklists you trust, make quick exceptions for one device or a whole group, set rules by the time of day, and when something is blocked, see exactly which rule did it.
- Add a blocklist
- List formats
- How blocked names are answered
- Quick block and allow rules
- Bedtime and other schedules
- Blocked services and safe search
- Try a list first
- Find out why
- Troubleshooting
Which devices get which lists is decided by groups: a group without lists uses every list. See Groups, devices, and VLANs.
Add a blocklist
A list can be a URL, a local file, or rules you type in. Downloads run in the background: DNS answers while they run, and a failed download never affects answers.
Web UI
- Open the Lists page and, under Manage lists, choose Add list.
- Give it a name, then fill in Download from (a URL) or Rules (one per line). Kind is block or allow.
- Test it downloads and parses the draft from this node: how many rules, and the first lines it can't use.
- Check shows what the change will do and any warnings (such as a list no group uses). Nothing is saved until Apply.
Config file
[[list]]
name = "hagezi-pro" # lowercase letters, digits, - and _
url = "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt"
[[list]]
name = "family-allow"
kind = "allow" # block (default) or allow
match = "exact" # subtree (default) or exact
path = "/etc/telltale/allow.txt" # a local file, re-read on every refresh
[[list]]
name = "manual"
rules = ["||ads.example.com^", "@@||cdn.example.com^"]
- Adding, removing, or editing lists applies on reload (
SIGHUP).[filter]changes need a restart. - Lists refresh every 24 hours by default (
[filter] refresh_secs). To refresh every list now, sendSIGUSR1(docker kill -s USR1 telltale), likepihole -gin Pi-hole. telltale lists fetch -c telltale.tomlfetches outside the server, andtelltale lists check -c telltale.tomlshows which lines were skipped and why.
API / CLI
PUT /api/v1/lists/{name} takes the same fields as [[list]]; add ?dryRun=true to preview. POST /api/v1/checks/list tests a draft without saving it.
telltale ctl put lists/hagezi-pro --data '{"url": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt"}'
telltale ctl lists # with unique names and hits
A failed refresh never loses a list. The last good copy stays in use, and a failing list is retried after 5 minutes, then 10, 20, and so on up to hourly. Downloads that can't be a list (an empty body, a captive portal's HTML page, mostly invalid lines) are refused.
Every [[list]] and [filter] key is in the configuration reference; compiling and memory sizing are in the detailed docs.
List formats
Every common format works, and formats can be mixed in one list:
| Line | Meaning |
|---|---|
0.0.0.0 ads.example.com | Hosts file: block the name (localhost and similar lines are skipped) |
ads.example.com | The name and everything below it (only the name with match = "exact") |
*.example.com or .example.com | Everything below example.com, but not example.com itself |
||ads.example.com^ | AdBlock: the name and everything below it |
@@||cdn.example.com^ | Exception: allow, even in a blocklist |
||ads.example.com^$important | Wins over ordinary allow rules |
||example.com^$client=192.168.1.0/24|'Kids tablet' | Only for these clients (~ excludes) |
||nas.example^$dnsrewrite=192.168.1.20 | Answer with this address instead |
/^ad[0-9]+\./ or (^|\.)doubleclick\.net$ | AdBlock regex, or Pi-hole regex |
Rules that only make sense in a browser (cosmetic ## rules, URL paths, $third-party) are counted as unsupported and skipped, not treated as errors. The full syntax table has every modifier.
How blocked names are answered
A query is checked against the filter after local records and before the cache, so a blocked name never reaches an upstream. The answer is set per group (the device's highest-priority group decides):
[[group]]
name = "kids"
block_mode = "null_ip" # default: A → 0.0.0.0, AAAA → ::, other types → no records
# or "nxdomain", "nodata", "refused", "custom_ip"
block_ips = ["192.168.1.2", "fd00::2"] # for "custom_ip", e.g. a "blocked" page
block_ttl = 60 # how long clients may cache the block
ede = "filtered" # "blocked" (EDE 15, default) or "filtered" (EDE 17: parental controls)
ede_text = true # name the list in the EDE text (false hides list names)
null_ip is the default, as in Pi-hole and Technitium: apps treat it as unreachable and give up quickly, while NXDOMAIN makes some apps retry or fall back to another resolver. In the UI, the blocked-answer mode is on the group's editor in the Advanced view.
CNAMEs are checked too: if an answer leads through a CNAME to a blocked name, the client gets its block answer. When rules disagree, the most important one wins:
- Important allow (
@@||cdn.example.com^$important) - Important block (
||ads.example.com^$important) - Allow (allowlists and
@@rules) - Block
Quick block and allow rules
A quick rule allows or blocks a site, and everything under it, for one device, a group, or everyone, for a while or until you remove it. Unblock a game's server on one phone for an evening, or allow something a list blocks by mistake while you report it.
Web UI
- In the query log, press Why? on a query and use Make a quick rule. The site, the device, and its group are filled in.
- Or open the Quick rules page (Filtering & DNS). It also lists the rules in effect with a countdown, who made each, and a Remove button.
Config file
[[rule]]
id = "kids-videos"
action = "block" # or "allow"
domain = "videos.example.com" # and its subdomains
groups = ["kids"] # or devices = ["Mom phone", "192.168.1.40"]; neither = everyone
expires = "2026-10-06T07:00:00Z" # optional (RFC 3339)
note = "school night"
Useful with GitOps. The API can't remove rules defined in a file.
API / CLI
telltale ctl block tiktok.com --group kids --for 120 --note "homework time" # 120 minutes
telltale ctl allow cdn.example.com --device living-room-tv
telltale ctl rules # what's in effect; `ctl unrule <id>` removes one
block and allow take --dry-run. Over HTTP, PUT /api/v1/rules/{id} takes action, domain, devices or groups, forMinutes, and note; DELETE /api/v1/rules/{id} removes one. Agent tokens need config:write:rules.
- Quick rules decide before any list, on the next query; nothing is recompiled. They also decide CNAME targets.
- A rule for the device beats one for its group, which beats one for everyone. Within those, the more specific site wins, then allow over block.
- A rule with an end time stops at that moment on every node. The query log shows quick allow or quick block with the rule's note.
Quick rules aren't parental controls: there's no screen time and no content categories. Before saving, What would this have done? replays recent queries with the change.
Bedtime and other schedules
A schedule changes a group's filtering by time of day and day of week. block_all blocks every name; block_services and enable_lists add services or lists only during the windows.
Web UI
- Open Groups → Schedules and add a schedule. Write windows one per line, like
weekdays 21:00-07:00. - In the group's editor, pick the schedules it follows.
- The Groups page shows which schedules are on now.
Config file
[[group]]
name = "kids"
networks = ["192.168.20.0/24"]
schedules = ["bedtime", "school-hours"]
[[schedule]]
name = "bedtime"
action = "block_all"
tz = "Europe/Berlin" # default: the system's time zone
window = [
{ days = ["sun", "mon", "tue", "wed", "thu"], start = "21:00", end = "07:00" },
{ days = ["fri", "sat"], start = "22:30", end = "08:00" },
]
[[schedule]]
name = "school-hours"
action = "block_services" # or "enable_lists" with `lists = [...]`
services = ["tiktok", "youtube", "roblox"]
window = [{ days = ["weekdays"], start = "08:00", end = "15:00" }]
API / CLI
PUT or DELETE /api/v1/schedules/{name}, with the same fields as [[schedule]]. Agent tokens need config:write:groups; AI agents can plan one with the MCP tool plan_set_schedule.
- An end at or before the start runs past midnight:
21:00–07:00on Sunday covers Sunday night into Monday morning. - During
block_all, quick allow rules still win (a homework site can stay reachable), and local names still answer. - Schedules are checked every 15 seconds. Pausing a group's blocking pauses its schedules too.
Blocked services and safe search
Blocked services
Block a whole service for a group without finding its domains: blocked_services = ["tiktok", "roblox", "fortnite"] on the [[group]], or Groups → Manage groups → Edit → Blocked services. telltale services list (or GET /api/v1/services) shows every service and its domains. The query log names the service. To block one extra domain of a service, add a quick rule.
Safe search
safe_search = true on a group makes Google, Bing, DuckDuckGo, Yandex, and Pixabay answer with their own safe-search service. youtube_restrict is "strict", "moderate", or "off". In the UI: Groups → Manage groups → Edit → Safe search.
Both apply only to the group's devices. The service catalog and safe-search table are part of the binary and change with updates.
Try a list first
Not sure a list is safe for your network? Add it in shadow mode: it's downloaded and checked like any other, but never blocks.
[[list]]
name = "hagezi-ultimate"
url = "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/wildcard/ultimate.txt"
mode = "shadow" # enforce (the default) or shadow
- Add it in shadow mode: Mode → shadow on the Lists page, or
mode = "shadow"in the file. - Watch Would have blocked on the Lists page: per shadow list, how many queries it would have blocked, from how many devices, the names most often, and the last one.
- Turn it on when those are names you want gone: change
modetoenforce, or edit the list on the Lists page.
- The same page shows Likely over-blocking: blocked names that someone seems to want, with the lists that blocked them. They're candidates for an allowlist, or a sign a list is too aggressive.
- Counting runs off the query path and respects groups and allow lists. Counts start again at each restart.
- The API has
GET /api/v1/analytics/shadow-listsandGET /api/v1/analytics/overblocking.
Find out why
Explain shows what happens to a name for a given device: who the device is recognized as and its groups, every matching rule in every list with its file line, which rule decides, and where the query would be forwarded. It follows the same steps as the server, including quick rules, a schedule that blocks everything, rewrites, and safe search.
Web UI
- In the query log, press Why? on any query. From there, Make a quick rule fixes it.
- The Explain page answers for any name and any device.
API / CLI
telltale explain ad.doubleclick.net --client 192.168.1.50 -c telltale.toml
# client tablet (identified by ip); groups: kids
# outcome ALLOWED: allowed by list family-allow; resolved normally
# rules snapshot 1, in precedence order (* decides, - list not used by this client, ~ shadow list)
# * allow family-allow doubleclick.net (and subdomains)
# family-allow:1 @@||doubleclick.net^
# - block stevenblack ad.doubleclick.net (and subdomains)
# stevenblack:7102 0.0.0.0 ad.doubleclick.net
-t AAAAfor another query type,--macor--client-idfor a device recognized that way,--jsonfor scripts.- It reads the config and data directory, so it works whether or not the server is running.
- Over HTTP:
GET /api/v1/explain?name=ads.example.com&client=192.168.1.20.
Explain covers the query name only. To see why a CNAME target was blocked, explain the target name. More in the detailed docs.
Troubleshooting
| What you see | What to do |
|---|---|
| A site is blocked that shouldn't be | Press Why? in the query log to see which list and line blocked it, then Make a quick rule to allow it. For a lasting fix, add it to an allow list, or drop a list that keeps over-blocking. |
| A device still reaches a blocked site | Devices keep answers they already have for a few minutes. Mobile data, a VPN, a hard-coded DNS server, or a browser's own encrypted DNS go around TelltaleDNS: have your router block outgoing DNS (ports 53 and 853) to anything but TelltaleDNS. |
| A rule for one device doesn't apply | Check who Explain says the device is. Give it a fixed address (a DHCP reservation), or turn off its private Wi-Fi address for your network. |
| Nothing is blocked for a group | Its blocking may be paused: a paused group gets no blocks from lists, quick rules, or schedules. Pauses resume on their own. |
| A list says unchanged for N days | Its content hasn't changed in 30 days; its maintainer may have stopped. Replace it, or set stale_after_days = 0 on a list that rarely changes on purpose. |
| The Lists page says "N regex rules left out" | A snapshot keeps up to 1,000 regex rules ([filter] max_regexes). Prefer plain domain rules where a list offers both. |
| The process is killed while lists compile | Compiling briefly needs much more memory than serving. Leave at least 256 MiB in a container limit for list sets over about 1M names. |