Below is a small tutorial on how you can create your own recursive DNS server using Unbound , adding custom records to block ads (plus fakenews, porn and/or social websites), on Apple macOS. Also, you can use DNS over TLS if needed/wanted.


Unbound is already in Homebrew so installing it is just a matter of running:

$ brew install unbound


Find a free unique id for the unbound user in the 1-500 range (reserved for system accounts). For example, 444.

$ dscl . -list /Groups PrimaryGroupID | grep 444
$ dscl . -list /Users PrimaryGroupID | grep 444

If you have no output for those two commands, you can proceed to actually create the user and group.

$ sudo dscl . -create /Groups/_unbound
$ sudo dscl . -create /Groups/_unbound PrimaryGroupID 444
$ sudo dscl . -create /Users/_unbound
$ sudo dscl . -create /Users/_unbound RecordName _unbound unbound
$ sudo dscl . -create /Users/_unbound RealName "Unbound DNS server"
$ sudo dscl . -create /Users/_unbound UniqueID 444
$ sudo dscl . -create /Users/_unbound PrimaryGroupID 444
$ sudo dscl . -create /Users/_unbound UserShell /usr/bin/false
$ sudo dscl . -create /Users/_unbound Password '*'
$ sudo dscl . -create /Groups/_unbound GroupMembership _unbound

Fetch the root key required for DNSSEC validation:

$ sudo unbound-anchor -a /usr/local/etc/unbound/root.key

Create the certificates needed:

$ sudo unbound-control-setup -d /usr/local/etc/unbound

Here is a single-line command that will download the StevenBlack hosts list (fakenews + gambling + porn + social, so keep that in mind), convert it for unbound and save it in /usr/local/etc/unbound/zone-block-general.conf. Unbound will respond with NXDOMAIN to all the domains in this list.

$ (curl --silent | grep '^0\.0\.0\.0' | sort) | awk '{print "local-zone: \""$2"\" refuse"}' > /usr/local/etc/unbound/zone-block-general.conf

Let’s configure unbound as an authoritative, validating, recursive caching DNS server . The lines below go to your /usr/local/etc/unbound/unbound.conf:

	# log verbosity
	verbosity: 1
	access-control: allow
	chroot: ""
	username: "_unbound"
	auto-trust-anchor-file: "/usr/local/etc/unbound/root.key"
	# answer DNS queries on this port
	port: 53
	# enable IPV4
	do-ip4: yes
	# disable IPV6
	do-ip6: no
	# enable UDP
	do-udp: yes
	# enable TCP, you could disable this if not needed, UDP is quicker
	do-tcp: yes
	# which client IPs are allowed to make (recursive) queries to this server
	access-control: allow
	access-control: allow
	access-control: allow
	root-hints: "/usr/local/etc/unbound/root.hints"
	# do not answer id.server and hostname.bind queries
	hide-identity: yes
	# do not answer version.server and version.bind queries
	hide-version: yes
	# will trust glue only if it is within the servers authority
	harden-glue: yes
	# require DNSSEC data for trust-anchored zones, if such data
	# is absent, the zone becomes  bogus
	harden-dnssec-stripped: yes
	# use 0x20-encoded random bits in the query to foil spoof attempts
	use-caps-for-id: yes
	# the time to live (TTL) value lower bound, in seconds
	cache-min-ttl: 3600
	# the time to live (TTL) value cap for RRsets and messages in the cache
	cache-max-ttl: 86400
	# perform prefetching of close to expired message cache entries
    prefetch: yes
    num-threads: 4
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8
    rrset-cache-size: 256m
    msg-cache-size: 128m
    so-rcvbuf: 1m
    private-domain: "home.lan"
    unwanted-reply-threshold: 10000
    val-clean-additional: yes
    # additional blocklist (Steven Black hosts file, read above)
    include: /usr/local/etc/unbound/zone-block-general.conf
    control-enable: yes
    server-key-file: "/usr/local/etc/unbound/unbound_server.key"
    server-cert-file: "/usr/local/etc/unbound/unbound_server.pem"
    control-key-file: "/usr/local/etc/unbound/unbound_control.key"
    control-cert-file: "/usr/local/etc/unbound/unbound_control.pem"

If you want to use DNS over TLS, you can forward requests to a TLS-capable recursive server, for example Cloudflare ( or Quad9 ( Add the lines below to your /usr/local/etc/unbound.conf:

    # use Quad9
    # or Cloudflare
    # forward-addr:

The unbound process needs read and write permissions for the configuration directory, use staff as group so the user can use unbound-control:

$ sudo chown -R _unbound:staff /usr/local/etc/unbound
$ sudo chmod 640 /usr/local/etc/unbound/*

If you want to start unbound at boot, you need to create the /Library/LaunchDaemons/net.unbound.plist file and place those lines in it:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

Start the Unbound daemon:

$ sudo launchctl load /Library/LaunchDaemons/net.unbound.plist

Stop (when needed) the Unbound daemon:

$ sudo launchctl unload /Library/LaunchDaemons/net.unbound.plist

Set your local DNS server as default for the Wi-Fi connection:

$ networksetup -setdnsservers Wi-Fi

Check if DNS was set and everything is ok:

$ networksetup -getdnsservers Wi-Fi

Testing DNSSEC for your new DNS resolver is easy, using dig:

$ dig org. SOA +dnssec @                                                         
; <<>> DiG 9.10.6 <<>> org. SOA +dnssec @
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8381
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 7, ADDITIONAL: 1

The ad flag is short for Authenticated Data and means DNSSEC is working.