How CFnew Implements Encrypted Client Hello (ECH) for TLS Privacy
CFnew implements Encrypted Client Hello by fetching ECH configurations via DNS-over-HTTPS, enforcing TLS-only connections, and injecting Chrome fingerprint parameters into proxy URLs, effectively encrypting the Server Name Indication during TLS handshakes to prevent domain interception.
Encrypted Client Hello (ECH) is a TLS extension designed to conceal the Server Name Indication (SNI) from network observers during the initial handshake. The open-source CFnew project, hosted at byJoey/cfnew, provides a production-ready implementation for Cloudflare Workers that automatically configures ECH support through KV storage and dynamic request manipulation. This article breaks down the specific source code mechanisms that enable ECH protection, from DNS configuration fetching to client fingerprint masking.
What Is Encrypted Client Hello?
Encrypted Client Hello (ECH) is a privacy-preserving extension to the TLS 1.3 protocol that encrypts the Client Hello message, which traditionally exposes the target domain name (SNI) in plaintext. By encrypting this handshake data, ECH prevents middleboxes, ISPs, and passive observers from identifying the destination domain, mitigating both surveillance and domain-fronting attacks. CFnew leverages this standard to ensure that proxy connections reveal only the Cloudflare edge IP, not the actual backend service domain.
How CFnew Implements ECH
CFnew integrates ECH support through a five-stage pipeline that activates when the ech KV configuration key is set to "yes" or "true".
Activating the ECH Flag
The request handler entry point checks KV storage for the ech key and sets the global enableECH variable. This boolean flag controls all subsequent ECH logic in the request lifecycle. According to the source code, this initialization occurs in the main worker export: 明文源吗#L6665-L6670.
Enforcing TLS-Only Mode
Since ECH requires encrypted TLS channels to function, CFnew automatically disables non-TLS connections when ECH is enabled. The code explicitly sets disableNonTLS = true and persists this constraint to KV storage, ensuring that plaintext HTTP nodes are excluded from the proxy pool: 明文源吗#L586-L593.
Fetching ECH Configuration via DNS-over-HTTPS
The fetchECHConfig(domain) function retrieves the ECH configuration by querying DNS HTTPS records (Type 65). It first attempts to resolve cloudflare-ech.com using Google DNS-over-HTTPS, falling back to a user-defined custom domain if necessary. The function decodes base64 payloads, extracts the ech= value, and stores a debug trace in echDebugInfo: 明文源吗#L2440-L2596.
Injecting Diagnostic Headers
For transparency and debugging, CFnew adds response headers indicating ECH status. When active, the worker returns X-ECH-Status: ENABLED and includes X-ECH-Config-Length showing the byte size of the retrieved ECH payload: 明文源吗#L2770-L2773.
Masking Client Fingerprints
To ensure compatibility with ECH handshakes, CFnew modifies outbound proxy link generation. When enableECH is true, the system appends fp=chrome to the connection parameters, forcing a Chrome-compatible TLS fingerprint that supports ECH negotiation: 明文源吗#L7892-L7894.
Configuration and KV Storage
CFnew exposes ECH controls through KV keys and UI toggles. The implementation reads configuration values early in the request lifecycle to determine whether to activate the ECH pipeline: 明文源吗#L7778-L7790.
Available configuration options:
ech: Set to"yes"or"true"to enable ECH processingcustomECHDomain: Optional custom domain for fetching ECH configs (defaults tocloudflare-ech.com)
These values are typically set via the UI toggle "启用 ECH (Encrypted Client Hello)" and the corresponding custom domain field.
Code Examples
Enable ECH programmatically via KV storage:
// Activate ECH support
await setConfigValue('ech', 'yes');
// Optional: Use custom ECH domain instead of cloudflare-ech.com
await setConfigValue('customECHDomain', 'ech.example.com');
Verify ECH status from client responses:
fetch('https://<worker-domain>/test-api')
.then(response => {
console.log('ECH Status:', response.headers.get('X-ECH-Status')); // "ENABLED"
console.log('Config Length:', response.headers.get('X-ECH-Config-Length'));
});
Inspect the ECH configuration fetch results internally:
// Inside the worker context
const echConfig = await fetchECHConfig(customECHDomain);
console.log('Raw ECH config:', echConfig);
console.log('Resolution trace:', echDebugInfo);
Generate ECH-compatible proxy links:
// Link construction includes Chrome fingerprint when ECH is active
const link = `${proto}://${user}@${safeIP}:${port}`
+ `?encryption=none&security=tls&sni=${workerDomain}`
+ `&fp=${enableECH ? 'chrome' : 'randomized'}`
+ `&type=ws&host=${workerDomain}&path=${wsPath}`;
Summary
- ECH Activation: Controlled by the
echKV key, read at the worker entry point to setenableECH - TLS Enforcement: Automatically disables non-TLS nodes when ECH is active via
disableNonTLS - DoH Resolution:
fetchECHConfig()retrieves Type 65 HTTPS records using Google DNS, with fallback support for custom domains - Header Transparency: Injects
X-ECH-StatusandX-ECH-Config-Lengthheaders for debugging - Fingerprint Spoofing: Forces
fp=chromeon proxy links to ensure ECH handshake compatibility
Frequently Asked Questions
What happens if the ECH configuration fetch fails?
If fetchECHConfig() cannot resolve the ECH record from either the default cloudflare-ech.com or the custom domain, the function returns null or an empty string. The proxy continues operating but without ECH encryption, and the debug trace in echDebugInfo logs the specific DNS resolution failure.
Why does CFnew require a Chrome fingerprint when ECH is enabled?
ECH handshakes require specific TLS extensions and cipher suites that match modern browser implementations. By appending fp=chrome to proxy URLs, CFnew ensures the downstream connection presents a Client Hello packet that Cloudflare recognizes as ECH-capable, preventing handshake failures that would occur with randomized fingerprints.
How do I verify that ECH is actually working in my deployment?
Send a test request to your CFnew worker endpoint and inspect the response headers. You should see X-ECH-Status: ENABLED confirming the feature is active, and X-ECH-Config-Length indicating successful retrieval of the ECH configuration from DNS. Additionally, packet captures should show encrypted SNI data in the TLS 1.3 Client Hello message.
Can I use ECH with non-TLS proxy nodes?
No. The implementation explicitly forces TLS-only mode when ECH is enabled by setting disableNonTLS = true. This constraint is necessary because ECH extensions only function within encrypted TLS 1.3 handshakes; plaintext HTTP connections expose the target domain regardless of ECH configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →