Networking Azure

PowerDNS Recursor with dnsdist on Ubuntu 24.04 on Azure User Guide

| Product: PowerDNS Recursor 5.4 with dnsdist 2.1 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of PowerDNS Recursor with dnsdist on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. It is a two-daemon appliance built from two open source projects by PowerDNS.COM BV:

  • PowerDNS Recursor 5.4 is the recursive resolver. It answers queries on behalf of your clients by walking the DNS hierarchy from the root servers, following delegations, validating DNSSEC signatures and caching the results.
  • dnsdist 2.1 is the DNS load balancer and TLS terminator that sits in front of it, providing the encrypted transports, the client access control list, and live traffic visibility.

Both are installed from the official PowerDNS apt repositories (repo.powerdns.com, branches noble-rec-54 and noble-dnsdist-21), pinned above the distribution packages. Unattended security upgrades keep the appliance patched on your running VM.

How the two daemons fit together

  your clients ──▶  plain DNS   :53  (udp + tcp) ──┐
  your clients ──▶  DNS-over-TLS   :853           ─┼──▶  dnsdist  ──▶  PowerDNS Recursor
  your clients ──▶  DNS-over-HTTPS :443/dns-query ─┘     0.0.0.0        127.0.0.1:5300

dnsdist is the only daemon on a routable address. The Recursor is bound to loopback and accepts queries only from 127.0.0.0/8, so the resolver itself is unreachable from the network even if the dnsdist access list were later widened by mistake. That is deliberate defence in depth.

Never an open resolver — secure by default. An internet-facing resolver that answers anyone is a DNS amplification hazard: attackers spoof a victim's source address, send small queries, and your instance floods the victim with large responses. This image ships with the dnsdist ACL restricted to loopback and the RFC1918 private ranges only. Queries from arbitrary internet sources are refused. Opening the appliance to your own client subnets is a single documented change (Step 8), and 0.0.0.0/0 must never be added.

Security by design — no default credentials. This image never ships a fixed key, password or certificate. On the very first boot of every VM a one-shot service generates a fresh dnsdist console key, dnsdist dashboard password and REST API key, a fresh PowerDNS Recursor REST API key and webserver password, and a fresh TLS key pair for the encrypted transports. All of it is written to /root/powerdns-recursor-dnsdist-credentials.txt (mode 0600, root only). Both daemons refuse to start until that provisioning has run, and a start-time guard blocks them from ever running on a published upstream example credential.

What is included:

  • PowerDNS Recursor 5.4.5 and dnsdist 2.1.1 from the official PowerDNS packages, each run under systemd as its own unprivileged user (pdns-recursor.service, dnsdist.service)

  • Three client transports from one instance: plain DNS on 53 (UDP and TCP), DNS over TLS on 853, DNS over HTTPS on 443 at /dns-query

  • A dnsdist access control list restricted to loopback and RFC1918 private ranges, so the instance is never an open resolver

  • DNSSEC validation of signed answers, and the systemd-resolved stub listener disabled so dnsdist owns port 53 cleanly

  • Two authenticated web dashboards, both bound to loopback: dnsdist on 127.0.0.1:8083 and the Recursor on 127.0.0.1:8082

  • dig, kdig and curl preinstalled so you can verify all three transports without installing anything

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the PowerDNS Recursor with dnsdist listing on Azure Marketplace

  • Network Security Group rules allowing TCP 22 (admin) and, from the client networks that will use the resolver, UDP 53 and TCP 53 (plain DNS), TCP 853 (DNS over TLS) and TCP 443 (DNS over HTTPS)

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for a small resolver. Resolvers serving heavy query volumes should use Standard_D2s_v5 or larger.

Step 1: Deploy from the Azure Portal

Search PowerDNS Recursor with dnsdist in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow the transports your clients will use — UDP 53 and TCP 53 for plain DNS, TCP 853 for DNS over TLS, TCP 443 for DNS over HTTPS — from your client networks only, plus TCP 22 for administration. Both management dashboards stay on loopback and are never exposed.

Step 2: Deploy from the Azure CLI

RG="dns-prod"; LOCATION="eastus"; VM_NAME="resolver1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/powerdns-recursor-dnsdist/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values "$SSH_KEY" \
  --public-ip-sku Standard

Step 3: Connect and retrieve the per-instance credentials

Every credential on this appliance is unique to your VM and was generated on its first boot.

sudo cat /root/powerdns-recursor-dnsdist-credentials.txt

The file lists the dnsdist console key, the dnsdist dashboard password and REST API key, the Recursor REST API key and webserver password, the TLS certificate fingerprint, and this instance's addresses. Store them somewhere safe.

