Panoptes › ECH on Linux

Enable Encrypted Client Hello (ECH) on Linux

You encrypted your DNS, yet every HTTPS connection still announces the site's name in the clear. Your distribution's curl rejects --ech, and you are not sure whether your browser is using ECH at all. This page covers what ECH hides, why it does nothing without secure DNS, how to build a curl that speaks it, how to set it up in Firefox and Chromium-based browsers, and how to prove it works.

What ECH hides, and what it does not

HTTPS encrypts what you send and receive, but not which site you are talking to. The first message of a TLS handshake, the ClientHello, carries the hostname in the SNI field before any encryption exists. Your ISP, the café router or a corporate middlebox can read it, and much real-world filtering and logging works exactly that way.

With ECH, the site publishes a public key in a DNS HTTPS record. The client encrypts the real ClientHello, hostname included, to that key and wraps it in an outer ClientHello that names only a cover name, such as the provider's. An observer sees a connection to the cover name, not the site behind it.

What it does not do:

The DNS half is not optional

The ECH key arrives in a DNS lookup. If that lookup travels in plaintext, the hostname leaks in the query instead, and anyone on the path can strip the key from the answer so the client falls back to a normal, readable handshake. ECH is only worth having with DNS-over-HTTPS or DNS-over-TLS protecting the lookup.

Check whether a site publishes an ECH key with an ech= value in its HTTPS record:

dig +short HTTPS defo.ie
resolvectl query -t HTTPS defo.ie

If your dig does not know the HTTPS type, ask for it by number: dig +short defo.ie TYPE65.

Does your curl support ECH?

curl -V | grep -w ECH

An ECH-capable build lists ECH on its Features: line. Without it, curl --ech true stops with option --ech: the installed libcurl version does not support this. Distribution builds usually lack it: the curl project marks ECH as experimental, and it has to be switched on when curl is built (--enable-ech) against a TLS library that implements it. According to curl's build notes, those are OpenSSL 4.0.0 or later, BoringSSL, AWS-LC, wolfSSL and Rustls.

Build an ECH-capable curl

These steps follow curl's own docs/ECH.md. Everything stays under $HOME/code, so your system curl and OpenSSL are untouched. You need git, a C compiler, make, perl, autoconf, automake, libtool and pkg-config. If curl's configure stops on a missing library, install that library's development package.

mkdir -p $HOME/code && cd $HOME/code
git clone --depth 1 --branch openssl-4.0.0 https://github.com/openssl/openssl
cd openssl
./config --libdir=lib --prefix=$HOME/code/openssl-local-inst
make -j"$(nproc)"
make install_sw
cd $HOME/code
git clone --depth 1 https://github.com/curl/curl
cd curl
autoreconf -fi
LDFLAGS="-Wl,-rpath,$HOME/code/openssl-local-inst/lib/" ./configure --with-ssl=$HOME/code/openssl-local-inst --enable-ech
make -j"$(nproc)"

configure should end with WARNING: ECH is enabled but marked EXPERIMENTAL. If it does not, ECH is not enabled. The rpath in LDFLAGS lets the new binary find its OpenSSL without touching the system library path. Do not add that directory to /etc/ld.so.conf.d, which would hand an experimental TLS library to every program on the machine.

Test ECH with curl

cd $HOME/code/curl
./src/curl -V | grep -w ECH
./src/curl -sS --ech hard --doh-url https://one.one.one.one/dns-query https://defo.ie/ech-check.php | grep -oiE 'SSL_ECH_STATUS: [a-z_]+'
./src/curl -sS --ech hard --doh-url https://one.one.one.one/dns-query https://tls-ech.dev/ | grep -o 'You are[^<]*'

Firefox, LibreWolf and other Firefox-based browsers

In current Firefox source, the ECH preferences network.dns.echconfig.enabled and network.dns.http3_echconfig.enabled default to true. Check them in about:config, and set them back to true if something changed them. The part that usually needs doing is secure DNS, which Firefox calls TRR:

