macOS SecureNetwork Suite › Chrome ECH on macOS

Chrome, Brave or Edge ECH not working on macOS: how to fix it

You turned on “Use secure DNS” in Chrome, Brave or Edge, but tls-ech.dev still says you are not using Encrypted Client Hello. On a Mac with a DNS configuration profile installed, the browser has quietly switched its own DNS-over-HTTPS off, so it never receives the key ECH needs. This page shows how to switch it back on with an enterprise policy, how to set up Firefox, what Safari can and cannot do, and how to check the result.

What ECH is, and why it depends on DNS

HTTPS encrypts what you send, but not which site you are talking to. The first packet of a TLS handshake, the ClientHello, carries the hostname in the clear in a field called SNI. Most network-level filtering and logging works from that one field. Encrypting your DNS does not close the gap: the name leaks again a few milliseconds later, on every connection.

Encrypted Client Hello (ECH) encrypts the ClientHello to a public key that the site publishes in its DNS HTTPS record (type 65, also called SVCB/HTTPS). Someone watching the network then sees only the CDN in front of the site, not the hostname.

So ECH needs two things:

Why Chrome, Brave and Edge skip ECH on macOS

Many Macs that care about DNS privacy have a system DNS configuration profile installed: AdGuard DNS, NextDNS, a company's profile, or a DNS-over-HTTPS profile you made yourself (payload type com.apple.dnsSettings.managed). That profile does its own job: every app on the Mac resolves through encrypted DNS.

Chromium, however, takes that profile as a sign that DNS is enterprise-managed, and it silently switches off its own DNS-over-HTTPS. The “Use secure DNS” setting can still look turned on, yet the browser's internal state reports secure_dns_mode: 0. With its own DoH off, Chromium never fetches the HTTPS records that carry ECH keys, so ECH never starts. The same thing happens in every Chromium-based browser on the Mac: Chrome, Brave, Edge, Vivaldi and Opera.

Google's own documentation for the DnsOverHttpsMode policy says as much: “If this policy is unset, for managed devices DNS-over-HTTPS queries will not be sent.” The way out is to set that policy yourself. A policy you set explicitly overrides the managed-DNS detection.

Fix: force DNS-over-HTTPS in Chromium browsers by policy

You need two enterprise policies, set for each browser you use:

Policy keyValueWhat it does
DnsOverHttpsModesecureDNS-over-HTTPS only, with no fallback to plain DNS. Of the policy's three values (off, automatic, secure), this is the one used here.
DnsOverHttpsTemplatesyour DoH URL, for example https://dns.adguard-dns.com/dns-queryThe resolver to use. When the mode is secure, this policy must be set and not empty.

On macOS, browser policies are delivered as a configuration profile. Each browser reads them from its own preference domain, which becomes the PayloadType of a payload in the profile:

BrowserPayloadType (preference domain)
Google Chromecom.google.Chrome
Bravecom.brave.Browser
Microsoft Edgecom.microsoft.Edge
Vivaldicom.vivaldi.Vivaldi
Operacom.operasoftware.Opera

1. Save the profile

