Skip to content
DnsLister Forum

Where domain hunters compare notes

Encrypted Client Hello on EU targets: why skipping DoH flags your scraper

For years, inspecting TLS handshakes was mostly a matter of matching cipher suites and ordering TLS extensions. If you used a tool that generated a clean JA3 or JA4 hash matching Google Chrome, you could route your connection through a decent residential proxy and expect to get through without much resistance.

Over the past year or two, European sites behind Cloudflare, Fastly, and custom Akamai setups have quietly rolled out Encrypted Client Hello (ECH).

ECH alters how a client establishes an encrypted session. Instead of broadcasting the target domain in cleartext inside the Server Name Indication (SNI) field of the initial TLS packet, the client encrypts the real SNI so that intermediary network observers cannot see which domain is being requested.

This sounds like a privacy upgrade for ordinary users, which it is. But for automation engineers, it introduces an entirely new detection vector. Real desktop browsers do not just decide to use ECH out of nowhere. They discover ECH keys by issuing specialized DNS queries before the TCP connection ever begins. If your scraping script uses standard operating system DNS resolution and transmits a classic cleartext SNI to a target that explicitly advertises ECH, you loose your browser disguise before the server even sends its first response packet.

The transition from cleartext SNI to ECH

In standard TLS 1.3, almost everything after the initial greeting is encrypted, but the initial ClientHello packet remains unencrypted. The SNI extension sits in that open packet in plain text so the receiving edge server knows which virtual host and SSL certificate to serve.

ECH splits this handshake into two distinct layers:

  • The outer ClientHello: This packet is visible on the wire, but it only contains a public cover domain (often the CDN provider's own domain or a shared frontend host).
  • The inner ClientHello: This contains the actual destination domain you want to scrape, along with your real cryptographic extensions, encrypted using Hybrid Public Key Encryption (HPKE).

When the edge server receives the packet, it uses its private key to decrypt the inner ClientHello and routes the request internally to the target origin.

Anti-bot systems monitor this handshake closely. If a domain has published active ECH parameters, but an incoming client claiming to be Chrome 120 or newer sends a traditional cleartext SNI without any ECH extension, the CDN instantly registers a protocol inconsistency.

How DNS type 65 records leak client automation

To construct the encrypted inner ClientHello, the browser needs the CDN's public encryption key and supported cipher configurations before it opens a socket.

The browser fetches this information through DNS by querying HTTPS resource records (DNS record type 65), defined in RFC 9460. Modern browsers do this over DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT).

When Chrome resolves an ECH-enabled European domain, it does not just ask for an A or AAAA record. It sends a concurrent query for the HTTPS record. The response returns an ech parameter containing a base64-encoded configuration block.

Standard scraping libraries never ask for type 65 records. When you execute a request using standard Python or Node libraries, the runtime calls the operating system's native resolver (like getaddrinfo on Linux), which only asks for IPv4 or IPv6 addresses over standard UDP port 53.

Alot of crawlers immediately give themselves away right here:

  • The scraper resolves the IP address using standard UDP 53 without ever looking up the HTTPS record.
  • The crawler initiates a TLS 1.3 handshake using a legacy cleartext SNI for a domain that has had ECH mandatory or enabled for months.
  • The declared User-Agent claims to be a modern desktop browser that has had ECH enabled by default in its release channel.
  • The incoming JA4 fingerprint lacks the specific ECH extension values (such as extension 0xfe0d or the standardized draft values) that real browsers automatically append to their extension list.

Because the edge platform controls both the DNS records and the CDN termination endpoint, correlating these signals is straightforward for them. A client that fails to perform the ECH exchange while claiming to be an up-to-date consumer browser is marked with a high bot probability score.

Resolving HTTPS resource records with dnspython

If you want your networking layer to behave like a modern browser, you have to query the HTTPS type 65 record during your resolution phase.

You can inspect and parse these records in Python using the dnspython library by querying a DoH resolver:

import base64 import dns.message import dns.query import dns.rdatatype def get_ech_configs(domain, doh_endpoint="https://cloudflare-dns.com/dns-query"): # Construct a DNS query message specifically asking for HTTPS record (type 65) query = dns.message.make_query(domain, dns.rdatatype.HTTPS) # Send the query over HTTPS to match browser transport behavior response = dns.query.https(query, doh_endpoint, timeout=5) ech_configs = [] for rrset in response.answer: if rrset.rdtype == dns.rdatatype.HTTPS: for item in rrset: # Check if the record contains the ECH parameter (param key 5) if hasattr(item, "params") and 5 in item.params: raw_ech = item.params[5].value encoded_ech = base64.b64encode(raw_ech).decode("utf-8") ech_configs.append(encoded_ech) return ech_configs domain_to_check = "crypto.cloudflare.com" configs = get_ech_configs(domain_to_check) if configs: print(f"Discovered ECH configuration for {domain_to_check}:") for cfg in configs: print(f"Base64 ECHConfig: {cfg}") else: print("No ECH parameters published in DNS.") 

When you inspect the resulting byte sequence, you will find the supported symmetric ciphers, the KEM (Key Encapsulation Mechanism) ID, and the public key used by the target's edge servers.

What happens when a scraper skips the encrypted handshake

Some developers assume that because CDNs provide fallback mechanisms for older clients, sending a plain handshake is harmless.

While it is true that public websites rarely drop plain TLS handshakes outright to avoid breaking ancient legacy clients, falling back changes your risk tier. Anti-bot heuristics do not rely on a single parameter to block a visitor. Instead, they calculate an aggregate threat score based on several attributes.

When a client hits an ECH-capable European endpoint with a cleartext handshake:

  • The server notes that the client is either an outdated browser or an automated tool that skipped the HTTPS DNS lookup.
  • If that same request originates from a residential IP that has a slightly higher latency than expected, or if the HTTP/2 frame parameters show a default Go or Python window size, the cumulative score crosses the challenge threshold.
  • The server issues a managed challenge or an invisible JavaScript fingerprint test that a headless script cannot easily pass.

Their is also the issue of key rotation. CDNs rotate their ECH keys frequently. If your client attempts to use cached or hardcoded ECH keys that have expired, the server will reject the inner handshake and send back an unencrypted retry_configs extension in the ServerHello, forcing the client to re-handshake. If your automated tool fails to handle this re-handshake sequence properly, the connection simply terminates.

Practical steps to align your DNS and TLS stack

Getting past these checks requires synchronizing your DNS lookups with your TLS engine rather than treating them as isolated steps.

First, stop relying exclusively on local system DNS resolvers that only perform simple A record lookups over port 53. If you run workers inside Docker containers or on headless European VPS hosts, your scripts should query an upstream resolver that supports modern resource records. Using a local Unbound cache or querying European Quad9 endpoints via DoH ensures your resolver pipeline can actually retrieve type 65 answers.

Second, keep an eye on how your low-level HTTP clients handle TLS extensions. Standard Python urllib3 and requests use system OpenSSL, which historically lacked support for dynamic ECH negotiation. To properly emit the correct extensions, you need clients linked against modern BoringSSL builds or custom Go implementations that explicitly expose ECH configuration parameters.

Finally, test your client against specialized diagnostic endpoints before launching a scrape. Services like Cloudflare's connection diagnosis pages report whether your client successfully negotiated ECH or fell back to cleartext SNI.

Aligning your network layer so it resolves HTTPS records and constructs valid outer and inner greetings takes additional setup, but it removes one of the cleanest signals security platforms use to seperate automated scripts from legitimate browser traffic.

Source: r/EuroProxy · by /u/reddgsmtrx3x

Leave a Reply

Your email address will not be published. Required fields are marked *