Two different questions
You set a DNS-over-HTTPS URL somewhere, then run a leak test, and it names the resolver you configured. It feels like confirmation. It is confirmation of one thing only.
A leak test works by asking you to resolve names it controls, then reporting which addresses queried its authoritative servers. What reaches it is the recursive resolver that did the lookup. It has no view of the hop between your device and that resolver - which is exactly the hop encryption protects. The two questions are:
- • Which resolver answered? A leak test answers this well. It is the right tool for finding out that your VPN or router quietly redirected you somewhere else.
- • Was the query encrypted in transit? A leak test cannot answer this at all. Plaintext UDP port 53 to a resolver and DNS-over-HTTPS to the same resolver produce an identical leak-test result.
Both configurations are "not leaking". Only one of them is private on the local network, which is where the observer you were worried about usually sits. If you want the first question answered, our leak test and the guide to how a leak test actually works cover it. The rest of this page is the second question.
Four combinations exist. A leak test distinguishes two of them.
Check DNS over HTTPS from the command line
The direct test is to speak the protocol yourself. A DoH query is an HTTP request carrying a wire-format DNS message, so curl is enough. This asks for example.com:
curl -s -o /dev/null -w 'http=%{http_version} code=%{http_code}\n' \
-H 'accept: application/dns-message' \
'https://dnsdoh.art/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
http=2 code=200
code=200 means the endpoint answered a real DNS query over TLS. That base64 string is a fixed, standard query for example.com A - it is the same for every server, so you can point this at any DoH URL and change nothing else.
Two failure modes are worth recognising. A 400 usually means the server is a DoH endpoint but disliked the query. Anything that hangs or returns 000 means you did not reach a DoH server at all, which includes the common case of a captive portal or filtering middlebox intercepting 443.
Do not test a DoH endpoint with curl -I. A bare HEAD /dns-query against our endpoint returns nothing at all and curl exits 92. That is the endpoint declining a request with no query in it, not a broken server. We wasted a check on exactly this. Always send the ?dns= parameter.
Check DoT and DoQ
DNS over TLS and DNS over QUIC are not HTTP, so curl cannot speak them. The most convenient client is q, which takes the transport in the server URL:
# DNS over TLS, port 853
q -s tls://dnsdoh.art example.com A
# DNS over QUIC, port 853/udp
q -s quic://dnsdoh.art example.com A
example.com. 5m A 104.20.23.154
example.com. 5m A 172.66.147.243
An answer is the whole test. If the transport were unavailable you would get a connection error rather than records, because there is no fallback to plaintext in either protocol.
If you reach for kdig instead and it reports handshake failed (The requested data were not available.), that is a client bug rather than a server problem: versions before Knot 3.4.7, including the 3.3.4 in Ubuntu 24.04, abort against certificates issued with no Subject field. Ours is one such certificate and it verifies cleanly with gnutls-cli and openssl s_client.
Check DoH over HTTP/3
DoH3 is the same DoH request carried over QUIC instead of TCP. One flag separates them, and the useful part of the output is the version curl reports back:
curl -s --http3 -o /dev/null -w 'http=%{http_version} code=%{http_code}\n' \
-H 'accept: application/dns-message' \
'https://dnsdoh.art/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE'
http=3 code=200
http=3 is the confirmation. A plain curl without the flag will report http=2 even against a server that supports HTTP/3, because the first connection has no reason to try QUIC yet. Not every curl build can do this: it needs an HTTP/3-capable build, which curl --version will list.
How a browser gets there without a flag is the alt-svc header, which the endpoint returns on an ordinary query:
alt-svc: h3=":443"; ma=86400
Checking on Windows, macOS, iOS and Android
Everything so far assumes a shell. A phone does not have one, and on Windows and macOS the built-in indicator answers a narrower question than most people read it as. Every operating system will happily show you what you configured. None of them shows you what happened to your last lookup.
| Platform | Where the setting lives | What it proves |
|---|---|---|
| Windows 11 | Settings, Network & internet, your adapter, DNS server assignment. Encryption reads Encrypted only (DNS over HTTPS). | Configuration only |
| macOS | System Settings, Privacy & Security, Profiles - the installed DNS profile is listed there. | Configuration only |
| iPhone, iPad | Settings, General, VPN & Device Management, then the installed DNS profile. | Configuration only |
| Android | Settings, Network & Internet, Private DNS, provider hostname. | Configuration only, and see below |
Windows has a command that looks like it settles the question and does not. Get-DnsClientDohServerAddress in PowerShell lists the DoH templates the client knows about for given server addresses. That is a table of what Windows would use, not a record of what it did use. The same caution applies to reading a template out of the registry.
macOS is not an exception, though an earlier version of this guide said it was. scutil --dns prints Apple's dnsinfo structure, and that structure has no field for a DoH template: a resolver record carries nameserver addresses, search domains, flags, port, timeout and order, and nothing that could hold https://dnsdoh.art/dns-query. The command cannot show an encrypted profile's URL whether the profile took or not. Every row in that table is configuration, not proof.
Correction, 31 August 2026. Until today this section said macOS was the exception in the table: that scutil --dns "reports the resolver configuration the system has actually loaded", that it was therefore the verification step on our setup page, and that "if the profile did not take, the name is simply absent". The table row was marked Resolver actually in use. All of that is withdrawn.
The evidence is Apple's own source. scutil --dns prints the dnsinfo configuration through configd, and in dnsinfo_logging.h the fields it emits per resolver are exactly: domain, search domain, nameserver, sortaddr, options, port, timeout, if_index, service_identifier, flags, reach, order and config id. dnsinfo.h declares no DoH, HTTPS, TLS, URL or template member at all. A DoH profile configures a URL, and there is nowhere in that structure to put one.
So the check we published could not have worked, and the absence it told readers to treat as failure proves nothing either way. The corrected step on the setup page is the profile list in System Settings, plus the browser leak test for what actually resolves. We had already written the same caution about Get-DnsClientDohServerAddress on Windows one paragraph earlier and did not apply it to a platform none of us has.
Android's Private DNS is DNS over TLS, not DoH. We measured this on a Galaxy S21: the system setting produces DoT on port 853 and nothing else, whatever hostname you give it. That is genuinely encrypted and it covers every app on the device. But if you set it and then look for evidence of DNS over HTTPS, you will not find any, and nothing is broken. A Chromium browser's own Secure DNS setting is separate, applies to that browser only, and does reach DoH over HTTP/3.
The check that works on every platform: a browser
A browser is the one tool present on all four systems, and one of them reports real state rather than configuration.
Firefox, on any desktop platform, is the best built-in check that exists. Settings, then Privacy & Security, scroll to DNS over HTTPS. It prints a Status line reading Active, Off, or Not active with the reason in brackets, plus a Provider line naming the resolver. The bracketed reason is the most valuable diagnostic in any of this, because it tells you why Firefox fell back instead of leaving you to guess.
Chrome, Edge and Brave have no equivalent status page. On desktop, open DevTools and use the Security panel, which describes the current connection: "The connection to this site is encrypted and authenticated using ..." with the protocol, key exchange and cipher, and names Encrypted ClientHello when it is in use. That describes the page you are on rather than DNS, so it confirms ECH and TLS, not your resolver.
On a phone there is no DevTools and no status line. Safari and mobile Chrome expose neither, so the only observation available is behavioural: load the leak test to see which resolver did the lookup, and the connection page to see the HTTP and TLS version your browser actually negotiated. Between them you learn which resolver answered and whether your connection to us is what you expected - which is as far as a phone browser can take you without installing anything.
A browser setting covers the browser. Secure DNS in Chrome does nothing for your mail client, your package manager, or any other app. An OS-level setting covers everything, which is why the two are worth checking separately - and why a device can be encrypted in one and plaintext in the other at the same time. The per-platform steps for setting it are on the setup page.
Check ECH, and what it hides
Encrypted DNS hides which names you look up. It does not hide the name you then connect to, because that name is sent in the clear in the TLS handshake unless Encrypted Client Hello is in use. Checking whether a site offers ECH is a DNS lookup, since the key is published in the HTTPS record:
dig +short HTTPS dnsdoh.art
1 . alpn="h3,h2,http/1.1" ech=AEH+DQA9AgAgACBJKPwET/KL8YQLoeVc4Gl306d0v
K1FgBb4llpxekrUEQAIAAEAAQABAAMACmRuc2RvaC5hcnQAAA==
The presence of an ech= parameter is the answer: this name publishes an ECH key and a browser that supports ECH will use it. Decoding that blob gives version 0xfe0d, config id 2, an X25519 key of 32 bytes, and the public name dnsdoh.art - the name that appears in the clear instead of the real one.
The alpn parameter in the same record is worth reading while you are there. It advertises HTTP/3, HTTP/2 and HTTP/1.1, which is how a client knows to try QUIC before making a connection at all.
A flag existing is not a feature existing. curl documents --ech, and on this machine using it prints the installed libcurl version does not support this. The option is compiled in conditionally. Check what your build actually has with curl --version before concluding anything about the server.
Check what a device would discover on its own
Modern clients can find an encrypted endpoint without being told, using Discovery of Designated Resolvers. If you want to see what a device is offered before it commits, ask the resolver directly:
dig +short SVCB _dns.resolver.arpa @194.180.189.33
1 dnsdoh.art. alpn="h3,h2" port=443 ipv4hint=194.180.189.33 key7="/dns-query{?dns}"
2 dnsdoh.art. alpn="doq" port=853 ipv4hint=194.180.189.33
3 dnsdoh.art. alpn="dot" port=853 ipv4hint=194.180.189.33
Three designations, in the order the operator recommends them: DoH over HTTP/3 or HTTP/2 first, then DoQ, then DoT. The client picks from what it supports, which is why a plain address can upgrade itself on Windows and stays plaintext on a device with no DDR support. key7 is the dohpath, the URL template a client fills in.
Three ways to get a confident wrong answer
Each of these has cost us a wrong conclusion at some point, and all three look like a server fault when they are not.
- • Your TLS client cannot do what you are testing for. The openssl in Ubuntu 24.04 is 3.0, which has no ML-KEM. Point it at an endpoint that offers post-quantum key exchange and it negotiates the classical group and reports exactly that - Server Temp Key: X25519 - which reads as "the server does not support it" and means "I do not support it".
- • You tested the wrong verb. A DoH endpoint that ignores HEAD is not down. Send a real query.
- • You tested the wrong layer. A page that loads over HTTP/3 tells you nothing about DNS, and a DNS setting inside a browser tells you nothing about the rest of the machine. Match the test to the layer you changed.
There is a general shape here: a negotiated protocol has two ends, and a test only ever reports the intersection. A fourth way to be wrong is that something else took the resolver over without telling you - Tailscale does this by default, and no check above will call it a fault. Before concluding anything about the far end, check that your own end is capable of the thing you are looking for.
What our own pages report
Two of them are useful here, and it is worth being precise about their limits.
The leak test answers the first question only: which resolvers performed your lookups. That is the right tool for catching a VPN or router redirect, and it is not evidence about encryption, for the reason this page opens with.
The connection page reports the properties of your current connection to this site: the HTTP version, the TLS version and cipher, your address and network, and the headers your client sent. It is the quickest way to confirm that a browser really is reaching a site over HTTP/3, or to see what a request discloses. It does not report the key exchange group or ECH status, so for those use the commands above.
The short version
| Question | What answers it |
|---|---|
| Which resolver answered? | a leak test |
| Is DoH reachable? | curl with ?dns= , expect code=200 |
| Is it really HTTP/3? | curl --http3 , expect http=3 |
| Do DoT and DoQ work? | q -s tls:// and q -s quic:// |
| Is my browser using DoH? | Firefox status line; Chrome has none |
| Does this site offer ECH? | dig +short HTTPS name , look for ech= |
| What would a device discover? | dig SVCB _dns.resolver.arpa @resolver |
If a single check has to stand in for all of them, make it the DoH one with a real query, because it is the only one that fails loudly when something in the middle is interfering. A test that cannot fail is not a test, and "the leak test looked fine" is the encrypted-DNS version of that.