// Engineering Log

HTTP, HTTPS, and TLS: Part 6 — What is ECH (Encrypted Client Hello) and How It Hides the Site Name

Published on 2026-10-07

// Fast route

This article belongs to the topic Servers and infrastructure.

ECH (Encrypted Client Hello) — a TLS 1.3 extension that encrypts the first handshake message, ClientHello. The main thing it hides is the site name in the SNI field. Without ECH any node between you and the server can see that you opened shop.example.ru, even if everything else is encrypted. With ECH an observer sees only a common name, the same for many sites behind one server or CDN. The standard was published in March 2026 as RFC 9849, and the way to publish keys in DNS — as RFC 9848.

What remains exposed without ECH

The TLS 1.3 handshake encrypts almost everything, including the server certificate. But the ClientHello is sent before the parties agree on keys, so it is plaintext and contains:

  • SNI — the site name;
  • ALPN — the list of protocols (h2, http/1.1), which reveals the application type;
  • a set of ciphers, groups and extensions used to build client fingerprints (JA3, JA4).

SNI is what DPI equipment, corporate network filters and ISP parental controls use to block sites. Previously there was an attempt to encrypt only SNI (ESNI, 2018), but it left other fields exposed and never became a standard. ECH is its replacement: the entire ClientHello is encrypted.

How it works

Two ClientHellos in one. The client constructs:

  • an inner ClientHello — the real one, with the desired site name and full ALPN list;
  • an outer ClientHello — public, with a common name (public_name), for example cloudflare-ech.com. The inner one is embedded in it encrypted in the encrypted_client_hello extension.

Everyone sees the outer ClientHello; only the server that holds the ECH private key can read the inner one.

Where the client gets the key. The server publishes an ECH configuration (ECHConfigList: public key, algorithms and public_name) in an HTTPS DNS record for its domain:

bash
dig +short HTTPS example.ru
1 . alpn="h3,h2" ipv4hint=192.0.2.10 ech=AEX+DQBBpQAgACB...

The browser queries this record along with A/AAAA and, if it contains the ech parameter, encrypts the ClientHello. Encryption uses HPKE (RFC 9180): the client derives an ephemeral key from the server’s public key.

What the server does. It decrypts the inner ClientHello and continues the handshake with it as if the outer one never existed. In the response the server quietly confirms to the client that ECH was accepted.

If the key is stale. ECH keys are rotated periodically. If the client encrypted the ClientHello with an old key, the server cannot decrypt it and will complete the handshake using the outer ClientHello — with a certificate for the public_name — and will provide the client with the current configuration (retry configs). The client reconnects with the new key. Therefore the server needs a valid certificate for the common name as well.

Two deployment modes:

  • shared mode — the same server both decrypts ECH and serves sites; Cloudflare works this way and Nginx supports ECH in this mode;
  • split mode — a separate front-end only decrypts the ClientHello and passes the connection to the site server without seeing its contents. Suitable when the front and the sites belong to different owners.

GREASE ECH. ECH-capable browsers send the encrypted_client_hello extension with random data even to sites that don’t have ECH. This prevents distinguishing a real ECH-capable connection by the presence of the extension and prevents middleboxes from failing on an unknown extension.

What ECH hides and what it doesn’t

ECH hides the site name from a passive observer, but only under several conditions:

  • DNS is also encrypted. If the example.ru HTTPS query goes via plain DNS, the name is visible there. ECH makes sense together with DNS-over-HTTPS or DNS-over-TLS. Firefox uses ECH only when DoH is enabled.
  • Many sites are behind the address. If there is only one site at the IP address, the name is revealed by the address itself. Therefore ECH truly works at CDNs and large hosters where thousands of domains share one address.
  • The observable connection features. Response length, timing, presence of the extension — all remain available for traffic analysis.

ECH does not hide the server’s IP address and is not a means to bypass blocks: the fact of using ECH or the provider’s address can be blocked.

Where ECH works today

  • Browsers: Chrome since version 117 and Firefox since version 119 (2023) use ECH by default if they find a configuration in DNS.
  • Cloudflare enabled ECH for sites on free plans in 2023, disabled it due to issues and began rolling it out again in September 2024. Its public_name is cloudflare-ech.com.
  • OpenSSL supports ECH since version 4.0 (April 2026). Previously ECH existed only in a separate OpenSSL branch, in BoringSSL (Chrome) and NSS (Firefox).
  • Nginx gained ECH in version 1.29.4 (the ssl_ech_file directive, shared mode), when built with an OpenSSL that supports ECH.
  • curl — the --ech option since 8.8.0 as an experimental feature, when built with a TLS library that supports ECH.

ECH in Russia

On November 5–6, 2024 TSPU began blocking connections to Cloudflare that contained the ECH extension — along with the public_name cloudflare-ech.com. Roskomnadzor explained that using ECH violates Russian legislation and recommended site owners not to use the Cloudflare CDN. In practice, users with ECH enabled stopped being able to open many Cloudflare-hosted sites unrelated to politics.

For a site owner with an audience in Russia this implies:

  • if the site is behind Cloudflare and some visitors report inaccessibility, check whether ECH is enabled in SSL/TLS → Edge Certificates (on the free plan the toggle is not always available — then use the API or support);
  • enabling ECH on your own server for a Russian audience makes no sense: there is no benefit in hiding the name, and there is a risk of the connection being blocked.

How to enable ECH in Nginx

You need Nginx 1.29.4 or newer and OpenSSL 4.0 or newer. Prebuilt packages are still rare, so Nginx is usually built from sources with the option --with-openssl=<path-to-OpenSSL-4-source>.

  1. Generate the ECH key. The file contains the private key and the configuration to publish in DNS:
bash
openssl ech -public_name ech.example.ru -out /etc/nginx/ech/ech.example.ru.pem.ech

public_name is the common name an observer will see. It needs a regular certificate: it is used when the client connects with an old key.

  1. Attach the key. It’s convenient to set the directive at the http level so it applies to all sites:
nginx
http {
    ssl_ech_file /etc/nginx/ech/ech.example.ru.pem.ech;

    log_format ech '$remote_addr "$request" $status '
                   'ech=$ssl_ech_status outer=$ssl_ech_outer_server_name sni=$ssl_server_name';

    server {
        listen 443 ssl;
        server_name ech.example.ru;
        ssl_certificate     /etc/letsencrypt/live/ech.example.ru/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ech.example.ru/privkey.pem;
        return 404;
    }

    server {
        listen 443 ssl;
        server_name shop.example.ru;
        ssl_certificate     /etc/letsencrypt/live/shop.example.ru/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/shop.example.ru/privkey.pem;
        access_log /var/log/nginx/shop.access.log ech;
        root /var/www/shop;
    }
}

The variable $ssl_ech_status takes values SUCCESS (ECH accepted), NOT_TRIED (the client didn’t try), GREASE (the client sent a GREASE dummy), FAILED and BACKEND.

  1. Publish the configuration in DNS. Add the Base64 string from the .pem.ech file (the ECHCONFIG block) to the HTTPS record of each site:
shop.example.ru.  300  IN  HTTPS  1 . alpn="h2,http/1.1" ech="AEX+DQBBpQAgACB..."

Not all DNS hosts allow creating HTTPS records; if yours doesn’t, ECH will not work.

  1. Rotate keys. ECH keys should be refreshed every few weeks: generate a new one, add it to Nginx alongside the old one (the directive can be repeated), publish it in DNS, wait for the TTL to expire, then remove the old key.

How to check

In the browser. The page https://crypto.cloudflare.com/cdn-cgi/trace shows sni=encrypted if ECH worked, and sni=plaintext if not. On your own site the result is visible in logs via the $ssl_ech_status variable.

Via curl (requires a build with ECH):

bash
curl --ech true https://shop.example.ru/     # try ECH, if it fails — fall back
curl --ech hard https://shop.example.ru/     # only ECH, otherwise error
curl --ech ecl:AEX+DQBBpQAgACB... https://shop.example.ru/   # config manually, without DNS
curl --ech false https://shop.example.ru/    # do not use ECH

curl fetches the configuration from the HTTPS record via DoH, so --doh-url https://dns.example.net/dns-query is usually specified together with --ech.

Via openssl (4.0 and newer):

bash
openssl s_client -connect shop.example.ru:443 -servername shop.example.ru \
  -ech_config_list AEX+DQBBpQAgACB...

From the network. In Wireshark a connection with ECH will show the SNI as the public_name, and the ClientHello will contain the encrypted_client_hello extension (type 0xfe0d) with a large data block.

Common errors

  • ECH is enabled, but the client’s DNS is plain — the site name is still visible in DNS queries.
  • No certificate for the public_name — clients with an old key cannot obtain the new one and drop out.
  • The key was changed on the server but not updated in DNS, or vice versa — clients get errors until TTL expires.
  • ECH on a separate server with a single site: the name is not visible in SNI, but is easily determined by IP address.
  • ECH enabled for a site with a Russian audience — some visitors lose access due to blocking.

// Similar task

If you are dealing with something similar

This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.

Article topic

Servers and infrastructure

VPS, Linux, web stack, migrations, hosting, databases, and core operations.

Typical tasks behind this topic

  • Move a site or service to a new server
  • Set up Linux, Nginx, databases, and backups
  • Figure out why the system behaves unstably

// Next step

If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.

Open services

// Reviews

Related reviews

apande

apande

Configuring Nginx and OpenCart

2024-09-07 · ★ 5/5

A very powerful buyer
kireevk

kireevk

Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue

2024-03-15 · ★ 5/5

Settled-in customer

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply