DNS Filtering & Forced Resolution¶
Blocking a domain at the resolver only works for devices that ask that resolver. Making a DNS policy apply to an entire segment takes three layers, each closing a hole the previous one leaves open: answer authoritatively, force all plaintext DNS to the local resolver, then block the encrypted transports that route around port 53. Assumes a pfSense firewall acting as gateway for the segment — see VLAN Basics in pfSense, NAT & Private IP Ranges and Firewall Rules & Aliases.
The Three Layers¶
| Layer | Mechanism | Closes | Leaves open |
|---|---|---|---|
| 1 | Resolver returns NXDOMAIN for the domain | Devices that use the DHCP-supplied resolver | A device with a hardcoded external resolver |
| 2 | NAT port forward redirecting port 53 to the firewall | Hardcoded plaintext resolvers | Encrypted DNS, which never uses port 53 |
| 3 | Firewall block on DoT/DoQ (853), plus a DoH endpoint blocklist | DoT and DoQ on their standard port; DoH partially | DoH to endpoints not on the list |
Before any of this, confirm the segment is being handed the firewall as its resolver. With the DNS Resolver or Forwarder enabled, leaving DNS Servers empty in the scope at Services → DHCP Server → interface makes the firewall advertise itself; with both disabled and the field left empty, it hands out the servers from System → General Setup instead — see DHCP Fundamentals. That reaches only devices that use the resolver they are given.
Layer 1 — Answer Authoritatively at the Resolver¶
The DNS Resolver (Unbound) accepts raw configuration directives, so a domain can be answered with NXDOMAIN locally rather than being resolved upstream.
- The field is Custom Options, on Services → DNS Resolver → General Settings. Netgate's documentation shows it as a plain text area; shipping builds place it behind a button that reveals it. Locate it on that page and confirm the label on screen rather than trusting the documented wording.
- Directives belonging to Unbound's server clause need a
server:header line at the top of the box. Netgate's guidance is to addserver:if Unbound fails to start after custom options are entered — and a resolver that will not start presents as total DNS failure across every segment. - A
local-zoneentry covers the domain and all of its subdomains, so there is no need to enumerate hostnames.
Entered into the DNS Resolver custom options field (Unbound configuration syntax, not a shell):
server:
local-zone: "example.com" always_nxdomain
Add one local-zone line per domain under the single server: header. Useful zone types:
| Type | Response | Use |
|---|---|---|
always_nxdomain |
NXDOMAIN for every name in the zone | Standard block — the client treats the name as non-existent |
refuse |
REFUSED | Signals a policy refusal rather than a missing name |
static |
NXDOMAIN or NODATA except for names given in local-data |
Block a zone but keep specific hostnames resolvable |
redirect |
All names answered with the zone's local-data |
Point an entire domain at a sinkhole address |
Save, then Apply Changes. If the resolver fails to restart, the syntax is wrong — check Status → System Logs → System → DNS Resolver.
Verify against the firewall's own resolver. From the pfSense shell (Diagnostics → Command Prompt, or an SSH session — see Enable SSH):
drill blocked.example.com @127.0.0.1
pfSense ships drill; dig belongs to the optional bind-tools package and may not be installed. Diagnostics → DNS Lookup in the GUI does the same job without a shell.
Hole this leaves: a device configured with its own hardcoded resolver address never sends a query to the firewall, so nothing above applies to it.
Layer 2 — Force All DNS to the Local Resolver¶
A destination NAT rule catches every plaintext DNS query leaving the segment and redirects it to the firewall, regardless of which resolver the client intended to use. The technique that makes this work is invert match on the destination: a destination selector normally means "traffic going to this address", and ticking the invert box reverses it to "traffic going to anything except this address". Applied to the interface's own address, the rule catches queries aimed at any external resolver while ignoring those already addressed to the firewall.
The DNS Resolver or Forwarder must be enabled and bound to Localhost or All interfaces first, or the redirect to 127.0.0.1 has nothing to land on.
At Firewall → NAT → Port Forward, click Add:
| Field | Value |
|---|---|
| Interface | The segment's interface |
| Protocol | TCP/UDP — DNS uses both (TCP vs UDP & Common Ports) |
| Source | The segment's subnet |
| Destination | The interface address, with Invert match ticked |
| Destination port range | DNS (53) to DNS (53) |
| Redirect target IP | 127.0.0.1 |
| Redirect target port | DNS (53) |
| NAT reflection | Disable |
| Filter rule association | Add associated filter rule |
- The redirect to
127.0.0.1with reflection disabled is the recipe Netgate documents for this job. - Filter rule association creates the matching pass rule automatically. NAT is applied on the way into an interface before firewall rules are evaluated, so without a pass rule the translation happens and the traffic is then blocked, which looks identical to DNS being broken.
- Repeat per segment. A port forward applies only to the interface it is created on.
Hole this leaves: encrypted DNS does not use port 53 at all, so none of it is caught.
Layer 3 — Block Encrypted DNS Transports¶
| Transport | Port | Blockable by port rule |
|---|---|---|
| DNS over TLS (DoT) | TCP 853 | Yes |
| DNS over QUIC (DoQ) | UDP 853 | Yes |
| DNS over HTTPS (DoH) | TCP 443 | No |
At Firewall → Rules → segment interface, add a block rule with Protocol set to TCP/UDP and Destination port range 853 to 853. One rule covers both DoT and DoQ. Set Source to the segment's subnet and Destination to any.
How a client reacts to the block depends on its configuration. Clients using encrypted DNS opportunistically fall back to plaintext port 53, which layer 2 then catches. Clients configured to require encrypted DNS fail to resolve at all rather than falling back.
DoH runs over TCP 443 alongside ordinary web traffic and is indistinguishable from it at the port level, so blocking 443 would block the web with it. The only port-level approach is a blocklist of known DoH endpoint addresses, applied as an alias and referenced by a block rule — see Firewall Rules & Aliases. Such a list is ongoing maintenance, is never complete, and any endpoint not on it will work.
Rule Ordering¶
Firewall rules and NAT rules are both matched top-down, first match wins. Three orderings matter here:
- The pass rule permitting DNS to the firewall must sit above any isolation rule that blocks the segment from reaching the firewall or other local subnets — see Management Plane Isolation. An automatically created associated rule is appended to the end of the interface's rule list, so it will sit below an existing blanket block; check its position after saving the port forward and move it up.
- The 853 block rule must sit above any broad pass rule, or the pass rule matches first and the block never applies.
- Any exception to the redirect must sit above the redirect itself in the port forward list.
Exceptions¶
Forcing DNS applies to every device on the segment. Anything that legitimately requires a specific external resolver — an appliance validating against its vendor's resolver, a device with a licence check tied to a named server, a management system with a fixed configuration — must be excepted before the forced rules go in, or it breaks the moment they are applied.
A firewall pass rule will not create the exception. NAT is applied before filtering, so the query is already redirected by the time any pass rule is evaluated. The exception has to be made in NAT:
- Add a second port forward matching that host's address, with No RDR (NOT) ticked, positioned above the redirect rule. Netgate describes this option as preventing redirection of packets that would otherwise match a port forward.
- Alternatively, narrow the redirect's Source to an alias that omits the host.
- Record the exception alongside the rest of the configuration — see Configuration Management.
Verification¶
Test from a client on the affected segment, not from the firewall. A query run on the firewall never crosses the port forward, so it only ever proves layer 1.
From a Linux or macOS shell on a client in the segment:
# Should return NXDOMAIN
dig blocked.example.com
# Same query aimed at an external resolver — should also return NXDOMAIN,
# proving the port forward redirected it
dig @1.1.1.1 blocked.example.com
# Control: an unblocked name should resolve normally
dig example.org
- A blocked answer shows
status: NXDOMAINin the header andANSWER: 0in the flags line — no records returned. - A normal answer shows
status: NOERRORwith one or more records in the ANSWER SECTION. - The second query is the one that proves layer 2. If it returns a real answer, the redirect is not matching — check the invert match tick, the interface, and that the protocol is TCP/UDP rather than TCP alone.
digships with macOS; on Linux it may need thednsutilsorbind-utilspackage.nslookup blocked.example.comgives the same verdict in less detail.
To confirm the 853 block, attempt a TCP connection to a known DoT resolver on port 853 from the client. A pfSense Block rule silently drops, so the attempt times out rather than being refused; Reject would return an immediate refusal instead. More options in Diagnostic Commands and Packet Capture Method.