> ## Content Index
> Fetch the complete content index at: https://www.novea.cc/llms.txt
> Use this file to discover other available public pages before exploring further.

# dnscrypt-proxy: Automating the Firewall with nftables Sets
- URL: https://www.novea.cc/dnscrypt-proxy-automating-the-firewall-with-nftables-sets/
- Published: 2026-09-05T17:44:44.000Z
- Updated: 2026-09-05T17:44:44.000Z
- Description: Blocking domains by hand is a losing game. Dynamic nftables sets turn a blocklist into a live firewall rule.
- Author: Ethan Nicolas
- Tags: Linux, Networking, DNS

Blocking a domain by hand works exactly once. The second time you add a rule, you start wondering whether there is a better way — and there is. An nftables set can be updated while the firewall is running, which means a blocklist becomes a live rule instead of a pile of manual commands.

## The pattern: a set, and something to fill it

Instead of one rule per domain, you create a named set and point a single rule at it. Then you only ever add or remove elements:

```
nft add set inet filter blocklist { type ipv4_addr; flags interval; }
nft add rule inet filter forward ip daddr @blocklist drop
nft add rule inet filter input ip daddr @blocklist drop
```

Now anything you add to the set is blocked by the same rule. The trick is feeding the set automatically, because a static list is just manual work with extra steps.

## A script that feeds the firewall

The clean way is a small script that resolves a domain list and pushes the addresses into the set:

```
for DOMAIN in $(cat /etc/blocklist.domains); do
  getent ahostsv4 "$DOMAIN" | awk '{print $1}' | sort -u
done
```

Feed that output to nftables and you have a live blocklist. Run it once by hand, then on a schedule. The full script, with the set refresh, looks like this:

```
#!/usr/bin/env bash
# /usr/local/sbin/update-blocklist.sh
set -euo pipefail
nft flush set inet filter blocklist
for DOMAIN in $(cat /etc/blocklist.domains); do
  getent ahostsv4 "$DOMAIN" | awk '{print $1}' | sort -u
done | while read -r IP; do
  nft add element inet filter blocklist { "$IP" }
done
```

Then hook it to a systemd timer: a `.service` unit that runs the script plus a `.timer` unit on `OnCalendar=hourly`, enabled with `systemctl enable --now update-blocklist.timer`. The `flush` before the reload keeps the set in sync when a domain's IPs change between runs.

The `flush` first, then the reload, keeps the set in sync when a domain's IPs change between runs. Run it once by hand, then from a systemd timer every hour — the timer unit is three lines and does not need showing here.

Domains that resolve to several IPs (hello, CDNs) get every address blocked, and the block stays correct as long as the DNS does not change — which is why the hourly refresh matters. When an address changes, a fresh run replaces the set:

```
# clear and reload, safely, from the same script:
nft flush set inet filter blocklist
```

nftables makes the hard part — editing live firewall state without dropping your SSH session — a two-command operation. The set pattern is the same one behind fail2ban-style tools: the firewall stays simple, and the logic lives in a script you can read.

One honest caveat: DNS-based blocking is a convenience filter, not a security boundary. Anything that bypasses your DNS (a hardcoded IP, a VPN, DoH) walks around it. Use it to keep noise out of your network, and keep your real security in the places that do not depend on name resolution.