Save the following as chrome-doh-policy.mobileconfig. It has one payload per browser, and it points all five browsers at AdGuard's public DoH resolver. To use a different resolver, change each DnsOverHttpsTemplates string, for example to your AdGuard DNS account URL https://d.adguard-dns.com/dns-query/<device-id>.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadType</key>
            <string>com.google.Chrome</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>com.local.chrome.doh</string>
            <key>PayloadUUID</key>
            <string>A1B2C3D4-E5F6-7890-ABCD-EF1234567890</string>
            <key>PayloadEnabled</key>
            <true/>
            <key>DnsOverHttpsMode</key>
            <string>secure</string>
            <key>DnsOverHttpsTemplates</key>
            <string>https://dns.adguard-dns.com/dns-query</string>
        </dict>
        <dict>
            <key>PayloadType</key>
            <string>com.brave.Browser</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>com.local.brave.doh</string>
            <key>PayloadUUID</key>
            <string>A1B2C3D4-E5F6-7890-ABCD-EF1234567891</string>
            <key>PayloadEnabled</key>
            <true/>
            <key>DnsOverHttpsMode</key>
            <string>secure</string>
            <key>DnsOverHttpsTemplates</key>
            <string>https://dns.adguard-dns.com/dns-query</string>
        </dict>
        <dict>
            <key>PayloadType</key>
            <string>com.microsoft.Edge</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>com.local.edge.doh</string>
            <key>PayloadUUID</key>
            <string>A1B2C3D4-E5F6-7890-ABCD-EF1234567892</string>
            <key>PayloadEnabled</key>
            <true/>
            <key>DnsOverHttpsMode</key>
            <string>secure</string>
            <key>DnsOverHttpsTemplates</key>
            <string>https://dns.adguard-dns.com/dns-query</string>
        </dict>
        <dict>
            <key>PayloadType</key>
            <string>com.vivaldi.Vivaldi</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>com.local.vivaldi.doh</string>
            <key>PayloadUUID</key>
            <string>A1B2C3D4-E5F6-7890-ABCD-EF1234567893</string>
            <key>PayloadEnabled</key>
            <true/>
            <key>DnsOverHttpsMode</key>
            <string>secure</string>
            <key>DnsOverHttpsTemplates</key>
            <string>https://dns.adguard-dns.com/dns-query</string>
        </dict>
        <dict>
            <key>PayloadType</key>
            <string>com.operasoftware.Opera</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>com.local.opera.doh</string>
            <key>PayloadUUID</key>
            <string>A1B2C3D4-E5F6-7890-ABCD-EF1234567894</string>
            <key>PayloadEnabled</key>
            <true/>
            <key>DnsOverHttpsMode</key>
            <string>secure</string>
            <key>DnsOverHttpsTemplates</key>
            <string>https://dns.adguard-dns.com/dns-query</string>
        </dict>
    </array>
    <key>PayloadDescription</key>
    <string>Forces DNS-over-HTTPS (secure mode) with AdGuard DNS for all Chromium browsers to enable ECH</string>
    <key>PayloadDisplayName</key>
    <string>Chromium Browser DoH Policy</string>
    <key>PayloadIdentifier</key>
    <string>com.local.chromium.doh.policy</string>
    <key>PayloadOrganization</key>
    <string>Local</string>
    <key>PayloadRemovalDisallowed</key>
    <false/>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadUUID</key>
    <string>B2C3D4E5-F6A7-8901-BCDE-F12345678901</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
</dict>
</plist>

2. Install and approve it

Open the file. macOS will not install a configuration profile silently, so this only queues it:

open chrome-doh-policy.mobileconfig

Then go to System Settings › General › Device Management, select Chromium Browser DoH Policy and approve it.

3. Check that the browser sees the policy

Quit and reopen the browser, then open chrome://policy (Edge and Brave also answer at edge://policy and brave://policy). You should see DnsOverHttpsMode with the value secure and DnsOverHttpsTemplates with your resolver URL. If they are missing, the profile was not approved, or the PayloadType does not match that browser.

Know what secure costs. Chrome's policy description says that secure mode “will only send DNS-over-HTTPS queries and will fail to resolve on error.” If the DoH server cannot be reached, these browsers stop resolving names; they do not fall back to the network's DNS. A captive portal (hotel, airport or café Wi-Fi) works by hijacking plain DNS, so it cannot redirect an encrypted query to its login page. So while the policy is in place, expect a portal's login page not to appear in these browsers.

Firefox: switch ECH and DoH on together

Firefox needs ECH and its own DNS-over-HTTPS (TRR) switched on together. Set these in about:config, or put them in a user.js file in your profile folder (~/Library/Application Support/Firefox/Profiles/<profile>/) and restart Firefox:

// ECH
user_pref("network.dns.echconfig.enabled", true);
user_pref("network.dns.https.enabled", true);           // fetch HTTPS DNS records (needed for ECH keys)
user_pref("network.dns.http3_echconfig.enabled", true); // ECH over HTTP/3

// DNS over HTTPS (TRR)
user_pref("network.trr.mode", 2);                       // 2 = prefer DoH, fall back to system DNS
user_pref("network.trr.uri", "https://dns.adguard-dns.com/dns-query");
user_pref("network.trr.bootstrapAddress", "94.140.14.14");

network.trr.bootstrapAddress is the resolver's IP address, which Firefox uses to reach the DoH server itself. If you use a different resolver, change it together with network.trr.uri. For an AdGuard DNS account resolver that pair is https://d.adguard-dns.com/dns-query/<device-id> and 94.140.14.49.

