RSS Amplifier

jmcglock · Feb 7, 2026

running blocky on the unifi dream machine pro

0
Sign in to vote or save

jmcglock · jmcglock

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 version

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
EOF

Notes:

  • Upstream DNS: I’m using Cloudflare’s 1.1.1.1 and Quad9s 9.9.9.9 IPs. You can also use Google’s 8.8.8.8/8.8.4.4.

  • Resource limits: maxItemsCount at 500k and concurrency at 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 blocky

The 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 dnsmasq

dnsmasq 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/bash

Then 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.sh

The 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.0

Reboot 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.com
  • 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.

To add or remove blocklists, edit /data/blocky/config.yml and restart:

nano /data/blocky/config.yml
systemctl restart blocky

Or reload lists without restart:

curl -X POST http://127.0.0.1:4000/api/lists/refresh

Recommended blocklists:

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 blocky

dnsmasq 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.sh

Query 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.com

Content 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.

Read the original on jmcglock.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.