Per-VM credentials and the no-known-credential start-time guard

Step 4: Confirm both daemons and the listener topology

Check that both services are running, and confirm which ports are public and which are loopback-only.

sudo systemctl is-active dnsdist.service pdns-recursor.service

Expected output:

active
active

Now confirm the topology. dnsdist should own the three public DNS ports; the Recursor and every management surface must be on 127.0.0.1 only.

echo "PUBLIC (dnsdist front door):"
sudo ss -tulnH | awk '{print $1"\t"$4"\t"$5}' | grep -E '(^|\s)(0\.0\.0\.0|\[::\]):(53|443|853)\b' | sort -u -k2
echo "LOOPBACK ONLY (recursor + management):"
sudo ss -tulnH | awk '{print $1"\t"$4"\t"$5}' | grep -E '127\.0\.0\.1:(5300|8082|8083|5199)' | sort -u -k2

0.0.0.0:53, 0.0.0.0:853 and 0.0.0.0:443 belong to dnsdist. 127.0.0.1:5300 is the Recursor, 127.0.0.1:8082 its REST API, 127.0.0.1:8083 the dnsdist dashboard and 127.0.0.1:5199 the dnsdist console — none of which are reachable from the network.

dnsdist and PowerDNS Recursor services and listener topology

Confirm the versions you are running:

pdns_recursor --version 2>&1 | head -1
dnsdist --version 2>&1 | head -1

Step 5: Query the resolver over all three transports

These commands query the appliance on its own routable address, exactly as a client on your VNet would. RESOLVER_IP is resolved from the instance itself so you can paste the block as-is; from a client machine, set it to this VM's address instead.

Plain DNS on port 53:

RESOLVER_IP="$(hostname -I | awk '{print $1}')"
dig +short @"$RESOLVER_IP" A example.com

DNS over TLS on port 853. The appliance's certificate is self-signed and unique to your VM, so point kdig at it with +tls-ca (or install your own certificate — Step 9):

RESOLVER_IP="$(hostname -I | awk '{print $1}')"
kdig +tls +tls-hostname="$RESOLVER_IP" +tls-ca=/etc/dnsdist/tls/dnsdist.crt \
     +short @"$RESOLVER_IP" -p 853 A example.com

DNS over HTTPS on port 443 at the /dns-query path. The query below is the RFC 8484 wire-format request for example.com A; the response is binary DNS, so we read the answer count out of the DNS header rather than printing it:

RESOLVER_IP="$(hostname -I | awk '{print $1}')"
DNS_Q="AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE"
CODE=$(curl -sk -o /tmp/doh.bin -w '%{http_code}' -m 15 \
  -H 'accept: application/dns-message' \
  "https://${RESOLVER_IP}/dns-query?dns=${DNS_Q}")
echo "HTTP $CODE  answer records: $(od -An -tu1 -j6 -N2 /tmp/doh.bin | awk '{print $1*256+$2}')"
rm -f /tmp/doh.bin

A healthy appliance returns HTTP 200 with one or more answer records. An answer count of 0 means the query reached dnsdist but resolution failed — check outbound UDP 53 from the VM to the internet.

Plain DNS, DNS over TLS and DNS over HTTPS all answering

You can also confirm dnsdist sees the Recursor backend as healthy, using the per-instance console key:

CONSOLE_KEY=$(sudo grep '^dnsdist.console.key=' /root/powerdns-recursor-dnsdist-credentials.txt | cut -d= -f2-)
printf "setKey('%s')\n" "$CONSOLE_KEY" | sudo tee /run/ck.conf >/dev/null
sudo chmod 600 /run/ck.conf
sudo dnsdist -C /run/ck.conf -c 127.0.0.1:5199 -e 'showServers()'
sudo rm -f /run/ck.conf

The powerdns-recursor backend at 127.0.0.1:5300 should report state up.

Step 6: Open the dnsdist dashboard

The dnsdist dashboard shows live queries per second, per-transport latency, backend health, and the active access control list. It is bound to loopback, so reach it through an SSH tunnel from your workstation:

ssh -L 8083:127.0.0.1:8083 azureuser@YOUR_VM_ADDRESS

Then open http://127.0.0.1:8083/ and sign in with user admin and the dnsdist.webserver.password value from the credentials file.

dnsdist dashboard overview showing live queries, per transport latency, listeners and ACL

The header line tells you a great deal at a glance: total questions answered and current QPS, ACL drops, average response time broken out per transport (UDP, TCP, DoT, DoH), the ports being listened on, and the exact ACL in force.

dnsdist live traffic chart and the PowerDNS Recursor backend reporting up

Step 7: Open the PowerDNS Recursor dashboard

