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'
Quote the URL.

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 typeapplication/dns-messageapplication/dns-json (Cloudflare) or the provider's own format
RequestA binary DNS message, sent as a POST body or base64url-encoded in a dns= GET parameterReadable name= and type= parameters
ResponseA binary DNS message, the same bytes a server would send over UDPA JSON object with the status, flags, question and answers
Supported byEvery DoH server, and what browsers and operating systems useOnly 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" }
  ]
}
FieldMeaning
StatusThe 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.
ADThe resolver says it validated the answer with DNSSEC. It is the resolver's claim, worth only as much as your trust in that resolver.
CDEchoes whether you asked the resolver to skip validation (cd=1 in the URL)
TTLSeconds left in the resolver's cache, so it counts down between requests
typeThe 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 seeLikely explanation
Same data, different TTLsNormal. The two requests may reach different cache instances.
Different addresses for a CDN-hosted nameOften 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 oneSomething 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 worksHTTPS to the DoH host is blocked or intercepted, or the certificate is not trusted on this machine.
Both return SERVFAILThe 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.

Sources and further reading