Tired of Annoying Ads and Privacy-Invading Trackers? Here’s How to Take Control
Are you frustrated with pop-up ads in your browser or ads cluttering news articles? Not to mention those pesky privacy-invading trackers? You’re not alone — but the good news is, you don’t have to put up with them.
The first step is to enable privacy settings in your browsers and install an ad-blocking extension like uBlock Origin. This will block ads in your browser, but that’s just one part of the solution. You can also take things a step further by setting up a DNS blackhole on your local network. This will send ads and trackers to a dead-end, keeping your devices protected across your entire home network.
A DNS blackhole works by redirecting requests for known ad-serving and tracker domains to a non-existent address, effectively blocking them. There are many options available, from free, DIY solutions to paid DNS subscription services.
If you’re running your own home network with a Unix-based server, you can easily integrate a DNS blackhole into a local BIND DNS server on your router — and best of all, it’s completely free. If you’re already using BIND and know your way around it, here’s how I set up a DNS blackhole on my FreeBSD server.
Setup
Note: Depending on how you have things set up on your server, you will likely need to provide the correct paths to things, as they surely differ from mine.
Quick overview: You’re going to create a directory, put two shell scripts into it, create a config file and two empty files, tweak your BIND settings, and you’re ready! Sample files are included below.
- Create
/usr/local/etc/dns-blackhole
directory (several things go in here) - Copy
dns-blackhole-update.sh
into this new directory - Copy the
dns-blocker.sh
utility script here as well - Create a
dns-blackhole.conf
config file - Create a
allowed_hosts
file (can be empty) - Create a
local_blocked_hosts
file (can be empty) - Adjust BIND Configuration (steps below)
- Add an entry in crontab or periodic to automate updates
Operation
- Run
dns-blocker.sh off
to initialize the control switch (symlink) - Run
dns-blackhole-update.sh
to make sure it works before automating - Run
dns-blocker.sh on
to enable the blackhole - Run
dns-blocker.sh off
whenever you need to disable it (e.g., updating your LG smart TV)
Adjust BIND Configuration
- Add this entry inside of the
options
block innamed.conf
:
response-policy { zone "rpz"; };
- Add this to the end of
named.conf
:
include "/usr/local/etc/namedb/named.blocker";
- Create the
named.rpz
file:
zone "rpz" {
type master;
file "named.blacklist.rpz";
};
- Create the
named.blacklist-disabled
file:
zone "rpz" {
type master;
file "/dev/null";
};
dns-blackhole-update.sh
Copy this to your dns_blackhole_dir
directory.
#!/bin/sh
#
# dns-blackhole-update.sh - Fetches and builds DNS Response Policy Zone (RPZ)
#
# Usage:
# dns-blackhole-update.sh [config_file]
#
# Requirements:
# - FreeBSD environment with BIND DNS server
# - /usr/bin/fetch
# - /usr/local/etc/dns-blackhole directory structure with:
# - local_blocked_hosts - list of hosts to block ("0.0.0.0 hostname")
# - allowed_hosts - list of hostnames to allow ("hostname")
# - Service 'named' available to restart after updating
#
# Config file sample format:
# dns_blackhole_dir="/usr/local/etc/dns-blackhole"
# named_dir="/usr/local/etc/namedb"
# dns_server_hostname="ns.space.lan"
# retry_seconds="10"
# max_attempts="3"
#
# Explanation of config settings:
# dns_blackhole_dir - Directory for input and output RPZ files
# named_dir - Directory for storing the bind zone files
# dns_server_hostname - The DNS NS record to use in the RPZ
# retry_seconds - Seconds to wait between fetch retries (default: 10)
# max_attempts - Maximum number of fetch attempts (default: 3)
set -eu # Exit on error and undefined variables
cd "$(dirname "$0")";
config_file="${1:-dns-blackhole.conf}"
if [ -f "$config_file" ]; then
. "$config_file"
else
echo "Config file '$config_file' not found. Exiting." >&2
exit 1
fi
cd $dns_blackhole_dir
if [ -z "$named_dir" ] || [ -z "$dns_server_hostname" ] || [ -z "$retry_seconds" ] || [ -z "$max_attempts" ]; then
echo "Missing required config settings. Exiting." >&2
exit 1
fi
echo "Fetching Steven Black master hosts..."
attempt=0
url="https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
output="steven_black_hosts"
while [ "$attempt" -lt "$max_attempts" ]; do
echo "Fetching (attempt $(($attempt + 1))/$max_attempts)..."
if /usr/bin/fetch -q -m -T 30 -o "$output" "$url"; then
echo "Fetch succeeded."
break
fi
attempt=$((attempt + 1))
if [ "$attempt" -lt "$max_attempts" ]; then
echo "Fetch failed. Retrying in $retry_seconds seconds..." >&2
sleep "$retry_seconds"
else
echo "Fetch failed after $max_attempts attempts. Giving up." >&2
exit 1
fi
done
echo "Optimizing hosts list..."
cat steven_black_hosts local_blocked_hosts | \
sed -e 's/^[[:space:]]*//' | \
grep -v '^#' | \
awk '{print $2}' | \
grep -Ev '^(0\.0\.0\.0|localhost)$' | \
grep -v '^$' | \
sort -u > optimized_hosts
echo "Excluding allowed hosts..."
sort allowed_hosts | comm -23 optimized_hosts - > blocked_hosts
echo "Building zone file..."
serial_number=$(date +%Y%m%d%H%M%S)
cat <<EOF > named.blacklist.rpz
; [built by $0]
\$TTL 604800
@ IN SOA localhost. root.localhost. (
; YYYYMMDDHHMMSS
$serial_number ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Minimum
IN NS $dns_server_hostname.
EOF
sed -E 's/^(.*)$/\1 CNAME ./' blocked_hosts >> named.blacklist.rpz
cp named.blacklist.rpz "$named_dir"
service named restart
Sample dns-blackhole.conf
Create this in your dns_blackhole_dir
directory.
dns_blackhole_dir="/usr/local/etc/dns-blackhole"
named_dir="/usr/local/etc/namedb"
dns_server_hostname="ns.yourdomain.local"
retry_seconds="10"
max_attempts="3"
Explanation:
dns_blackhole_dir
- Directory for various input/output support files
named_dir
- Directory for storing the
named.blacklist.rpz
file dns_server_hostname
- The hostname of your DNS server for the NS record in the RPZ
retry_seconds
- Seconds to wait between fetch retries (default: 10)
max_attempts
- Maximum number of fetch attempts (default: 3)
Sample allowed_hosts
This file holds hostnames you want to omit from the DNS blackhole. This goes in your dns_blackhole_dir
directory.
api.ipstack.com
freeipapi.com
geolocation-db.com
ip-api.com
ipapi.co
ipinfo.io
ipstack.com
Sample local_blocked_hosts
This file holds custom host entries you want to add to the DNS blackhole. This goes in your dns_blackhole_dir
directory.
#
# Block LG TV ads
#
# (see https://gist.github.com/wassname/78eeaaad299dc4cddd04e372f20a9aa7)
0.0.0.0 lgappstv.com
0.0.0.0 ngfts.lge.com
0.0.0.0 e6114.e21.akamaiedge.net
0.0.0.0 us.ibs.lgappstv.com
0.0.0.0 us.lgtvsdp.com
0.0.0.0 us.rdx2.lgtvsdp.com
0.0.0.0 eic.recommend.lgtvcommon.com
dns-blocker.sh
Copy this to your dns_blackhole_dir
directory. To easily run it at any time without having to give the full path, add dns_blackhole_dir
to your $PATH or create a symlink to this in your ~/bin directory:
#!/bin/sh
# dns-blocker.sh
#
# Toggles the DNS blocker state on a FreeBSD server running BIND.
#
# This script allows you to enable or disable a DNS blocker by switching
# the symbolic link for the `named.blocker` file. It expects one argument:
# "on" to enable the DNS blocker or "off" to disable it.
#
# Usage:
# ./dns-blocker.sh <on|off>
#
# on - Enables the DNS blocker by linking named.rpz to named.blocker
# off - Disables the DNS blocker by linking named.blacklist-disabled to named.blocker
#
# Example:
# ./dns-blocker.sh on - Turns the DNS blocker on
# ./dns-blocker.sh off - Turns the DNS blocker off
#
# Assumptions:
# - The script is run on a FreeBSD server with BIND and the necessary files:
# - named.rpz (DNS blocker zone file)
# - named.blacklist-disabled (disabled DNS blocker file)
# - The script operates in the directory: /usr/local/etc/namedb
# - The "named.blocker" symlink exists and can be toggled between these two files.
#
# Note:
# This script requires root privileges to restart the BIND service.
# Ensure that option is provided
option="$1"
if [ -z "$option" ]; then
echo "Usage: $0 <on|off>"
exit 1
fi
cd "/usr/local/etc/namedb" || exit 1 # Exit if the directory can't be accessed
# Get the current symlink target (handle case if symlink does not exist)
current=$(readlink "named.blocker")
if [ -z "$current" ]; then
echo "$0: Error: named.blocker symlink does not exist."
exit 1
fi
case "$option" in
on)
if [ "$current" != "named.rpz" ]; then
echo "Turning DNS blocker on..."
ln -sf named.rpz named.blocker
else
echo "$0: DNS blocker is already on."
exit 0 # Success, no change needed
fi
;;
off)
if [ "$current" != "named.blacklist-disabled" ]; then
echo "Turning DNS blocker off..."
ln -sf named.blacklist-disabled named.blocker
else
echo "$0: DNS blocker is already off."
exit 0 # Success, no change needed
fi
;;
*)
echo "Usage: $0 <on|off>"
exit 1
;;
esac
# Restart the service after making the change
service named restart
Sample Output
# /usr/local/etc/dns-blackhole/dns-blackhole-update.sh
Fetching Steven Black master hosts...
Fetching (attempt 1/3)...
Fetch succeeded.
Optimizing hosts list...
Excluding allowed hosts...
Building zone file...
Stopping named.
Waiting for PIDS: 10669.
Starting named.
# ~/bin/dns-blocker.sh off
/home/mdavis/bin/dns-blocker.sh: DNS blocker is already off.
# ~/bin/dns-blocker.sh on
Turning DNS blocker on...
Stopping named.
Waiting for PIDS: 26637.
Starting named.
Sample Crontab Entry
#
#minute hour mday month wday command
#
15 4 * * * /usr/local/etc/dns-blackhole/dns-blackhole-update.sh 2>&1 | mail -s "update DNS blackhole zone" root