SmartDNS on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of SmartDNS on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. SmartDNS is a local DNS server that takes each lookup, sends it to several upstream resolvers concurrently, measures how quickly each returned address actually responds, and hands your client the fastest one. It caches results aggressively for low latency, speaks encrypted upstream protocols (DNS over TLS, DNS over HTTPS and QUIC), and can apply per domain rules such as pinning an address or blocking a name outright.
Because a name like github.com or a large CDN hostname resolves to many different addresses depending on which resolver you ask, the address you are given matters as much as how fast you were given it. SmartDNS is built around that idea: it does not just forward, it compares and chooses.
The image installs the official SmartDNS Release 48.4 package published by the upstream project, pinned to an exact release and verified against a known SHA256 checksum during the build, so you always know precisely what is on the image.
Security by design — not an open resolver. A DNS resolver that answers arbitrary lookups for anyone on the internet is a well known DNS amplification and reflection DDoS vector. SmartDNS listens on all interfaces so it is useful right across your VNet, but unlike some resolvers it has no admission control of its own (its client-rules directive changes per client behaviour, not who is allowed in). cloudimg therefore enforces the boundary where it cannot be bypassed, in the kernel with nftables: DNS on UDP and TCP 53, and the web interface on TCP 6080, are accepted from the local host and private ranges only and dropped from every other source. The image ships an audit tool that proves this functionally rather than by assertion (see Step 7).
Security by design — no published default password. The SmartDNS web interface documents a default login of admin / password. That credential is never written into this image. In the shipped image the web interface is not even loaded: its configuration include is blank, so there is no window in which a known credential is live. At first boot your instance generates a 32 character random password for itself, writes the configuration, and starts the interface. The in browser terminal feature is deliberately disabled, because a remote shell on a network appliance is a remote root surface.
What is included:
-
SmartDNS Release 48.4 (official upstream package, checksum pinned), run under systemd as
smartdns.service -
Fastest address selection:
response-mode fastest-ipwith a TCP based speed check (tcp:443,tcp:80), chosen over ICMP ping because outbound ICMP is unreliable in cloud networks and a silently failing check would degrade the very measurement the product exists for -
Six upstream resolvers, encrypted first: DNS over TLS to Cloudflare, Google and Quad9 with hostname verification, plus plain UDP equivalents as a fallback for networks that block port 853
-
Aggressive caching with prefetch and stale answer serving, so repeat lookups are answered in well under a millisecond
-
A web interface on port 6080 with live query logs, per upstream response times, cache statistics and client breakdown
-
A kernel level nftables boundary restricting DNS and the web interface to private networks, plus
smartdns-openresolver-check.sh, an audit tool that proves it -
A per instance web password generated at first boot, stored root only at
/root/smartdns-credentials.txt -
Unattended security upgrades left enabled so the underlying OS keeps receiving patches
Prerequisites
-
Active Azure subscription, SSH public key, VNet and subnet in the target region
-
Subscription to the SmartDNS listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 for administration, and both UDP and TCP 53 from the client subnets that will query the resolver
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a resolver serving a VNet. Use Standard_D2s_v5 or larger for very heavy query volumes.
Keep port 53 closed to the internet in your NSG. The image already drops public DNS traffic in the kernel, but defence in depth means your NSG should not offer it either.
Step 1: Deploy from the Azure Portal
Search SmartDNS in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow UDP 53 and TCP 53 from your client subnets only, and TCP 22 for administration. DNS uses UDP 53 for most queries and falls back to TCP 53 for large responses, so open both.
Do not add an NSG rule exposing port 6080 to the internet. The web interface is intended to be reached over an SSH tunnel or from inside your VNet.
Step 2: Deploy from the Azure CLI
RG="dns-prod"; LOCATION="eastus"; VM_NAME="smartdns1"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" \
--name "$VM_NAME" \
--image cloudimg:smartdns-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard \
--location "$LOCATION"
# Open DNS to your CLIENT SUBNET only - never to the internet:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 53 --priority 1001
Step 3: First boot
On first boot the instance generates its own web interface password, writes the resolver configuration, loads the kernel firewall, and verifies that it is not an open resolver before it finishes. This takes a few seconds. Connect over SSH and read the details, which are unique to your instance:
sudo cat /root/smartdns-credentials.txt

The file records the DNS endpoint, the web interface URL, the generated username and password, and the result of the open resolver audit. The password shown in the screenshot above is masked; on your instance it is a real 32 character value.
Step 4: Confirm the resolver is running
systemctl is-active smartdns.service nftables.service
Both must report active. Check that the resolver and web interface are listening, and that both services are enabled so they survive a reboot:
sudo ss -tulnp | grep -E ':53 |:6080'
systemctl is-enabled smartdns.service nftables.service

Step 5: Resolve a name
Query the resolver directly:
dig @127.0.0.1 github.com A +short
You should get one or more addresses back. Now look at the timing behaviour that makes SmartDNS different. Run the same lookup twice:
dig @127.0.0.1 cloudflare.com A | grep -E 'status:|Query time'
dig @127.0.0.1 cloudflare.com A | grep -E 'Query time'
The first query is resolved upstream; the second is answered from cache, typically in 0 ms.

