CarbolicAcid is a CoreDNS plugin for filtering poisoned DNS responses.
To enable CarbolicAcid as a CoreDNS plugin, please refer to the official CoreDNS documentation:
https://coredns.io/2017/07/25/compile-time-enabling-or-disabling-plugins/
Named in tribute to Joseph Lister, the British surgeon who first used carbolic acid for surgical antisepsis in 1865.
CarbolicAcid intercepts DNS responses containing poisoned IP addresses and prevents them from reaching downstream clients or entering the CoreDNS cache.
Examples include:
127.0.0.1- Private networks such as
10.0.0.0/8or192.168.0.0/16 ::1100::/64(blackhole)- Other commonly abused ranges
Supported response actions:
drop— silently discard the responseservfail— returnSERVFAILnxdomain— returnNXDOMAINbypass— pass upstream response through unchanged and log it (audit‑only)
Each plugin instance uses a dual‑table model consisting of a blockList and an allowList:
-
preset ianaloads IANA‑defined special‑use and reserved prefixes
(IPv4: https://www.iana.org/assignments/iana-ipv4-special-registry/
IPv6: https://www.iana.org/assignments/iana-ipv6-special-registry/)
These prefixes are inserted directly into the blockList. -
preset allipinserts0.0.0.0/0and::/0into the blockList, treating all IPv4/IPv6 addresses as blockable by default.
Exclusions must be used to define the allowed ranges. -
blockappends additional prefixes to the blockList. -
excludemay only carve out sub‑prefixes from its parent rule:- In
preset iana { exclude X },Xmust be a subnet of the IANA prefix set - In
block 10.0.0.0/8 { exclude X },Xmust be a subnet of10.0.0.0/8 excludedoes not operate across nodes and does not affect other presets or blocks- If an exclude is not a valid subnet of its parent, the parent rule is skipped during initialization and the next preset/block is processed
- In
Matching behavior:
- allowList has priority — if any A/AAAA record matches allowList, the entire response is allowed and blockList is skipped
- If allowList does not match, blockList is evaluated; any match triggers the configured response action
- When CarbolicAcid is placed before the Cache plugin, intercepted responses will not enter the CoreDNS cache
CarbolicAcid is intentionally designed as an old‑school, assembly‑style, non‑smart, ISP‑grade filter that operates only on IP addresses (A/AAAA):
- It does not validate DNSSEC
- It does not filter by domain or FQDN
- It does not attempt to guess which domains are “safe”
- It does not protect users from misconfiguration
- It does not partially filter responses —
one poisoned A/AAAA record poisons the entire response
Please ensure:
- System‑critical DNS names are not accidentally blocked
- Windows users should be especially cautious
- Read this document carefully and configure according to your environment
If you enable maximum strictness without proper exclusions, you may experience true
“carbolic‑acid‑style DNS sterilization” — so clean that even your system’s own DNS won’t survive.
CarbolicAcid is a pure response filter:
- It only inspects
A/AAAArecords - It only matches IPs and CIDRs
- It only returns what you configure:
NXDOMAIN/SERVFAIL/DROP/BYPASS - It provides no special handling for domains, FQDNs, clients, or protocol stacks
It will not:
- Detect whether your configuration is dangerous
- Protect the system from your mistakes
- Apply “helpful corrections” or “safety fallbacks”
Meaning:
If you accidentally block DNS names your system depends on, the system may behave very badly.
If misconfigured:
You may relive the DOS era where “writing past array bounds kills the whole machine.”
The following content is extremely important. Please read carefully.
During boot and short‑hostname resolution, both the OS and LAN clients will query the server’s own FQDN.
These queries must be allowed, or you may experience:
- Slow boot
- Network instability
- Windows UI freeze or partial deadlock
You must configure the server’s FQDN as a dedicated CoreDNS zone.
- In domain environments:
<hostname>.<domain> - In non‑domain environments:
<hostname>.
Example:
"server.example.com" {
log
}
This ensures hostname‑related queries do not fall into the root zone, where CarbolicAcid may block them.
Important notes:
- The server’s FQDN is not
localhost - It is not the Windows NetBIOS name
- It is not the search‑domain
- FQDN may or may not include the search‑domain depending on:
- AD membership
- DHCP option 15 / 119
- Local resolver configuration
A later section explains how to correctly obtain the Windows FQDN.
CarbolicAcid’s safety depends on separating:
- The server’s own FQDN zone (must be allowed)
- The root zone (where poisoned responses are intercepted)
Windows performs numerous DNS lookups during boot and login:
- WinHTTP
- RPC
- SMB
- LSA
- Winlogon
- Certificate chain validation
- NCSI
- WPAD
- IPv6 reverse lookups
- And more
Some hostname lookups must succeed, or Windows may:
- Freeze temporarily
- Stop responding to mouse/keyboard input
- Drop RDP sessions
- Fail to start services
- Partially deadlock the network stack
- Block the DNS Client
- Enter a “half‑deadlock” state for several minutes
After recovery, you may find CoreDNS / CarbolicAcid errors in the logs.
If:
- You enable
preset ianaor manually block127.0.0.1/32or::1/128, and - Windows points its DNS to this CoreDNS + CarbolicAcid instance
Then:
Windows will query system‑critical names.
If CarbolicAcid blocks them, Windows may freeze or behave unpredictably.
But don't worry — with enough patience, Windows may eventually unfreeze itself.
Until then, even the logging subsystem will remain completely silent.
I've experienced this myself.
Standalone Windows (WORKGROUP):
$env:COMPUTERNAMEDomain‑joined Windows:
$env:COMPUTERNAME + "." + (Get-CimInstance Win32_ComputerSystem).Domain⚠ Note:
On standalone Windows, the above may output <HOSTNAME>.WORKGROUP,
but WORKGROUP is not a DNS domain and must not be used as the FQDN.
- Create a dedicated zone for the server’s FQDN and do NOT block 127.0.0.1 / ::1 in that zone
- Point Windows to another DNS server (reduces protection; not recommended)
- Use
exclude 127.0.0.1/32andexclude ::1/128in the root zone
(reduces protection; not recommended)
These systems usually resolve their own hostname via /etc/hosts.
However, if /etc/nsswitch.conf contains:
hosts: dns files
then DNS may be used during boot.
Typical queries include:
localhost/::1- The system hostname
.local(mDNS)
Use:
hostname -f
to obtain the FQDN.
If CarbolicAcid blocks these lookups:
- Boot may slow down
- Some services may delay
- But usually not as severe as Windows
- Create a dedicated zone for the server’s FQDN and allow 127.0.0.1
- Ensure
/etc/hostscontains hostname → 127.0.0.1 / ::1 - Or (not recommended) exclude 127.0.0.1 / ::1 in the root zone
LAN clients generate their own FQDNs by appending the search‑domain:
pc01 → pc01.<search-domain>
These queries normally do not resolve to 127.0.0.1 and therefore are not blocked by CarbolicAcid.
When using CarbolicAcid:
- Always allow the server’s own FQDN
- Always split CoreDNS into at least two zones:
- One for the server’s FQDN (safe zone)
- One for the root zone (with CarbolicAcid enabled)
Using only a root zone with CarbolicAcid enabled and blocking 127.0.0.1 is almost guaranteed to cause system instability — a true “carbolic‑acid DNS sterilization.”
Example minimal configuration:
"your-server-fqdn" {
carbolicacid {
preset allip {
exclude 127.0.0.1
exclude ::1
}
responses bypass
}
}
. {
forward . 1.1.1.1
carbolicacid {
preset iana
responses nxdomain
}
}
DNS is case‑insensitive but case‑preserving.
CoreDNS normalizes Corefile FQDNs to lowercase.
Use lowercase for zone names.
⚠ Reminder:
"your-server-fqdn" {}
is an empty zone, and CoreDNS will fall back to the root zone —
meaning your server’s own hostname queries will still be filtered by CarbolicAcid,
leading to a full carbolic‑acid‑style DNS sterilization.
carbolicacid
Equivalent to:
carbolicacid {
preset none
responses drop
}
Meaning:
- No preset blockList (
preset none) - Default action is
drop - With no
block, no IPs are actually blocked
carbolicacid {
preset iana
}
preset iana loads the IANA‑defined special‑use/reserved prefixes
(e.g., private networks, loopback, link‑local, documentation ranges).
You may refine them:
carbolicacid {
preset iana { exclude 169.254.0.0/16 }
block 192.88.99.0/24
}
Conceptually:
preset iana+blocktogether form the candidate blockListexcludeextracts sub‑prefixes into the allowList- Matching order: allowList → blockList
carbolicacid {
preset allip
}
Expands to:
0.0.0.0/0::/0
Meaning: all IPv4 + IPv6 addresses are blockable.
Without exclude, every A/AAAA response will be blocked unless allowed.
Typical use cases:
- Audit mode with
responses bypass - “Default deny” policies with carefully defined exclusions
Example:
carbolicacid {
preset allip {
exclude 10.0.0.0/8
exclude 192.168.0.0/16
}
responses drop
}
Meaning:
- Everything except 10.0.0.0/8 and 192.168.0.0/16 is treated as poisoned
- Useful for strict internal DNS filtering (use with caution)
carbolicacid {
preset none
}
- Loads no preset blockList
- You must use
blockto block anything - Useful if you want full manual control
preset none cannot have exclude (no parent set).
If you attach an exclude, initialization fails.
carbolicacid {
block 198.51.100.0/24
}
Notes:
blockandexcludemust be valid CIDRs:- Explicit:
1.2.3.4/32,2001:db8::1/128 - Implicit:
1.2.3.4→/32,2001:db8::1→/128
- Explicit:
- All prefixes are validated via
net.ParseCIDR; invalid entries cause initialization failure
block may be combined with presets:
carbolicacid {
preset iana
block 224.0.0.0/4
}
carbolicacid {
preset iana { exclude 169.254.0.0/16 }
}
Subset rule:
Every
excludemust be a strict subnet of its parent rule (preset or block).
Otherwise, initialization fails.
Meaning:
- You may carve out a smaller prefix from a preset/block:
- If
preset ianaincludes 10.0.0.0/8 - You may
exclude 10.0.1.0/24
- If
- You cannot exclude from an empty set:
exclude 1.2.3.0/24without any preset/block → error
- You cannot exclude unrelated prefixes:
preset iana+exclude 8.8.8.0/24→ error
This is a hard safety rule:
- Prevents “I thought I excluded it, but nothing happened” confusion
- Misconfiguration is not silently ignored — initialization fails
carbolicacid {
responses drop
}
or:
responses servfail
responses nxdomain
responses bypass
Meaning:
drop— discard response, do not return or cacheservfail— returnSERVFAILnxdomain— returnNXDOMAINbypass— pass upstream response unchanged and log it- Useful for audit/observation, not protection
- Often paired with
preset allipfor full‑visibility mode
| Option | Default | Meaning |
|---|---|---|
| preset | none | No preset blockList |
| responses | drop | Drop poisoned responses |
| block | empty | No custom blocks |
| exclude | empty | No exclusions |
With preset none and no block:
- No IPs are blocked
- CarbolicAcid effectively “does nothing”
CarbolicAcid uses a dual‑table model with allowList priority:
-
allowList first
If any A/AAAA record matches allowList, the entire response is allowed
and blockList is skipped. -
Otherwise, evaluate blockList
If any record matches blockList, the configured action is applied. -
If placed before the Cache plugin,
blocked responses will not enter the CoreDNS cache. -
If no
excludeis configured, allowList is empty,
and matching skips allowList entirely. -
If
preset allipis used, the instance enters full‑block mode:- blockList is skipped entirely
- If exclusions exist, they form the allowList
- If no exclusions exist, allowList is empty and all responses are blocked
In other words:preset allipperforms allowList‑only matching.
carbolicacid {
preset [none|iana|allip] { exclude CIDR }
block CIDR { exclude CIDR }
responses [drop|servfail|nxdomain|bypass]
}
Rules:
- CIDRs may be explicit or implicit (
/32or/128) preset nonecannot haveexclude- Every
excludemust be a subnet of its parent - In a DNS response:
- Any poisoned A/AAAA record poisons the entire response
- The entire response is processed according to
responses - No partial filtering
- allowList overrides blockList
-
Automatic mask completion
127.0.0.1→/32::1→/128- Invalid CIDRs cause initialization failure
-
No partial RR filtering
- One poisoned A/AAAA record → entire response poisoned
- No “remove one record and forward the rest” behavior
-
Non‑A/AAAA records are ignored
CNAME,TXT,MX,SRV, etc. are not inspected
-
Strict subset enforcement for exclude
- Cannot exclude from empty sets
- Cannot exclude unrelated prefixes
- Misconfiguration causes initialization failure
CarbolicAcid aims to be:
- Explicit in behavior
- Honest about safety
- Zero‑tolerance for misconfiguration
- Lightweight and predictable for ISP/high‑load environments
If unsure, test your Corefile in a staging environment before deploying to production.