WARNING: This is a tutorial and not a normal blog post. Feel free to skip this one if that’s not your cup of tea (or coffee).
If you have a UDM Pro and want network-wide ad blocking without the latency penalty of a cloud-based solution, running Blocky directly on the device is the way to go. I went from 5-10ms round trips to a cloud instance down to sub-millisecond local responses.
I was previously running Blocky in Oracle Cloud (OCI) on their free tier. It worked fine, but every DNS query had to leave my house, hit Ashburn, and come back. That’s 5-10ms minimum, plus jitter.
The UDM Pro has plenty of resources to run Blocky locally:
ARM64 processor
2-4GB RAM (depending on model)
Persistent storage in
/data
With Blocky running locally, cached queries return in 0ms and even upstream lookups are faster since they originate from your gateway.
UniFi OS 3.x and 4.x dropped Docker/Podman support, but you don’t need containers. Blocky is a single Go binary that runs natively.
SSH into your UDM Pro and grab the ARM64 binary:
mkdir -p /data/blocky/logs
cd /data/blocky
curl -LO https://github.com/0xERR0R/blocky/releases/download/v0.28.2/blocky_v0.28.2_Linux_arm64.tar.gz
tar -xzf blocky_v0.28.2_Linux_arm64.tar.gz
rm blocky_v0.28.2_Linux_arm64.tar.gz
./blocky versionThe key thing here is using port 5335 instead of 53 because dnsmasq already owns port 53 on the UDM.
cat > /data/blocky/config.yml << 'EOF'
connectIPVersion: v4
bootstrapDns:
- 1.1.1.1
- 9.9.9.9
upstreams:
init:
strategy: fast
groups:
default:
- 1.1.1.1
- 9.9.9.9
strategy: parallel_best
timeout: 50ms
caching:
minTime: 4h
maxTime: 72h
maxItemsCount: 500000
cacheTimeNegative: 10m
prefetching: true
prefetchExpires: 12h
prefetchThreshold: 1
prefetchMaxItemsCount: 100000
customDNS:
filterUnmappedTypes: true
customTTL: 24h
mapping:
# Add your local DNS entries here
myserver.local: 10.0.0.100
app.local.example.com: 10.0.0.250
blocking:
denylists:
ads:
- <YOUR_BLOCKLISTS>
allowlists:
ads:
- <YOUR_ALLOWLISTS>
clientGroupsBlock:
default:
- ads
loading:
refreshPeriod: 6h
downloads:
timeout: 10s
attempts: 3
cooldown: 1s
concurrency: 64
strategy: fast
maxErrorsPerSource: 5
blockType: zeroIp
blockTTL: 1h
filtering:
queryTypes:
- AAAA
ports:
dns: 5335
http: 4000
ede:
enable: true
prometheus:
enable: true
path: /metrics
queryLog:
type: csv
target: /data/blocky/logs
logRetentionDays: 7
flushInterval: 30s
fields:
- clientIP
- clientName
- question
- responseReason
- responseAnswer
- duration
specialUseDomains:
rfc6762-appendixG: true
EOFNotes:
Upstream DNS: I’m using Cloudflare’s
1.1.1.1andQuad9s9.9.9.9 IPs. You can also use Google’s8.8.8.8/8.8.4.4.Resource limits:
maxItemsCountat 500k andconcurrencyat 64 keeps RAM usage reasonable on the UDM Pro (~250MB with large blocklists).AAAA filtering: Blocking AAAA queries forces IPv4, which can speed up resolution if your network doesn’t fully support IPv6.
cat > /etc/systemd/system/blocky.service << 'EOF'
[Unit]
Description=Blocky DNS
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/data/blocky
ExecStartPre=/bin/sleep 5
ExecStart=/data/blocky/blocky --config /data/blocky/config.yml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable blocky
systemctl start blockyThe ExecStartPre=/bin/sleep 5 gives the network time to come up before Blocky tries to download blocklists.
Here’s the tricky part. The UDM uses dnsmasq for DNS/DHCP, and we need to tell it to forward queries to Blocky. The config directory is /run/dnsmasq.dhcp.conf.d/ (not /run/dnsmasq.conf.d/ as you might expect).
mkdir -p /run/dnsmasq.dhcp.conf.d
cat > /run/dnsmasq.dhcp.conf.d/blocky.conf << 'EOF'
server=127.0.0.1#5335
no-resolv
EOF
killall dnsmasqdnsmasq respawns automatically and picks up the new config.
The /run directory is tmpfs and gets wiped on reboot. Install the unifios-utilities on-boot-script to run a script at startup:
curl -fsL "https://raw.githubusercontent.com/unifi-utilities/unifios-utilities/HEAD/on-boot-script-2.x/remote_install.sh" | /bin/bashThen create the boot script:
cat > /data/on_boot.d/10-blocky-dns.sh << 'EOF'
#!/bin/bash
# Wait for dnsmasq to fully initialize
sleep 30
mkdir -p /run/dnsmasq.dhcp.conf.d
cat > /run/dnsmasq.dhcp.conf.d/blocky.conf << 'DNSCONF'
server=127.0.0.1#5335
no-resolv
DNSCONF
# Restart dnsmasq to pick up config
killall dnsmasq
echo "Blocky DNS configured"
EOF
chmod +x /data/on_boot.d/10-blocky-dns.shThe 30-second delay is important – dnsmasq can restart after the initial boot and wipe your config.
# Test Blocky directly
dig @127.0.0.1 -p 5335 google.com
# Test through dnsmasq
dig @127.0.0.1 google.com
# Test ad blocking
dig @127.0.0.1 googleads.g.doubleclick.net
# Should return 0.0.0.0Reboot and verify everything comes back up:
reboot
# After reboot:
systemctl status blocky
cat /run/dnsmasq.dhcp.conf.d/blocky.conf
dig @127.0.0.1 google.comCloud (OCI): Cache hit latency ~820μs, upstream queries 5-15ms.
Local (UDM Pro): Cache hit latency 0ms, upstream queries 5-10ms, ~250MB RAM, 500k+ blocked domains.
The difference is most noticeable on cache hits – they’re essentially instant now.
To add or remove blocklists, edit /data/blocky/config.yml and restart:
nano /data/blocky/config.yml
systemctl restart blockyOr reload lists without restart:
curl -X POST http://127.0.0.1:4000/api/lists/refreshRecommended blocklists:
Hagezi Ultimate – comprehensive ad/tracker blocking
Hagezi TIF – threat intel feeds (optional)
OISD Big – well maintained alternative (optional)
Start with Hagezi Ultimate and add others as needed. More lists = more RAM usage and longer load times.
Blocky fails to download lists on boot: The network wasn’t ready. The ExecStartPre=/bin/sleep 5 should help, but you can also just restart Blocky once the system is up:
systemctl restart blockydnsmasq config missing after reboot: Make sure the on-boot script has the 30-second delay and is executable:
chmod +x /data/on_boot.d/10-blocky-dns.shQuery refused errors: dnsmasq hasn’t picked up the Blocky config. Kill it and let it respawn:
killall dnsmasq
sleep 3
dig @127.0.0.1 google.comContent Filtering override: If you have UniFi’s Content Filtering enabled on any network, it will override your DNS settings. Disable it in Settings → Networks → [Network] → Content Filtering.
Blocky exposes Prometheus metrics at http://<udm-ip>:4000/metrics. Point your Grafana instance at it for dashboards.
Query logs are in /data/blocky/logs/ as daily CSV files:
tail -f /data/blocky/logs/$(date +%Y-%m-%d)_ALL.log | grep -v "_grpc_config"That’s it. Local ad-blocking DNS on your UDM Pro with sub-millisecond response times. No containers, no cloud dependencies, just a single binary doing its job.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.