๐ŸŽ‰ Premium Proxies ยท 24-Hour Free TrialClaim Now
Proxy Basic

What Is DNS-over-HTTPS and How It Affects Proxy Traffic

DNS over HTTPS moves resolution into the browser, past the OS and sometimes past your proxy. How DoH and DoT change proxy traffic, CDN edges and geo answers.

S SparkProxy 1 19 min read
Share

DNS over HTTPS wraps DNS queries inside ordinary HTTPS requests on port 443, and because an application can run its own DoH resolver independently of the operating system, your browser may resolve a hostname from your own network even when the rest of the request leaves through a proxy in another country, which quietly changes which CDN edge answers you.

That is the whole problem in one sentence. Below: the mechanism, the two standards involved, why the resolver's location rather than your exit IP decides a GeoDNS answer, and how to put resolution back on the intended path.

What DoH and DoT Actually Change

Classic DNS runs over UDP and TCP port 53 in plaintext, so every device on the path can read the query and rewrite the answer. Encrypted DNS closes that gap in two ways.

DNS over TLS, specified in RFC 7858 (Hu, Zhu, Heidemann, Mankin, Wessels and Hoffman, May 2016, Standards Track), takes the existing DNS wire format and runs it inside a TLS connection on port 853.

DNS over HTTPS, specified in RFC 8484 (Hoffman and McManus, October 2018, Standards Track), labels that same wire format with the media type application/dns-message and sends it as an HTTP request. Servers must implement both GET, where the query is a base64url-encoded dns parameter, and POST, where it sits in the body. Clients are configured with a URI template rather than an IP address, and /dns-query is the de facto path.

Neither protocol changes DNS semantics. Same record types, same TTLs, same authoritative servers, same answers in principle. Encryption is not the interesting part for proxy work.

The part that actually matters

Turning on DoH almost always changes which resolver you use, not just how you talk to it. Your ISP resolver in Lisbon gets replaced by an anycast resolver run by Cloudflare, Google or Quad9. That resolver sits somewhere else, applies different policy about forwarding client information upstream, and now answers questions on behalf of one application rather than the whole machine. Those three changes, not the TLS, are what move your CDN edge.


DoH on 443 vs DoT on 853

The two protocols carry identical payloads and behave completely differently on a network you do not control.

DoH (RFC 8484)DoT (RFC 7858)
TransportHTTP/2 or HTTP/3 over TLS, port 443Raw DNS messages over TLS, port 853
Framing`application/dns-message`, GET and POST both mandatoryLength-prefixed DNS messages, as over TCP/53
Endpoint formURI template, commonly `https://host/dns-query`Hostname plus port 853
Visible on the wire?No, it looks like any other HTTPS request to that hostYes, the port number alone identifies it
Blocked how?Only by blocking the host, SNI or IPDrop port 853
Typical scopePer application, usually a browserSystem wide
Common deploymentsFirefox TRR, Chrome Secure DNS, `dnscrypt-proxy`, `cloudflared`Android Private DNS, `systemd-resolved` with `DNSOverTLS=`

Read the "typical scope" row twice. It is the operational split that causes proxy surprises. DoT is normally an operating system decision applied to every process, so your scraper inherits it and behaves consistently. DoH is normally a per-application decision, so your browser can resolve through Cloudflare while curl on the same host resolves through the corporate resolver, and neither knows about the other.

RFC 7858 chose a dedicated port on purpose, to reduce downgrade risk. The side effect is that any operator can neutralize DoT with one firewall rule, after which a client running the opportunistic privacy profile falls back to plaintext 53. DoH has no such switch. RFC 8484 notes that mixing DNS in with other HTTPS on shared connections makes traffic analysis harder, which is a feature for users and an obstacle for anyone enforcing one resolution path across a fleet.

A third option exists. DNS over QUIC, RFC 9250 (May 2022), also uses port 853 but over UDP. For this article it behaves like DoT: system scoped and identifiable by port.


Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

The Browser Surprise: Your Browser Is Its Own Resolver

Most people configuring a proxy assume there is one resolver on the machine. With DoH enabled in a browser there are at least two, and the browser's own resolver wins for everything the browser loads.

Firefox

