Use dedicated sets without replacing your firewall
This guide targets a maintained Linux system with nftables, Python 3.11+, curl, util-linux flock and systemd. The examples were exercised with nftables 1.1.6 in an isolated user and network namespace on 2026-10-28. Check your distribution's supported kernel and nftables version in staging, including available memory for both complete feeds.
Use the dedicated inet data_shield table only if that name is unused. Keep other rulesets and firewall managers intact. If firewalld, UFW, Docker, a distribution tool or a configuration manager owns your firewall, integrate through its supported persistence mechanism: it may otherwise delete externally created tables during reload.
Both Standard and Critical are exact IPv4 sets, with no prefix aggregation. The example enforces Standard on new incoming connections to the host. The Critical set is available as an alternative policy. Loading both sets does not require two drop rules, and does not block IPv6.
Stage and validate a consistent publication
Standard
113,445 unique IPv4 addresses in this published snapshot.
https://raw.githubusercontent.com/duggytuxy/Data-Shield_IPv4_Blocklist/refs/heads/main/prod_data-shield_ipv4_blocklist.txtCritical
122,242 unique IPv4 addresses in this published snapshot.
https://raw.githubusercontent.com/duggytuxy/Data-Shield_IPv4_Blocklist/refs/heads/main/prod_critical_data-shield_ipv4_blocklist.txtPublished 28 Oct 2026, 15:31 UTC. Check live counts and SHA-256.
The Linux staging helper retrieves both complete files at an immutable GitHub commit, verifies every public IPv4, rejects duplicates and unordered records, and compares counts, bytes and SHA-256 with the public snapshot. It checks for publication changes and rejects a snapshot older than six hours. A mismatch leaves the active sets untouched. The helper never invokes nftables.
curl --fail --show-error --location --proto '=https' --tlsv1.2 \
https://data-shield.eu/downloads/data-shield-linux.py \
--output data-shield-linux.py
python3 data-shield-linux.py --help
# Review the downloaded source before installing it.
sudo install -o root -g root -m 0755 data-shield-linux.py /usr/local/sbin/data-shield-linux.pyCreate unhooked sets and apply one transaction
Keep a trusted management path and a configuration backup. First check that the table name is unused with sudo nft list tables. Save this definition to a local file, review it, and load it once using sudo nft --file FILE. It deliberately has no hook and therefore blocks nothing yet. create table fails if the dedicated table already exists.
create table inet data_shield
add set inet data_shield standard { type ipv4_addr; }
add set inet data_shield critical { type ipv4_addr; }Create /var/lib/data-shield-nftables with root ownership and mode 0700. Save the updater below to /usr/local/sbin/data-shield-nftables-update, owned by root and mode 0750, then run it once as root.
sudo install -d -o root -g root -m 0700 /var/lib/data-shield-nftables#!/bin/sh
set -eu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
umask 077
exec 9>/run/lock/data-shield-nftables.lock
flock -n 9 || exit 75
work=$(mktemp -d /var/lib/data-shield-nftables/run.XXXXXX)
trap 'rm -rf -- "$work"' EXIT
trap 'exit 1' HUP INT TERM
python3 /usr/local/sbin/data-shield-linux.py --output "$work/bundle"
nft --check --file "$work/bundle/sets.nft"
nft --file "$work/bundle/sets.nft"
# Save the successfully applied transaction for the next boot.
install -m 0600 "$work/bundle/sets.nft" "$work/current.nft"
mv -f "$work/current.nft" /var/lib/data-shield-nftables/current.nft
install -m 0600 "$work/bundle/manifest.json" "$work/manifest.json"
mv -f "$work/manifest.json" /var/lib/data-shield-nftables/manifest.jsonThe generated transaction flushes and repopulates only these two dedicated sets. nft --check checks it without applying changes; the subsequent single nft --file applies the whole batch atomically. There is no empty-set interval between profiles. If a command in that batch fails, the existing kernel sets remain. Do not split the transaction into separate shell commands and never use flush ruleset.
See the official atomic replacement guide, named sets documentation and nft manual. Other administrators can still change the table concurrently; reserve ownership and use the same update lock for your own maintenance.
Compare every loaded address
Before adding a hook, export the dedicated table as JSON. The following check rebuilds the canonical sorted file for each exact-address set and compares its full count and SHA-256 with the saved manifest. Run it as root without an update in progress. Extra, missing or unexpected JSON elements fail validation.
sudo nft --json list table inet data_shield > loaded-data-shield.json
sudo python3 - loaded-data-shield.json /var/lib/data-shield-nftables/manifest.json <<'PYVERIFY'
import hashlib, ipaddress, json, sys
loaded = json.load(open(sys.argv[1]))
manifest = json.load(open(sys.argv[2]))
for profile in ('standard', 'critical'):
matches = [item['set'] for item in loaded['nftables']
if 'set' in item and item['set'].get('family') == 'inet'
and item['set'].get('table') == 'data_shield'
and item['set'].get('name') == profile]
if len(matches) != 1 or matches[0]['type'] != 'ipv4_addr':
raise SystemExit('Unexpected set: ' + profile)
values = [ipaddress.IPv4Address(value) for value in matches[0].get('elem', [])]
raw = ''.join(str(value) + '\n' for value in sorted(values)).encode()
expected = manifest['profiles'][profile]
if (len(set(values)) != len(values) or len(values) != expected['blocked']
or hashlib.sha256(raw).hexdigest() != expected['sha256']):
raise SystemExit('Membership mismatch: ' + profile)
print(profile + ': complete membership and SHA-256 match')
PYVERIFYAttach a deliberately scoped policy
Replace WAN_INTERFACE with your actual ingress interface and review the hook priority against the existing ruleset. Load this separate file only after verifying the sets. This example blocks new IPv4 connections from Standard members to the local host; it leaves established connections alone. Choose @critical instead when that is the intended policy.
add chain inet data_shield ingress { type filter hook input priority -5; policy accept; }
add rule inet data_shield ingress iifname "WAN_INTERFACE" ct state new ip saddr @standard counter dropFor routed traffic, use a reviewed forward hook instead. Port-forwarded services and containers may traverse forwarding rather than local input. Do not add output filtering by accident. The chain documentation explains that a drop is final, while accept in one base chain does not bypass later base chains. An allow rule in another chain therefore does not necessarily exempt traffic from this drop.
For a required local management exception, add a narrowly scoped return rule at the beginning of this dedicated chain, before its drop rule, with the exact source/interface/service constraints you have approved. Returning from this chain does not bypass other firewall chains. Record the exception's expiry and verify the actual packet path. Use counters and a controlled test connection to confirm both intended blocking and unaffected traffic.
Retain the last successful set across reboot
Use your existing firewall configuration mechanism to create the dedicated table, sets and reviewed chain on boot. Include the saved transaction after creating the two sets. For a system managed directly by nftables.service, add this line in the appropriate location of its existing configuration, without replacing the file or its other rules:
include "/var/lib/data-shield-nftables/current.nft"Generate the saved file with a successful updater run before making the include mandatory. Check the complete boot configuration with nft --check --file and test a reboot in staging. Ensure the private state directory is available before firewall restoration. A live kernel update and a disk write cannot be one transaction: a disk error after a successful update can leave an older boot copy, so treat any nonzero updater exit as a failure requiring investigation.
A failed download keeps the previous active and saved sets. This maintains blocking during an outage, but also retains removed addresses until a successful refresh. Monitor the manifest timestamp and alert on the first missed update; do not treat an old set as current simply because it is nonempty.
Schedule the dedicated updater
After initial validation, create these units in /etc/systemd/system/. Adapt After=nftables.service to your actual firewall manager. Keep its own service and rules intact.
[Unit]
Description=Update verified Data-Shield nftables sets
Wants=network-online.target
After=network-online.target nftables.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/data-shield-nftables-update
TimeoutStartSec=15min
UMask=0077[Unit]
Description=Refresh Data-Shield nftables sets every two hours
[Timer]
OnBootSec=5min
OnUnitActiveSec=2h
RandomizedDelaySec=2min
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl start data-shield-nftables.service
sudo systemctl enable --now data-shield-nftables.timer
sudo systemctl list-timers data-shield-nftables.timer
sudo journalctl -u data-shield-nftables.service -n 40 --no-pagerRoll back only the dedicated table
Disable the dedicated timer, stop a running updater, and remove the Data-Shield table definition and saved-set include from your persistent configuration. After confirming nothing else uses this reserved table, remove its live rules and sets with the command below. Other tables remain intact. Check the complete persistent configuration before any reload.
sudo systemctl disable --now data-shield-nftables.timer
sudo systemctl stop data-shield-nftables.service
sudo nft delete table inet data_shieldValidation scope. Complete-feed loading, membership comparison, additions, removals and failed-batch rollback were tested in an isolated namespace. No production firewall was changed. Capacity, boot ordering, other firewall managers and real traffic enforcement require validation on your host.
BETTER DATA. TOGETHER.
Need a correction?
Report a documentation issue or request a review of an address. Keep credentials and private configuration out of public issues.