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.
Why Blocky on the UDM Pro?
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.
The Setup
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.
Step 1: Download Blocky
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 versionStep 2: Create the Config
The 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.
Step 3: Create the systemd Service
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.
Step 4: Wire Up dnsmasq
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.
Step 5: Make It Persist
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.
Step 6: Test It
# 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.comAfter running for a few days, the performance difference is clear:
Cloud (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.
Managing Blocklists
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.
Troubleshooting
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.
Monitoring
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.




Thanks bro. I always wanted to know how to run blocky on the unified dream machine pro!