Firefox calls its DoH client the Trusted Recursive Resolver. The behaviour is controlled by network.trr.mode in about:config:

  • 0 = off, use the OS resolver
  • 2 = TRR first, fall back to the OS resolver on failure
  • 3 = TRR only, no fallback
  • 5 = off by explicit user choice, which also suppresses automatic re-enablement

Mode 3 is the one that bites. Firefox then never consults the operating system resolver, so anything you arranged at the OS level stops applying inside the browser: hosts file entries, a local dnsmasq, split-horizon internal DNS, or a DoH sidecar you pointed /etc/resolv.conf at. The page loads, nothing errors, and the answer comes from a resolver you did not choose.

Chrome

Chrome exposes "Use secure DNS" under chrome://settings/security, and its default is auto-upgrade: if the OS-supplied resolver appears on Chrome's list of known DoH providers, Chrome silently moves that provider from port 53 to its DoH endpoint. You never chose DoH, and the settings page still reads as the default. In managed environments the DnsOverHttpsMode policy pins this to off, automatic or secure, and a leftover corporate policy is a common reason a scraping VM behaves differently from a developer laptop.

Why this matters more for automation than for people

A human browsing with DoH gets a mild privacy win. An automation profile with DoH left on gets a resolver bound to the machine running the browser, not to the proxy that browser was told to use. Change exit country per session and the resolver does not follow. Every session resolves from the same place.


What Happens When a Proxy Enters the Picture

Two independent questions decide the outcome. Who performs the lookup, and where does that resolver sit? Encryption is a third question and it barely matters.

Browsers handle the simple cases correctly. Configure an HTTP proxy and the browser issues CONNECT host:443, so the proxy resolves and there is nothing for DoH to do. Chromium's proxy documentation states that for socks5:// proxies name resolution "is always done proxy side," with no preference to change it; socks4:// is always client side and IPv4 only. Firefox exposes network.proxy.socks_remote_dns for the same choice, and ships an explicit escape hatch: its source documentation lists TRR_HEURISTIC_TRIPPED_PROXY (skip reason 46) as the case where TRR is disabled "by a proxy being detected." Firefox turns its own DoH off when it notices a proxy. Which component resolves under each protocol is covered in our guide to HTTP, HTTPS and SOCKS5 proxy protocols.

So where does it break? In every setup that is not a plain browser with a plain proxy:

  1. Partial proxying. A PAC file or a proxy extension routes some hostnames through the proxy and the rest direct. DoH stays on for the direct set, and the proxied set now sees a mixture of edges.
  2. Automation profiles carrying stale prefs. A Firefox profile with network.trr.mode=3 baked in, or a Chrome profile under a DnsOverHttpsMode: secure enterprise policy, keeps resolving through its own DoH endpoint while Playwright or Selenium sets a per-context proxy.
  3. Host-level encrypted DNS. Android Private DNS, systemd-resolved with DNSOverTLS=yes, or a dnscrypt-proxy container sidecar pins every non-proxied lookup on the box to one encrypted resolver in one location, regardless of which exit you rented for that job.
  4. Tools that resolve before they connect. Anything handing an IP address to the proxy rather than a hostname has already made the geo decision locally. That includes curl --doh-url, which turns curl itself into a DoH client.

Whether any of this counts as a leak is a separate question with its own methodology, and we cover that in the guide to proxy DNS leak testing and mitigation. The concern here is narrower and sneakier: even when nothing leaks, the answer you receive can belong to the wrong country.


CDN and GeoDNS: The Resolver's Location Decides

Here is the mental model most proxy users get wrong.

A GeoDNS authoritative server picks its answer from where the query appears to come from. It never sees your exit IP, because at DNS time no connection to the target exists yet. It sees the recursive resolver's IP address, plus an optional client subnet hint if one was attached.

That means two different addresses control two different things:

  • The resolver-facing address decides which IP the CDN hands you, and therefore which edge, which regional content variant and which regional rate-limit bucket.
  • The exit IP decides where the TCP connection originates, and therefore what the target's server logs and geo-IP checks record.

Nothing forces those two to agree. When a browser resolves via DoH from a laptop in Ohio and then connects through a German exit, the CDN answers as if the visitor were in Ohio while the web server sees a German visitor. Both layers are internally consistent. The combination is nonsense.

Anycast complicates this usefully. 1.1.1.1 and 8.8.8.8 are anycast addresses, so the POP that answers is usually near whoever asked. That is why DoH does not always break geo-targeting: a query originating at the proxy exit hits a POP in the same region and the CDN steers correctly by accident. The failure appears when the query originates somewhere other than the exit, which is exactly what browser-level DoH arranges. If you depend on regional answers, read this alongside how geo-targeting in proxies is supposed to work.


EDNS Client Subnet and Why DoH Resolvers Strip It

There is a mechanism designed to fix exactly this mismatch, and the popular DoH resolvers deliberately disable it.

EDNS Client Subnet, RFC 7871 (Contavalli, van der Gaast, Lawrence and Kumari, May 2016, Informational), lets a recursive resolver attach a truncated prefix of the client's address to its upstream query. The authoritative server then answers for that prefix instead of for the resolver. RFC 7871 recommends truncating to 24 bits for IPv4 and 56 bits for IPv6 to limit exposure, and it is blunt about the tradeoff: the feature "SHOULD be disabled in all default configurations." A client that wants no part of it sends an ECS option with SOURCE PREFIX-LENGTH 0, which intermediate nameservers must honour by adding no address information at all.

Privacy-focused resolvers took that advice seriously. Here is what the major public DoH endpoints actually do upstream:

ResolverDoH endpointSends ECS upstream?
Cloudflare`https://cloudflare-dns.com/dns-query`No, documented, with one debug-domain exception
Google Public DNS`https://dns.google/dns-query`Yes
Quad9 default`https://dns.quad9.net/dns-query`No
Quad9 ECS variant`https://dns11.quad9.net/dns-query`Yes
Quad9 unsecured`https://dns10.quad9.net/dns-query`No

Cloudflare's resolver FAQ states that 1.1.1.1 "does not include client IP information in its queries to authoritative servers" and "does not send the EDNS Client Subnet (ECS) header," with a single carve-out for the Akamai debug domain whoami.ds.akahelp.net. Quad9 publishes ECS support as a per-endpoint property: dns.quad9.net and dns11.quad9.net are the same service with the client-subnet signal switched off and on.

The Quad9 pair is a lever almost nobody uses. If you run your own resolver near the exit and need GeoDNS answers to track the exit's network rather than just its broad region, pointing that resolver at the ECS-preserving endpoint restores the signal, while the default endpoint from the same operator throws it away. On the browser side, Firefox exposes network.trr.disable-ECS in about:config; read its value in the profile you actually ship rather than trusting any blog post about its default.

One distinction produces identical symptoms from opposite causes. A missing client-subnet signal can mean the resolver refused to forward one, or that your client sent SOURCE PREFIX-LENGTH 0 and the resolver obeyed. Both hand you an answer chosen for the resolver's location, and only direct measurement tells you which happened.


Forcing Resolution Through the Intended Path

The design rule is short. Exactly one component should own resolution, and it should be the component sitting at the exit. Ranked from most reliable to least:

1. Resolve server-side by never sending a hostname locally. Hand the target URL to a fetcher that runs next to the exit. The only name your machine resolves is the API host.

2. Use an HTTP proxy and let CONNECT do its job. The protocol requires the proxy to resolve, so there is no client-side decision to get wrong.

# The proxy resolves target.sparkproxy.io at the exit, not on this machine
curl -x http://user:pass@gateway.sparkproxy.io:11000 \
     -s -o /dev/null -w 'edge=%{remote_ip}\n' \
     https://target.sparkproxy.io/pricing

3. Use SOCKS5 with remote DNS. In curl and PySocks that is the socks5h scheme. Chrome already resolves proxy side for socks5://. In Firefox, set network.proxy.socks_remote_dns to true.

# socks5h sends the hostname to the proxy; plain socks5 resolves here first
curl --proxy socks5h://user:pass@gateway.sparkproxy.io:13000 \
     -s -o /dev/null -w 'edge=%{remote_ip}\n' \
     https://target.sparkproxy.io/pricing

4. If something must resolve locally, pin the answer. --resolve maps a host and port to an address you already trust; --connect-to redirects the connection without touching SNI or certificate validation.

