Support Library
DNS over HTTPS (DoH) Testing
How to test DNS over HTTPS with curl, dig and kdig: JSON APIs versus the RFC 8484 wire format, reading the answer, and comparing DoH with classic DNS.
The short answer
DNS over HTTPS (DoH), defined in RFC 8484, carries ordinary DNS messages inside HTTPS requests. Testing it means sending a query to a DoH endpoint and checking three things: that the HTTPS request succeeds, that the DNS answer inside it is the one you expect, and that it matches what the same provider returns over classic DNS on port 53.
The quickest test uses a provider's JSON interface with curl:
$ curl --fail --show-error --header 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=example.com&type=A'
The & between the parameters means "run in the background" to most shells. Without quotes the shell cuts the URL at that point and the query loses its type. The header also has to be passed with --header (or -H); a bare accept:... argument is treated as a second URL.
JSON APIs and the standard wire format
There are two ways to talk to a DoH server, and they are easy to confuse.
| Standard DoH (RFC 8484) | JSON API | |
|---|---|---|
| Media type | application/dns-message | application/dns-json (Cloudflare) or the provider's own format |
| Request | A binary DNS message, sent as a POST body or base64url-encoded in a dns= GET parameter | Readable name= and type= parameters |
| Response | A binary DNS message, the same bytes a server would send over UDP | A JSON object with the status, flags, question and answers |
| Supported by | Every DoH server, and what browsers and operating systems use | Only some providers, each with its own path and fields |
The JSON interface is a convenience for people and scripts, not part of the standard. Cloudflare documents it at https://cloudflare-dns.com/dns-query and Google at https://dns.google/resolve; other providers may not offer it at all. A successful JSON test proves that the provider's API works. It does not prove that a browser or operating system configured for DoH can use the same server, because those clients send wire-format messages.
Reading a JSON answer
{
"Status": 0,
"TC": false, "RD": true, "RA": true, "AD": true, "CD": false,
"Question": [{ "name": "example.com", "type": 1 }],
"Answer": [
{ "name": "example.com", "type": 1, "TTL": 1726, "data": "192.0.2.10" }
]
}
| Field | Meaning |
|---|---|
| Status | The DNS response code: 0 is NOERROR, 2 is SERVFAIL, 3 is NXDOMAIN. An HTTP 200 with Status 2 is a DNS failure, not a DoH failure. |
| AD | The resolver says it validated the answer with DNSSEC. It is the resolver's claim, worth only as much as your trust in that resolver. |
| CD | Echoes whether you asked the resolver to skip validation (cd=1 in the URL) |
| TTL | Seconds left in the resolver's cache, so it counts down between requests |
| type | The numeric record type: 1 is A, 28 is AAAA, 5 is CNAME, 15 is MX |
Testing the standard wire format
To test what real DoH clients use, send a wire-format query. Recent versions of dig (BIND 9.18 and later) and kdig (Knot DNS) can do it directly and decode the answer:
$ dig @cloudflare-dns.com +https example.com A
$ kdig @1.1.1.1 +https example.com A
With curl alone you can send the example query from RFC 8484, which asks for the A record of www.example.com. The reply is binary, so save it to a file or pipe it through a hex viewer rather than printing it to the terminal:
$ curl --fail --show-error --header 'accept: application/dns-message' \
--output reply.bin \
'https://cloudflare-dns.com/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB'
An HTTP 200 with content-type: application/dns-message shows the endpoint works. HTTP 415 means the server did not accept the media type, and 400 usually means the dns= value was not valid base64url.
Comparing DoH with classic DNS
Ask the same provider the same question both ways:
$ dig @1.1.1.1 example.com A +noall +answer
$ dig @cloudflare-dns.com +https example.com A +noall +answer
| What you see | Likely explanation |
|---|---|
| Same data, different TTLs | Normal. The two requests may reach different cache instances. |
| Different addresses for a CDN-hosted name | Often normal: answers can depend on which site served you and on EDNS Client Subnet handling. |
| Port 53 gives a block page address, DoH gives the real one | Something on your network, such as a filtering resolver or a middlebox, is intercepting classic DNS. |
| DoH fails with a TLS or connection error, port 53 works | HTTPS to the DoH host is blocked or intercepted, or the certificate is not trusted on this machine. |
| Both return SERVFAIL | The problem is the domain or its DNSSEC, not the transport. See DNS SERVFAIL Explained. |
How DNS Panopticon helps
Domain Visualizer runs DNS-over-HTTPS lookups with DNSSEC data requested. Cloudflare and Google are queried through their JSON interfaces. Quad9 is queried with standard RFC 8484 wire-format DoH, because Quad9 retired its JSON service on May 5, 2025. It uses Cloudflare by default and can repeat the main lookups on a second resolver when one is selected and the scan's request budget allows, reporting any differences. Because it runs from DNS Panopticon's servers, it tells you what those public resolvers return, not whether DoH works from your own browser or network. Use the commands above for that.