Step 6: See the fastest address selection at work
The resolver configuration sets the selection strategy and the upstream pool:
grep -E '^(response-mode|speed-check-mode|server|server-tls)' /etc/smartdns/conf.d/10-cloudimg-resolver.conf
response-mode fastest-ip tells SmartDNS to return the address that responded fastest, and speed-check-mode tcp:443,tcp:80 is how it measures. The six server and server-tls lines are the resolvers it asks in parallel. You can see the measurements it has collected per upstream in the web interface in Step 9.
Step 7: Verify it is not an open resolver
This is the most important check on a DNS appliance, and the image can prove it rather than just claim it. Run the bundled audit:
sudo /usr/local/sbin/smartdns-openresolver-check.sh

The audit builds two temporary network namespaces attached to the host, one holding a public source address and one holding a private source address, and queries the resolver from each. Because SmartDNS listens on all interfaces it is genuinely reachable on both, so the only thing that can make one succeed and the other fail is the firewall. The audit requires:
-
the query from the private source to be answered, so the resolver is genuinely usable inside your VNet, and
-
the query from the public source to be dropped, so the resolver cannot be abused as an amplifier.
It fails closed: if the boundary is missing, weakened, or a blanket accept rule has been added, the audit exits non zero. You can inspect the rules directly:
sudo nft list table inet cloudimg_smartdns | grep -E 'dport (53|6080)'
Step 8: Point your clients at the resolver
From another VM in the same VNet, query the resolver by its private address:
dig @<SMARTDNS_HOST> github.com A +short
To make it the system resolver on an Ubuntu client, set the DNS server on the interface rather than editing /etc/resolv.conf directly, since that file is managed:
sudo resolvectl dns eth0 <SMARTDNS_HOST>
sudo resolvectl domain eth0 '~.'
resolvectl status eth0
In production, set the DNS server on the Azure VNet itself (Virtual network, DNS servers, Custom) so every VM in the network picks it up automatically.
Step 9: Open the web interface
The web interface is bound to private addresses only, so reach it from your workstation over an SSH tunnel:
ssh -L 6080:127.0.0.1:6080 azureuser@YOUR_VM_PUBLIC_IP
With the tunnel open, browse to http://127.0.0.1:6080/ and sign in with the username and password from /root/smartdns-credentials.txt.

The dashboard shows live totals: how many queries the resolver has answered, the cache hit rate, and the average query time.

Upstream Servers is where the product's behaviour becomes visible. Each configured resolver is listed with its measured average response time, its success rate and how many queries it has handled, which is exactly the comparison SmartDNS uses to decide what to return. The tls rows are the encrypted upstreams.

Query Log shows every lookup with its client, type, and the measured response time for the answer that was chosen.

You can confirm the interface is authenticated, and that the published default credential does not work, without leaving the shell:
curl -s -o /dev/null -w 'unauthenticated: HTTP %{http_code}\n' http://127.0.0.1:6080/api/stats/overview
curl -s -o /dev/null -w 'upstream default admin/password: HTTP %{http_code}\n' \
-X POST http://127.0.0.1:6080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"password"}'
Both must report 401. Now sign in with your own instance's credential:
curl -s -X POST http://127.0.0.1:6080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"<SMARTDNS_UI_USER>","password":"<SMARTDNS_UI_PASSWORD>"}' \
| head -c 80
That returns a JSON web token, which is what the browser interface uses for subsequent calls.
Step 10: Serve additional networks
If you need to serve a network that is not in the private ranges the image allows by default, add its CIDR to the nftables set rather than removing the boundary. Edit /etc/nftables.conf, add your range to cloudimg_private_v4, then reload and re-audit:
sudo nft -f /etc/nftables.conf
sudo /usr/local/sbin/smartdns-openresolver-check.sh
Never replace these rules with a blanket accept on port 53. That turns your instance into an open resolver, and the audit above will fail.
Step 11: Customise the resolver
All cloudimg configuration lives in /etc/smartdns/conf.d/, and /etc/smartdns/smartdns.conf simply includes that directory. Add your own *.conf file there to extend or override behaviour, then restart:
sudo systemctl restart smartdns.service
Useful directives to explore: address /ads.example.com/# to block a name, nameserver /internal.example.com/office to send a domain to a specific upstream group, and cache-size to tune the cache. The upstream project documents the full configuration reference.
Step 12: Rotate the web interface password
The password is stored in /etc/smartdns/conf.d/50-cloudimg-webui.conf, which is readable by root only. Change the smartdns-ui.password line and restart the service:
sudo systemctl restart smartdns.service
Remember to update /root/smartdns-credentials.txt so your record stays accurate.
Step 13: Security recommendations
-
Keep port 53 closed to the internet in your NSG. The kernel boundary already drops public DNS, but your NSG should agree with it.
-
Never expose port 6080 to the internet. Use an SSH tunnel, or reach it from inside the VNet.
-
Re-run
smartdns-openresolver-check.shafter any firewall or configuration change, and treat a failure as a production incident. -
Rotate the web interface password if it has ever been shared.
-
Leave unattended security upgrades enabled so the underlying OS stays patched.
-
Restrict SSH (TCP 22) to known administrative addresses.
Step 14: Support and Licensing
SmartDNS is open source software licensed under the GPL-3.0 licence. The licence text is included on the image at /usr/share/doc/cloudimg/smartdns/LICENSE.
cloudimg provides the packaged, hardened Azure image and 24/7 support for the image itself. cloudimg is not affiliated with or endorsed by the SmartDNS project; SmartDNS is the name of the upstream project and is used here only to identify the software this image packages.
Deploy on Azure
Find SmartDNS on Ubuntu 24.04 LTS by cloudimg on the Azure Marketplace.
Need Help?
Contact cloudimg support at support@cloudimg.co.uk.