# Pin the edge you actually want; TLS still validates against the real hostname
curl --resolve target.sparkproxy.io:443:203.0.113.45 \
     -sI https://target.sparkproxy.io/pricing | head -1

Turn browser DoH off explicitly, do not rely on the heuristic

Firefox's proxy detection is helpful, but it is a heuristic with a numbered skip reason, not a contract. In an automation profile, set the prefs:

// user.js in the Firefox profile directory
user_pref("network.trr.mode", 5);
user_pref("network.proxy.socks_remote_dns", true);

For Chrome, set DnsOverHttpsMode to off by policy and confirm it at chrome://policy, not at the settings page, because a policy-managed value and a user-visible default look identical at a glance. Our walkthrough on configuring proxies in Chrome and Firefox covers where these settings live.

Avoid the self-inflicted version too: do not combine curl --doh-url with -x. That makes curl its own DoH client, resolves the target locally over HTTPS, then sends the resulting address through the proxy. It reproduces the browser problem inside a CLI tool.


Diagnosing What Actually Resolved

Three checks, ordered by how much they tell you.

Which resolver reached the authoritative server, and with what client subnet. Akamai runs a debug zone that echoes both back:

dig +short TXT whoami.ds.akahelp.net
# "ns" "162.158.x.x"      <- the resolver IP the authoritative server saw
# "ecs" "203.0.113.0"     <- the client subnet, if one was forwarded at all

An absent ecs line means no client-subnet hint reached the authoritative server, so the answer was chosen for the resolver's location. Google publishes an equivalent: dig +short TXT o-o.myaddr.l.google.com.

Which edge you actually connected to, per exit. Run the same request through two exit countries and compare:

for CC in de jp; do
  curl -s -o /dev/null -w "$CC edge=%{remote_ip}\n" \
    -x "http://user-country-$CC:pass@gateway.sparkproxy.io:11000" \
    https://target.sparkproxy.io/pricing
done

Identical edge IPs from Germany and Japan mean resolution is not following your exit. Different edge IPs mean it is.

What the browser itself did. Firefox exposes about:networking#dns, which lists each cached entry with a TRR column showing whether DoH answered that name, plus about:networking#dnslookuptool for a one-off lookup. Chrome's equivalent is a chrome://net-export capture, which records the host-resolution source per request.

Run all three from inside the environment you are debugging, containers included, because a container's /etc/resolv.conf frequently differs from its host's.


The Scraping Consequence: Right Exit, Wrong Edge

The reason this class of bug survives so long in production is that it produces no errors.

Consider regional price monitoring. The job rents a German residential exit, the browser has DoH on with the default Cloudflare endpoint, and the machine sits in Ohio. Firefox resolves shop.sparkproxy.io through a Cloudflare POP near Ohio. No client subnet is forwarded, so the CDN answers for that POP with a US edge address, and the browser opens a TLS connection to that edge through the German exit. The certificate validates, the page renders, your extractor fills every field. You collected US-edge content and labelled it Germany.

Two second-order effects follow, and both get misdiagnosed:

  • Concentration. If most of your fleet resolves through the same DoH resolver from the same datacenter, every session lands on the same handful of edge IPs regardless of exit diversity. You trip that edge's rate limits while the dashboard shows a healthy spread of exit addresses, so adding more exits changes nothing.
  • Cheap inconsistency signals. A request whose TCP source is Frankfurt, whose edge assignment says Ohio, and whose headers claim de-DE hands an anti-bot system a free correlation to flag. No individual signal is wrong. Their combination is not something a real German visitor produces.

Neither shows up as a DNS problem in your logs. They show up as "the German data looks like the US data" and "we started getting blocked and nobody knows why."


Removing the Problem Server-Side

Every failure above needs one precondition: your machine resolves the target hostname. Take that away and the whole class disappears.

With the SparkProxy Scraping API you pass the target URL as a parameter. The only hostname your host resolves is scrape.sparkproxy.io. The target name is resolved server-side, next to the exit that fetches it, so the resolver's region and the exit's region are the same region by construction.

curl -X GET "https://scrape.sparkproxy.io/api/v1?url=https://shop.sparkproxy.io/pricing&render_js=true&premium_proxy=true&country_code=de&format=md" \
  -H "X-API-Key: YOUR_API_KEY"