The Recursor has its own status dashboard on 127.0.0.1:8082 showing resolver-side metrics: query rate, cache hit rate, CPU, and rings of the most-queried domains and the busiest clients. Tunnel to it the same way:

ssh -L 8082:127.0.0.1:8082 azureuser@YOUR_VM_ADDRESS

Open http://127.0.0.1:8082/ and sign in with user admin and the powerdns.recursor.webserver.password value.

PowerDNS Recursor dashboard showing resolver query rate and cache hit rate

The Queries and Remotes rings are the quickest way to see what your clients are actually resolving. Because every query arrives via dnsdist, the only remote you will see is 127.0.0.1 — that is the front door, working as designed.

PowerDNS Recursor live query ring showing the domains being resolved

Step 8: Allow your own clients (the access control list)

Out of the box the appliance answers loopback and the RFC1918 private ranges only. You can see the shipped ACL and prove that anything outside it is refused:

grep -A 11 '^setACL' /etc/dnsdist/dnsdist.conf

Secure by default: the restricted ACL and a refused query from outside it

If your clients are already inside 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16 — which covers most Azure VNets — there is nothing to change. Confirm your VNet range first:

hostname -I

To add a client range that is not already covered, edit the setACL({...}) list in /etc/dnsdist/dnsdist.conf, add your subnet as a new quoted entry, and reload:

setACL({
  '127.0.0.0/8',
  '10.0.0.0/8',
  ...
  '203.0.113.0/24',        <-- your client subnet
})

Then apply it:

sudo systemctl reload dnsdist

Never add 0.0.0.0/0 or ::/0. That turns this instance into an open resolver: an internet-wide DNS amplification reflector that attackers use to DDoS third parties with your bandwidth and your IP address, and which will get the instance null-routed by your provider. The appliance actively refuses to start if it detects a default route in the ACL. Always restrict to the client networks you actually operate.

Restrict access at the Network Security Group as well as in dnsdist. Two independent layers is the right posture for anything answering DNS.

Step 9: Replace the TLS certificate (optional)

The DoT and DoH certificate is self-signed and generated uniquely for your VM, so clients must either trust /etc/dnsdist/tls/dnsdist.crt or pin its fingerprint:

sudo openssl x509 -in /etc/dnsdist/tls/dnsdist.crt -noout -fingerprint -sha256

For clients that expect a publicly trusted certificate, replace the key pair with your own (from your CA or an ACME client), keeping the same paths and ownership, then reload:

sudo install -o root -g _dnsdist -m 0644 /path/to/fullchain.pem /etc/dnsdist/tls/dnsdist.crt
sudo install -o root -g _dnsdist -m 0640 /path/to/privkey.pem  /etc/dnsdist/tls/dnsdist.key
sudo systemctl reload dnsdist

Step 10: Security posture

  • No shipped credentials. Every key, password and certificate on this appliance was generated on this VM's first boot. Nothing is shared with any other cloudimg instance.
  • Both daemons fail closed. dnsdist.service and pdns-recursor.service each carry a ConditionPathExists gate on their per-VM secrets plus a start-time guard that refuses to run if a published upstream example credential is in effect, if the console key is too weak, if the ACL contains a default route, or if a management surface is not loopback-bound. You can run that guard yourself at any time:
sudo /usr/local/sbin/cloudimg-dns-secret-guard.sh && echo "secrets in effect are unique to this VM"
  • Management surfaces are loopback-only. Neither dashboard, neither REST API, nor the dnsdist console is reachable from the network. Reach them over SSH tunnels as in Steps 6 and 7.
  • Secret files are root-owned. The credentials file is 0600 root:root; the dnsdist and Recursor secret drop-ins and the TLS private key are 0640, readable only by the daemon that needs them.
sudo ls -l /root/powerdns-recursor-dnsdist-credentials.txt /etc/dnsdist/tls/dnsdist.key

Step 11: Ongoing operations

Reload dnsdist after any configuration change (this re-reads the config without dropping service):

sudo systemctl reload dnsdist

Check the logs for either daemon:

sudo journalctl -u dnsdist.service --no-pager | tail -20
sudo journalctl -u pdns-recursor.service --no-pager | tail -20

Unattended security upgrades are enabled, so both packages continue to receive updates from the official PowerDNS repositories on your running VM.

Support

cloudimg provides 24/7 technical support for this image by email (support@cloudimg.co.uk) and live chat, with a one hour average response time for critical issues. We help with client access control, replacing the self-signed certificate, DNSSEC validation, forwarding zones, Response Policy Zones for DNS firewalling, dnsdist rules for rate limiting and filtering, REST API automation, cache and performance tuning, and PowerDNS version upgrades.

All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.