Safari: no ECH, and no setting adds it

Safari cannot do ECH. WebKit has no implementation as of Safari 26, and nothing you configure changes that. A system DNS-over-HTTPS profile still encrypts Safari's DNS lookups and lets it fetch HTTPS records, but the hostname still goes out in the clear in the TLS handshake. For anything you want hidden from the network path, use Firefox or a Chromium browser with the policy above.

How to check that ECH is working

In the browser

From Terminal: does your resolver pass the ECH key?

If the browser checks fail, find out whether the HTTPS record reaches you at all. crypto.cloudflare.com publishes an ECH key; cloudflare.com itself does not, so it is the wrong name to test.

dig +short TYPE65 crypto.cloudflare.com

Output containing ech= means your resolver passes the key. Use TYPE65 rather than the name HTTPS: older versions of dig (9.10.x) do not recognise the type name and silently send an A query instead, which looks like a failure. Those older versions print the record as raw hex, which still means it arrived. Empty output means the resolver your Mac uses may be stripping HTTPS/SVCB records.

Stock curl cannot test ECH: macOS's own curl is built on LibreSSL, which has no ECH, and OpenSSL gained it only in 4.0.0 (April 2026). A curl built against an ECH-capable OpenSSL can, but it only gets ECH keys over DoH, because the macOS resolver does not return HTTPS records to it. Give it --doh-url, and run it with that build rather than the system curl:

curl -v --doh-url https://dns.adguard-dns.com/dns-query --ech hard https://tls-ech.dev/

Automated: the profiles component of macOS-SecureNetwork-Suite builds the Chromium policy profile above for all five browsers, with your own DoH target, plus a system-wide AdGuard DNS-over-HTTPS profile (skipped if a device profile already provides encrypted DNS), and queues both for approval (./install.sh --only profiles). Its firefox component writes the Firefox preferences to every profile. checkdns then tests ECH at both layers: whether your resolver returns the HTTPS record for crypto.cloudflare.com, and, if it finds an ECH-capable curl, a real --ech hard handshake against tls-ech.dev with keys fetched over your DoH resolver. With only stock curl it says so rather than reporting a false failure.

FAQ

Does Safari support ECH?

No. WebKit has no implementation of Encrypted Client Hello as of Safari 26, and no setting changes that. Use Firefox or a Chromium browser for anything you want hidden from the network path.

Why doesn't Chrome use ECH even though “Use secure DNS” is on?

Because a system DNS configuration profile makes Chromium think DNS is enterprise-managed, and it silently disables its own DNS-over-HTTPS. Without it, Chrome never fetches the HTTPS record that carries the ECH key. Setting the DnsOverHttpsMode policy to secure, with a DnsOverHttpsTemplates URL, overrides that detection. Check the result at chrome://policy.

Does the same fix work for Brave, Edge, Vivaldi and Opera?

Yes. They read the same two policy keys, DnsOverHttpsMode and DnsOverHttpsTemplates, from their own preference domains: com.brave.Browser, com.microsoft.Edge, com.vivaldi.Vivaldi and com.operasoftware.Opera. Chrome uses com.google.Chrome. One profile can carry a payload for each.

The ECH test on defo.ie fails but tls-ech.dev passes. Which one is right?

Trust tls-ech.dev and Cloudflare's trace page (sni=encrypted) first. defo.ie is a test server whose published keys sometimes lag its server's; in October 2026 its DNS listed keys the server had retired. A failure there alone is not conclusive.

macOS says “The ‘VPN Service’ payload could not be installed” for my DNS profile. Why?

A DNS-settings payload is only valid in a device profile. If the profile has no PayloadScope set to System, Device Management installs it for the user and NetworkExtension refuses it; the log says the profile must be scoped to system. Add PayloadScope System, remove the failed copy from the Downloaded list in Device Management, and install it again. Approving a device profile asks for an administrator password.

After this fix, the AdGuard DNS dashboard says my Mac is not connected. Is DNS broken?

No. The dashboard runs its check inside the browser, and the browser now uses the DoH URL from the policy. If that is AdGuard's public resolver, your device ID never appears, even though every other app resolves through your account profile. Set DnsOverHttpsTemplates (and network.trr.uri in Firefox) to your account resolver, https://d.adguard-dns.com/dns-query/ followed by your device ID, then approve the updated profile and restart Firefox.