The same call in Python, using the JSON envelope so you can log behaviour over time:

import requests

resp = requests.get(
    "https://scrape.sparkproxy.io/api/v1",
    params={
        "url": "https://shop.sparkproxy.io/pricing",
        "render_js": "true",
        "premium_proxy": "true",
        "country_code": "de",
        "json_response": "true",
        "tag": "geo-check-de",
    },
    headers={"X-API-Key": "YOUR_API_KEY"},
    timeout=120,
)

data = resp.json()
print(data["status_code"], data["duration_ms"], data["credits_used"])

Comparing two regions is then one changed parameter:

for cc in ("de", "us"):
    r = requests.get(
        "https://scrape.sparkproxy.io/api/v1",
        params={"url": "https://shop.sparkproxy.io/pricing",
                "premium_proxy": "true", "country_code": cc,
                "format": "md", "tag": "geo-check-" + cc},
        headers={"X-API-Key": "YOUR_API_KEY"},
        timeout=120,
    )
    print(cc, r.status_code, len(r.text))

There is no trr.mode to audit, no socks5h to remember and no OS-level Private DNS profile quietly overriding your intent. Keeping the client under your own control is a legitimate choice, and the tradeoffs between a scraping API and self-managed proxies run well beyond DNS. Either way, pick one owner for resolution and prove it with the whoami.ds.akahelp.net check above. Questions on either path go to support@sparkproxy.io.


Frequently asked questions

FAQ

No. DoH encrypts the lookup between your client and the DoH resolver, and it says nothing about the connection that follows. If your browser resolves via DoH and then connects through a proxy, the proxy still sees the destination address, the SNI on an unencrypted ClientHello, and the full timing of the session.

For automation, yes. Set network.trr.mode to 5 in Firefox and DnsOverHttpsMode to off by policy in Chrome, then let the proxy resolve. Firefox does try to detect proxies and skip its own resolver, but that is a heuristic with a documented skip reason rather than a guarantee you should build a fleet on.

DoT is easier to reason about, because it is normally configured once at the operating system level and every process on the host then agrees on one resolver. DoH is riskier for proxy work precisely because each application picks its own endpoint, which is how a browser ends up resolving through a different country than the exit it was told to use.

Only when the query originates somewhere other than your exit. Cloudflare and Google both run anycast resolvers, so a query sent from the exit usually reaches a nearby POP and the CDN steers correctly. A query sent from your own machine reaches a POP near you, and the CDN then returns an edge near you rather than near the exit.

Google Public DNS forwards a truncated client subnet upstream. Cloudflare's 1.1.1.1 does not, and documents a single exception for the Akamai debug domain. Quad9 splits it by endpoint: dns.quad9.net sends no client subnet, while dns11.quad9.net is the ECS-enabled variant of the same service.

Query whoami.ds.akahelp.net for a TXT record from inside the environment in question and read the ns value, which is the resolver IP the authoritative server saw. In Firefox, about:networking#dns also shows a per-entry TRR column indicating whether DoH answered that specific name.


Special Discount ยท 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

The SparkProxy Technical Team builds and operates SparkProxy's datacenter proxies, residential proxies and Scraping API. We spend most of our time on the parts of proxy infrastructure that fail quietly rather than loudly: resolver placement, edge selection, geo consistency, and the gap between what a request claims and what it actually did. Everything above was checked against RFC 7858, RFC 8484 and RFC 7871, the Chromium and Firefox networking documentation, and the published resolver policies of Cloudflare and Quad9. Corrections go to support@sparkproxy.io.

Keep reading

Related articles

What Is DNS Resolution and How Proxies Handle It

What Is DNS Resolution and How Proxies Handle It

How DNS resolution works, where it happens in a proxied request, and why socks5 vs socks5h and HTTP CONNECT decide whether your client or the proxy resolves.

SparkProxyยทProxy Basic
Why Antidetect Browsers Need Proxies

Why Antidetect Browsers Need Proxies

Why antidetect browsers need proxies: the browser controls what a page reads, the proxy controls where packets come from. Detection scores both layers.

SparkProxyยทProxy Basic