What is private DNS, and how to run your own
Private DNS is Android's name for DNS over TLS. What it hides from your network, what it does not, and how to run your own resolver on a VPS with Unbound.
Private DNS on Android is DNS over TLS
Private DNS is the name Android gives to DNS over TLS, the protocol from RFC 7858 that wraps every DNS lookup in a TLS (transport layer security) session on TCP port 853. Normal DNS travels in plain text on port 53, so anyone between your phone and the resolver can read every name you look up. With Private DNS set to a hostname, the names travel inside an encrypted connection to one resolver you chose, and that resolver proves its identity with a certificate for the hostname. That is the whole feature. It changes who can read your lookups, and nothing about where your traffic goes. If DNS itself is new to you, start with what DNS is and how a lookup works.
The setting lives at Settings > Network & internet > Private DNS on Android 9 and later, and it has three states.
- Off. Every lookup goes in plain text to whatever DNS server the current network handed out with DHCP. The network operator, meaning whoever runs the network you are on (your mobile carrier, or the café Wi-Fi), reads and can rewrite every query.
- Automatic. The default. Android tries TCP port 853 on the network's own DNS server. If something answers, lookups are encrypted to that server. If nothing answers, which is the case on most networks, Android falls back to plain port 53 without telling you. Automatic has no hostname, so it cannot check who is on the other end. It stops passive reading only.
- Private DNS provider hostname. Strict mode. Android resolves the hostname once through the network's plain DNS, connects to port 853, and checks the certificate against that hostname. If the certificate does not match or the port is blocked, Android does not fall back. Name resolution stops, and the phone shows "Private DNS server cannot be accessed". This is the only mode that gives you a guarantee, and the only one that earns the word "private".
iOS has no field for this. Apple supports the same protocol from iOS 14 on, but only through an installed configuration profile, so the phone-side steps below are Android.
What the network operator still sees
An encrypted resolver hides the names you look up. It hides nothing else, and it is worth being exact about the rest.
- One name in the clear. Android has to find the IP address of your resolver before it can open the TLS session, and that lookup goes through the network's plain DNS. The operator sees exactly one hostname: your resolver's.
- That you use DNS over TLS, and where. A long-lived TCP connection to port 853 on one IP address is easy to spot, and an operator can block it. In strict mode a block means no DNS at all, which is by design.
- Every destination IP address. Your phone still connects directly to every site. The operator sees each IP, how much data moved, and when.
- The server name on most TLS connections. When a browser opens an HTTPS connection it sends the hostname in the clear, in the SNI (server name indication) field, so the site can pick the right certificate. Encrypted client hello (ECH) hides this, but it needs support from the browser and from the site, and most sites still do not offer it as of September 2026. So the operator loses your DNS log and keeps a fairly good substitute.
Private DNS is a small, real improvement. It is not anonymity, and it is not a tunnel. If you want the operator to see nothing but one encrypted stream to one server, that job belongs to a VPN, and a WireGuard VPN on your own VPS does it in about forty lines of config.
Public hostnames people paste into the field, and what each logs
Any of these works in the hostname field. Choosing one means choosing who gets your lookup history instead of the network operator. What follows is what each provider's own published policy said when read on 12 September 2026. Policies change; read the current one before you commit.
dns.google(Google Public DNS). Temporary logs hold your full IP address and are deleted within 24 to 48 hours. Permanent logs keep the requested name, the query type and a city-level location, with the IP address removed.one.one.one.one(Cloudflare 1.1.1.1). Source IPs are truncated (the last octet dropped for IPv4) and the resolver logs are deleted within 25 hours. Cloudflare shares query names and types, without client IPs, with APNIC for research, and states it will not sell resolver data or use it for advertising.dns.quad9.net(Quad9, a Swiss foundation). States it does not store your IP address at all: the reply address is used to send the answer and then discarded. It keeps aggregate counters per region, with no region smaller than 10,000 people. The default hostname blocks domains on its malware lists.dns.adguard-dns.com(AdGuard DNS, a company registered in Cyprus). States it processes no personal data on the public resolver, keeps aggregate server metrics, and stores an anonymous list of domains requested in the last 24 hours. The default hostname blocks advertising and tracking domains;unfiltered.adguard-dns.comdoes not.
Every one of these is an organisation that can be compelled or breached. That is the case for the rest of this guide.
The other thing called private DNS
Cloud providers use the same two words for something unrelated. A Route 53 private hosted zone on AWS, or an Azure Private DNS zone, is a set of names that resolve only from inside one virtual network, so db.internal can point at a private address that means nothing outside that network. It has nothing to do with encryption or with your phone. If you arrived here from a cloud console, that is the feature you want, and the rest of this page is about the other one.
Why run your own private DNS resolver
Pasting dns.quad9.net into the field moves your lookup history from the network operator to Quad9. Running your own resolver on a VPS moves it to you. State the trade plainly before you build it.
What you gain: no third party holds a query log with your name on it, because the resolver is a recursive one. It asks the root servers, then the .com servers, then the site's own servers, and no single upstream sees the full list. Unbound also does QNAME minimisation by default, which sends each server only the part of the name it needs, so the .com servers learn example.com and never mail.example.com.
What you take on: the VPS provider can see the encrypted connection from your phone and the plain-text queries the resolver sends out to the authoritative servers, because the last hop of DNS is still unencrypted. Your VPS has one IP address and one user, so a lookup from that address is a lookup by you, where a public resolver mixes you into millions. You also now own uptime and patching. If the VPS is down, your phone has no DNS at all in strict mode.
This is a resolver, not a VPN. Your web traffic still leaves the phone directly. If you already run WireGuard, the better design is to put the resolver on the tunnel address and skip the public certificate entirely, and fixing DNS inside a WireGuard tunnel covers that layout and its failure modes.
Run your own: the parts both paths share
Two paths follow. Unbound is a validating recursive resolver with no interface and about ten lines of config. AdGuard Home is a forwarder with blocklists and a web UI. Pick one, since both want port 853. Both need the same preparation, done once: a hostname that points at the VPS, and a certificate for that hostname that Android will accept. Both also need the firewall opened for 853 and nothing else. These steps run on your VPS, because the resolver needs outbound reach to the whole DNS tree and a certificate issued for a name that points at its public address.
Create an A record, dns.example.com, pointing at the VPS's public IP, and wait until dig +short dns.example.com returns it from your laptop. Certbot cannot issue a certificate for a name that does not resolve to the box.
Open exactly two ports. Port 80 is for the certificate challenge and 853 is for DNS over TLS. Port 53 stays closed to the internet on purpose: an open UDP resolver is an amplification source, since a 60-byte spoofed query can trigger a 3,000-byte answer aimed at whoever the attacker chose as the victim. TLS on TCP cannot be spoofed that way, because the handshake has to complete first.
sudo ufw allow 80/tcp
sudo ufw allow 853/tcp
sudo ufw statusufw status should list 80/tcp and 853/tcp as ALLOW and nothing for 53. If ufw is new to you, the ufw firewall basics for a VPS covers the default deny policy and the SSH rule you must add before enabling it. Most providers also have a separate network firewall in the control panel, and 853 has to be open there too.
Now the certificate. Nothing else is listening on port 80 on a resolver box, so certbot's standalone mode is the simplest option.
sudo apt update && sudo apt install -y certbot
sudo certbot certonly --standalone -d dns.example.com
sudo systemctl list-timers certbot.timerCertbot writes fullchain.pem and privkey.pem under /etc/letsencrypt/live/dns.example.com/ and installs a systemd timer that renews them. sudo certbot renew --dry-run proves renewal works before the real one is due. If certbot fails with a challenge error, the usual cause is the A record still pointing somewhere else, or port 80 blocked at the provider's firewall. The certbot on Ubuntu 24.04 guide goes through the challenge types and the errors each one produces.
Android accepts this certificate because Let's Encrypt's root is in Android's trust store. A self-signed certificate is not, and there is no field on the phone to add one, so the public certificate is not optional.
Path A: Unbound as a validating recursive resolver with TLS
sudo apt install -y unboundOn Ubuntu 24.04 the package enables DNSSEC validation out of the box through /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf, and starts listening on 127.0.0.1 port 53 only. It runs as the unbound user, and it opens the certificate after it has dropped root, so the two PEM files need to be readable by that user. A deploy hook copies them into place on every renewal and restarts the service. Copying is deliberate: /etc/letsencrypt/live is root-only, and loosening its permissions gives every renewal a chance to undo your fix. It also keeps the files inside /etc/unbound, which matters if you ever enable Unbound's chroot.
sudo install -d -o root -g unbound -m 750 /etc/unbound/tls
sudo tee /etc/letsencrypt/renewal-hooks/deploy/unbound.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
LIVE=/etc/letsencrypt/live/dns.example.com
install -o root -g unbound -m 640 "$LIVE/fullchain.pem" /etc/unbound/tls/fullchain.pem
install -o root -g unbound -m 640 "$LIVE/privkey.pem" /etc/unbound/tls/privkey.pem
systemctl restart unbound
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/unbound.sh
sudo /etc/letsencrypt/renewal-hooks/deploy/unbound.shRun the hook by hand once, as the last line does, because certbot only runs deploy hooks after a renewal, and the first issuance already happened.
Now the listener. Debian and Ubuntu load every file in /etc/unbound/unbound.conf.d/, so add one rather than editing the main file.
sudo tee /etc/unbound/unbound.conf.d/dot.conf >/dev/null <<'EOF'
server:
interface: 127.0.0.1
interface: 0.0.0.0@853
tls-port: 853
tls-service-pem: "/etc/unbound/tls/fullchain.pem"
tls-service-key: "/etc/unbound/tls/privkey.pem"
access-control: 0.0.0.0/0 allow
ip-ratelimit: 100
hide-identity: yes
hide-version: yes
prefetch: yes
EOF
sudo unbound-checkconf
sudo systemctl restart unbound
sudo ss -tlnp | grep 853unbound-checkconf should print no errors in /etc/unbound/unbound.conf, and ss should show unbound listening on 0.0.0.0:853. Line by line: the two interface lines replace Unbound's default, so the first keeps plain DNS on localhost for your own tests, and the second binds the public address on 853. tls-port marks 853 as the TLS port, and only interfaces bound with @853 get TLS. access-control: 0.0.0.0/0 allow is the line that makes people nervous, and it is required: a phone changes IP address every time it changes network, so there is no source range to restrict to. The firewall makes it safe. Plain 53 is reachable only from the box itself, and 853/tcp, the only thing the firewall lets through, requires a completed TLS handshake before Unbound reads a query. ip-ratelimit: 100 caps any one client at 100 queries a second, so a stranger who finds the hostname cannot use your VPS as a free resolver at volume. If your VPS has an IPv6 address, add interface: ::0@853 and access-control: ::0/0 allow as well.
If Unbound fails to start after this, read journalctl -u unbound -n 30. Error for server-cert-file: /etc/unbound/tls/fullchain.pem means Unbound could not open the certificate, and the cause is almost always ownership: ls -l /etc/unbound/tls should show group unbound and mode -rw-r----- on both files. can't bind socket: Address already in use on 853 means another service holds the port, which on a fresh box is usually an earlier AdGuard Home install.
Test it from your laptop with dig, which speaks DNS over TLS from BIND 9.18 on, and then check the certificate chain with openssl.
dig @dns.example.com +tls +tls-ca example.com
openssl s_client -connect dns.example.com:853 -servername dns.example.com </dev/null 2>/dev/null | grep 'Verify return'
dig @dns.example.com +tls dnssec-failed.orgThe first command should end with ;; SERVER: 203.0.113.10#853(dns.example.com) (TLS), with your address in place of the example one. +tls-ca makes dig validate the certificate against the system store, which is the same check Android performs. The second should print Verify return code: 0 (ok). The third should return status: SERVFAIL, because dnssec-failed.org is published with a deliberately broken signature and a validating resolver refuses it. A resolver that answers it is not validating.
Path B: AdGuard Home if you want blocking and a web UI
AdGuard Home serves DNS over TLS on the same port 853, adds blocklists and a query log you can read, and is configured from a browser. The difference underneath: it is a forwarder, not a recursive resolver. Every uncached query goes to the upstream listed under Settings > DNS settings, so read the logging notes above when you pick that upstream, or point it at a local Unbound on a second port for full recursion.
Install it with the script from the project README. Download it first if you prefer to read what a script does before it runs as root, which is a reasonable habit.
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -vThe script installs the binary under /opt/AdGuardHome, registers it as a systemd service, and starts the first-run wizard on port 3000. Do not open 3000 in the firewall. Reach the wizard through an SSH tunnel instead, so the admin page never faces the internet.
ssh -L 3000:127.0.0.1:3000 ubuntu@203.0.113.10Then open http://127.0.0.1:3000 in the laptop's browser. In the wizard, set the admin web interface to listen on 127.0.0.1 port 3000, and the DNS server to all interfaces on port 53. On Ubuntu the DNS step fails with bind: address already in use, because systemd-resolved holds port 53 on 127.0.0.53. The fix from the AdGuard Home FAQ tells resolved to stop listening and to use AdGuard Home itself.
sudo mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nDNS=127.0.0.1\nDNSStubListener=no\n' | sudo tee /etc/systemd/resolved.conf.d/adguardhome.conf
sudo mv /etc/resolv.conf /etc/resolv.conf.backup
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl reload-or-restart systemd-resolvedBetween this step and the end of the wizard, the server itself cannot resolve names, because its resolver now points at an AdGuard Home that is not yet serving. Finish the wizard and it comes back. Port 53 is then open on the box, and the firewall still drops it from the internet, which is the arrangement you want.
Encryption is under Settings > Encryption settings. Turn it on, enter dns.example.com as the server name, leave the DNS over TLS port at 853, and choose the file path option for both the certificate and the key: /etc/letsencrypt/live/dns.example.com/fullchain.pem and /etc/letsencrypt/live/dns.example.com/privkey.pem. AdGuard Home runs as root, so it can read them where they are, and it reloads them on its own when certbot renews. The page reports whether the chain and the key parsed, and the expiry date. The same dig and openssl tests from the Unbound section apply.
Point the phone at it
On the phone, open Settings > Network & internet > Private DNS, pick Private DNS provider hostname, and enter dns.example.com. Load a page. On the VPS, an established connection from the phone's address to port 853 is the proof.
sudo ss -tn state established '( sport = :853 )'"Private DNS server cannot be accessed" on the phone means strict mode failed and Android refused to fall back. Check in this order. The A record does not resolve on the network the phone is on, which dig +short dns.example.com from anywhere confirms. Port 853 is closed on the VPS firewall or the provider's firewall, which ss -tlnp on the box and openssl s_client from outside separate. Or the certificate does not match the name you typed, which openssl s_client shows as anything other than Verify return code: 0 (ok). Android compares the certificate's name against the hostname field exactly, so dns.example.com in the field and a certificate for example.com is a failure.
Leave the mode on the hostname permanently, and be ready for the consequence: when the VPS is down, the phone has no DNS at all, on every network. That is strict mode working as designed. Android offers no fallback, and Automatic is not one, since it silently returns you to plain text.
FAQ
Is Private DNS on Android the same as a VPN?
No. Private DNS encrypts one thing, the lookups that turn names into addresses, and sends them to one resolver over TLS on port 853. Your actual traffic still leaves the phone directly, so the network sees every destination IP address and, on most HTTPS connections, the site name in the TLS handshake. A VPN wraps all of it in one tunnel. The two combine well, and a resolver on the VPN's private address needs no public certificate.
What does the Automatic setting actually do?
Android probes port 853 on the DNS server the current network assigned. If it answers TLS, lookups are encrypted to it. If not, which is the case on most home and mobile networks, Android uses plain port 53 and does not tell you. Automatic never checks a certificate against a name, so even when it encrypts, it does not verify who is answering. Only the hostname mode gives a guarantee.
Why does my phone say "Private DNS server cannot be accessed"?
Strict mode failed and Android refused to fall back to plain DNS. In order: the hostname does not resolve to your VPS (dig +short dns.example.com), port 853 is blocked on the VPS or the provider's network firewall, or the certificate is not valid for exactly the name you typed. openssl s_client -connect dns.example.com:853 -servername dns.example.com from a laptop shows which, and a healthy server ends with Verify return code: 0 (ok).
Should I open port 53 on my VPS too?
No. Port 53 is UDP with no handshake, so an open resolver there answers spoofed queries and becomes an amplification source aimed at whoever the attacker chose. Port 853 is TCP with a completed TLS handshake before any query is read, which cannot be spoofed. Keep 53 bound to localhost for your own tests and let the firewall drop it from the internet. How ports work on Linux explains why binding to 127.0.0.1 keeps a port off the network entirely.
Which public resolver should I use if I do not want to run my own?
Pick by what the policy says and where the operator is based. Quad9 states it does not store your IP address at all. Cloudflare truncates IPs and deletes logs within 25 hours. Google keeps full IPs for up to 48 hours and anonymised query logs permanently. AdGuard states it keeps no personal data and a 24-hour anonymous domain list. These are the providers' own statements as read on 12 September 2026, and none of them replaces reading the current policy.