PreferenceValueMeaning
network.trr.mode2DoH first, the system resolver as fallback
network.trr.mode3DoH only, never the system resolver
network.trr.urihttps://cloudflare-dns.com/dns-queryThe DoH server to use (any https:// DoH endpoint)

The DNS over HTTPS section of Firefox's Privacy & Security settings sets the same thing. To make the settings survive a preferences reset, put them in user.js in your profile folder (about:profiles shows its path). Firefox reads that file at every start:

user_pref("network.dns.echconfig.enabled", true);
user_pref("network.dns.http3_echconfig.enabled", true);
user_pref("network.trr.mode", 2);
user_pref("network.trr.uri", "https://cloudflare-dns.com/dns-query");

Mode 2 falls back to the system resolver if DoH fails, so a hostile network can still push you to plaintext lookups. Mode 3 does not fall back, but it breaks captive portals and names that only your local DNS knows.

Chrome, Brave, Chromium and Vivaldi

Chrome has shipped ECH enabled by default since Chrome 117, so there is nothing to switch on for ECH itself. It still needs the site's HTTPS record, so turn on Use secure DNS under Settings → Privacy and security → Security and pick a provider. That keeps the lookup encrypted and the key intact. Organisations can turn ECH off with the EncryptedClientHelloEnabled policy; if ECH never works, check chrome://policy. Brave, Vivaldi, Edge and other Chromium-based browsers build on the same network stack, but confirm with a test page rather than assuming.

Leave Tor Browser and Mullvad Browser alone. They resolve names through their proxy circuit on purpose, and pointing them at a DoH resolver would send your lookups around it.

Test ECH in the browser

Restart the browser after changing settings. A Chromium-based browser that is still running when you edit its files can overwrite your change when it exits.

Automated: two scripts from Panoptes do this for you. ech-build.sh builds Stephen Farrell's OpenSSL ECH branch (ECH-draft-13c by default) as a static library in /opt/openssl-ech, so no other program can link it, then curl against it into /usr/local. Package updates leave both alone, and /usr/bin/curl stays installed. Note that /usr/local/bin/curl then comes first in PATH, including under sudo, and the build is deliberately minimal: no HTTP/3, and without --full no brotli, zstd, GSSAPI, SCP, SFTP, SMB or LDAP. --verify runs a live --ech hard handshake, and --uninstall hands curl back to your distribution. ech-browsers.sh finds every Firefox-family and Chromium-family browser, including Flatpak and Snap installs, and writes the preferences above to user.js, or sets Chromium's secure DNS in Local State. It backs up each file, supports --list, --dry-run and --revert, skips Chromium browsers that are still running, and never touches Tor Browser or Mullvad Browser. Its Firefox default is TRR mode 2 with Cloudflare's DoH endpoint. checkdns checks both halves: the ech= key in DNS and a real handshake with defo.ie.

FAQ

Is ECH on by default in Firefox and Chrome?

Chrome has shipped ECH enabled since Chrome 117. Current Firefox sets network.dns.echconfig.enabled and network.dns.http3_echconfig.enabled to true by default. In both, ECH only happens when the browser gets the site's HTTPS DNS record, so turn on secure DNS and confirm at defo.ie/ech-check.php.

Why does curl say the installed libcurl version does not support --ech?

Your curl was built without ECH. Run curl -V and look for ECH on the Features line. Distribution builds usually lack it, because ECH is experimental in curl and has to be enabled at build time against a TLS library that implements it, so build your own.

Why does curl --ech hard fail with error 101?

Exit code 101 is CURLE_ECH_REQUIRED: ECH could not be done. Check that you passed --doh-url, because curl only fetches the HTTPS record over DoH, and that the site's HTTPS record has an ech= value. If one test site fails and another works, the problem is on that server.

Does ECH hide which site I visit from my ISP?

It hides the hostname in the TLS handshake, not the destination IP address. On a large CDN that address is shared by many sites and says little; a site on its own IP is still identifiable. Packet sizes and timing also remain visible.

Should I enable DoH and ECH in Tor Browser or Mullvad Browser?

No. Both resolve names through their proxy circuit on purpose. Pointing them at a DoH resolver would send your lookups around the circuit, which weakens the anonymity they are built for.

Sources