BIND RPZ Content Filtering
DNS-based content filtering using BIND Response Policy Zones (RPZ). Block YouTube, social media, or any domain at the DNS layer.
Overview
| Item | Value |
|---|---|
Primary DNS |
bind-01 (10.50.1.90) |
Secondary DNS |
bind-02 (10.50.1.91) |
RPZ Zone File |
/var/named/rpz.zone |
Effectiveness |
~70-80% (bypassed by DoH/VPN) |
Time to Deploy |
~1 hour |
What is RPZ?
Response Policy Zones (RPZ) let you override DNS responses for specific domains. When a client queries youtube.com, BIND returns NXDOMAIN (domain doesn’t exist) instead of the real IP.
Normal DNS:
Client → "youtube.com?" → BIND → "142.250.x.x"
Client connects to YouTube ✓
With RPZ:
Client → "youtube.com?" → BIND → "NXDOMAIN"
Client gets "site not found" ✗
Limitations:
-
DNS-over-HTTPS (DoH) bypasses this - browser queries Cloudflare/Google directly
-
VPNs bypass this - DNS goes through VPN tunnel
-
Hardcoded IPs bypass this - apps that don’t use DNS
Mitigations (Phase 2):
-
Block DoH providers at VyOS firewall
-
Force all DNS through BIND (redirect port 53)
Phase 1: Create RPZ Zone on bind-01
1.0 Pre-flight
# Load credentials and sign SSH cert
ds d000 dev/vault && vault-ssh-sign
# SSH to bind-01
ssh {bind-name}
1.1 Verify Current State
# Check named is running
systemctl is-active named
# Check no existing RPZ config
sudo grep -n "response-policy" /etc/named.conf || echo "No RPZ configured (expected)"
# Check zone directory
ls -la {bind-zone-dir}/
1.2 Create RPZ Zone File
# Create the RPZ zone file
sudo tee /var/named/rpz.zone << 'EOF'
$TTL 300
@ IN SOA bind-01.inside.domusdigitalis.dev. admin.inside.domusdigitalis.dev. (
2026031601 ; Serial (YYYYMMDDNN)
3600 ; Refresh (1 hour)
600 ; Retry (10 min)
86400 ; Expire (1 day)
300 ; Minimum TTL (5 min)
)
IN NS bind-01.inside.domusdigitalis.dev.
; ============================================================
; BLOCKED DOMAINS
; Action: CNAME . (returns NXDOMAIN)
; ============================================================
; --- YouTube ---
youtube.com CNAME .
*.youtube.com CNAME .
youtubei.googleapis.com CNAME .
youtube-nocookie.com CNAME .
*.youtube-nocookie.com CNAME .
youtu.be CNAME .
yt3.ggpht.com CNAME .
*.yt3.ggpht.com CNAME .
ytimg.com CNAME .
*.ytimg.com CNAME .
googlevideo.com CNAME .
*.googlevideo.com CNAME .
; --- Add more domains below ---
; tiktok.com CNAME .
; *.tiktok.com CNAME .
; instagram.com CNAME .
; *.instagram.com CNAME .
EOF
Phase 2: Configure named.conf
2.2 Add RPZ Zone Definition
Add this zone block BEFORE the closing of the options section. Find the right location:
# Find where to add (after the last zone block)
sudo grep -n "^zone\|^};" /etc/named.conf | tail -10
# Add RPZ zone definition (add after last zone block)
sudo tee -a /etc/named.conf << 'EOF'
// ===========================================
// Response Policy Zone (Content Filtering)
// ===========================================
zone "rpz" IN {
type master;
file "rpz.zone";
allow-query { none; }; // RPZ is internal only
allow-transfer { 10.50.1.91; }; // Replicate to bind-02
also-notify { 10.50.1.91; }; // Notify on updates
};
EOF
2.3 Add response-policy Statement
The response-policy statement goes inside the options { } block. Find the right location:
# Find the options block closing brace
sudo awk '/^options/,/^\};/' /etc/named.conf | tail -5
You need to add response-policy { zone "rpz"; }; BEFORE the closing }; of the options block.
# Find line number of options closing brace
LINE=$(sudo grep -n "^};" /etc/named.conf | head -1 | cut -d: -f1)
echo "Options block ends at line: $LINE"
# Insert response-policy before that line
sudo sed -i "${LINE}i\\ // RPZ Content Filtering\\n response-policy { zone \"rpz\"; };" /etc/named.conf
2.4 Verify Configuration
# Check syntax
sudo named-checkconf /etc/named.conf
If no output, syntax is valid. If errors, fix before proceeding.
# Verify response-policy is in options block
sudo awk '/^options/,/^\};/' /etc/named.conf | grep -A1 "response-policy"
// RPZ Content Filtering
response-policy { zone "rpz"; };
# Verify RPZ zone is defined
sudo grep -A5 'zone "rpz"' /etc/named.conf
Phase 3: Restart and Test on bind-01
3.1 Restart BIND
sudo systemctl restart named
# Verify running
systemctl is-active named
# Check for errors
sudo journalctl -u named --since "1 minute ago" --no-pager | grep -iE "error|fail|rpz"
3.2 Test RPZ is Loaded
# Check RPZ zone is loaded
sudo rndc zonestatus rpz
name: rpz
type: master
serial: 2026031601
3.3 Test Blocking
# Test YouTube (should return NXDOMAIN)
dig @localhost youtube.com +short
(empty - NXDOMAIN)
# Verify NXDOMAIN status
dig @localhost youtube.com | grep -E "status:|ANSWER"
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 12345
;; ANSWER SECTION:
youtube.com. 300 IN CNAME .
# Test non-blocked domain still works
dig @localhost google.com +short
142.250.x.x (actual IP)
Phase 4: Replicate to bind-02
4.2 Add RPZ Zone as Secondary
# Backup config
sudo cp /etc/named.conf /etc/named.conf.pre-rpz-$(date +%Y%m%d)
# Add RPZ zone (secondary/slave)
sudo tee -a /etc/named.conf << 'EOF'
// ===========================================
// Response Policy Zone (Content Filtering)
// Replicated from bind-01
// ===========================================
zone "rpz" IN {
type slave;
file "slaves/rpz.zone";
masters { 10.50.1.90; };
allow-query { none; };
};
EOF
4.3 Add response-policy to options
Same process as bind-01 - find the options closing brace and insert before it:
LINE=$(sudo grep -n "^};" /etc/named.conf | head -1 | cut -d: -f1)
sudo sed -i "${LINE}i\\ // RPZ Content Filtering\\n response-policy { zone \"rpz\"; };" /etc/named.conf
Phase 5: End-to-End Validation
From your workstation (uses VyOS DHCP → BIND):
# Test YouTube blocked
dig youtube.com +short
# Expected: empty
# Test in browser
curl -I https://youtube.com 2>&1 | head -5
# Expected: "Could not resolve host"
# Verify using both DNS servers
dig @10.50.1.90 youtube.com +short
dig @10.50.1.91 youtube.com +short
# Both should return empty (NXDOMAIN)
Operations: Adding/Removing Domains
Add a Domain to Block
# SSH to bind-01
ssh {bind-name}
# Add domain (example: TikTok)
sudo tee -a /var/named/rpz.zone << 'EOF'
; --- TikTok ---
tiktok.com CNAME .
*.tiktok.com CNAME .
tiktokv.com CNAME .
*.tiktokv.com CNAME .
EOF
# Increment serial (CRITICAL - or zone transfer won't happen)
# Find current serial and increment
CURRENT=$(sudo grep -oP '\d{10}' /var/named/rpz.zone | head -1)
NEW=$((CURRENT + 1))
sudo sed -i "s/$CURRENT/$NEW/" /var/named/rpz.zone
echo "Serial: $CURRENT → $NEW"
# Reload zone
sudo rndc reload rpz
# Verify
dig @localhost tiktok.com +short
# Expected: empty
Remove a Domain (Unblock)
# Comment out the domain in rpz.zone
sudo sed -i 's/^youtube.com/; youtube.com/' /var/named/rpz.zone
sudo sed -i 's/^\*.youtube.com/; *.youtube.com/' /var/named/rpz.zone
# Increment serial
CURRENT=$(sudo grep -oP '\d{10}' /var/named/rpz.zone | head -1)
NEW=$((CURRENT + 1))
sudo sed -i "s/$CURRENT/$NEW/" /var/named/rpz.zone
sudo rndc reload rpz
Phase 6: Block DoH Bypass (Optional)
DNS-over-HTTPS (DoH) bypasses RPZ. Block common DoH providers at VyOS:
# SSH to vyos-01
ssh vyos-01
configure
# Block Cloudflare DoH
set firewall ipv4 name WAN_LOCAL rule 100 description "Block Cloudflare DoH"
set firewall ipv4 name WAN_LOCAL rule 100 action drop
set firewall ipv4 name WAN_LOCAL rule 100 destination address 1.1.1.1
set firewall ipv4 name WAN_LOCAL rule 100 destination port 443
set firewall ipv4 name WAN_LOCAL rule 100 protocol tcp
set firewall ipv4 name WAN_LOCAL rule 101 description "Block Cloudflare DoH IPv6"
set firewall ipv4 name WAN_LOCAL rule 101 action drop
set firewall ipv4 name WAN_LOCAL rule 101 destination address 1.0.0.1
set firewall ipv4 name WAN_LOCAL rule 101 destination port 443
set firewall ipv4 name WAN_LOCAL rule 101 protocol tcp
# Block Google DoH
set firewall ipv4 name WAN_LOCAL rule 102 description "Block Google DoH"
set firewall ipv4 name WAN_LOCAL rule 102 action drop
set firewall ipv4 name WAN_LOCAL rule 102 destination address 8.8.8.8
set firewall ipv4 name WAN_LOCAL rule 102 destination port 443
set firewall ipv4 name WAN_LOCAL rule 102 protocol tcp
set firewall ipv4 name WAN_LOCAL rule 103 description "Block Google DoH secondary"
set firewall ipv4 name WAN_LOCAL rule 103 action drop
set firewall ipv4 name WAN_LOCAL rule 103 destination address 8.8.4.4
set firewall ipv4 name WAN_LOCAL rule 103 destination port 443
set firewall ipv4 name WAN_LOCAL rule 103 protocol tcp
commit
save
exit
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
|
Syntax error in named.conf |
Check error message, fix syntax |
RPZ zone not loading |
Zone file syntax error |
|
Domain still resolves |
RPZ not in response-policy |
Verify |
bind-02 not getting updates |
Serial not incremented |
Increment serial, |
Zone transfer failed |
Firewall blocking 53/tcp |
Check VyOS rules for bind-01 → bind-02 |
Quick Reference
| Task | Command |
|---|---|
Check RPZ status |
|
Reload after changes |
|
Test blocking |
|
View zone file |
|
Check logs |
|
Common Block Lists
Social Media
facebook.com CNAME .
*.facebook.com CNAME .
instagram.com CNAME .
*.instagram.com CNAME .
tiktok.com CNAME .
*.tiktok.com CNAME .
twitter.com CNAME .
*.twitter.com CNAME .
x.com CNAME .
*.x.com CNAME .
snapchat.com CNAME .
*.snapchat.com CNAME .