# How to Configure TLS with ECH (Encrypted Client Hello) in Xray-core

> Learn how to configure TLS with ECH Encrypted Client Hello in Xray-core using echServerKeys echConfigList and echForceQuery. Generate keys easily with the xray tls ech command.

- Repository: [Project X Community, Not Porn-jet X Hub/Xray-core](https://github.com/XTLS/Xray-core)
- Tags: how-to-guide
- Published: 2026-04-21

---

**Xray-core supports native TLS with Encrypted Client Hello (ECH) through the `echServerKeys`, `echConfigList`, and `echForceQuery` configuration fields, with key generation handled by the built-in `xray tls ech` command.**

This guide walks you through configuring **TLS with ECH in Xray-core**, the privacy-enhancing extension that encrypts the Server Name Indication (SNI) and other ClientHello fields. The implementation spans multiple source files including [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) for the core handshake logic and [`main/commands/all/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go) for key generation.

---

## Understanding ECH Configuration Fields

Xray-core's ECH support is defined in [`transport/internet/tls/config.pb.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/config.pb.go) and exposed through three primary configuration fields. These fields control how servers publish encrypted keys and how clients retrieve and use them.

### Field Reference Table

| Field | Direction | Purpose |
|-------|-----------|---------|
| `echServerKeys` | Server | Base64-encoded ECH key-set list for accepting encrypted ClientHello |
| `echConfigList` | Client | Base64-encoded ECH config list or DNS-HTTPS URL for lookup |
| `echForceQuery` | Client | Failure mode: `none`, `half`, or `full` |

In [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go), the `ApplyECH` function bridges these configuration values into Go's standard `crypto/tls.Config` before the handshake begins.

---

## Generating ECH Keys with the CLI

Before configuring Xray-core, you must generate the cryptographic material. The `xray tls ech` command in [`main/commands/all/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go) handles this.

### Basic Key Generation

```bash

# Generate new ECH keys for your domain

xray tls ech --serverName mydomain.com --pem

```

This outputs two base64-encoded blocks:

- **ECH CONFIGS** — the `echConfigList` value for clients
- **ECH KEYS** — the `echServerKeys` value for servers

### Restoring Existing Keys

```bash

# Reuse previously generated keys

xray tls ech --serverName mydomain.com -i "BASE64_ECH_SERVER_KEYS" --pem

```

The command uses X25519 for key encapsulation (`hpke.DHKEM(ecdh.X25519())`) and marshals the configuration using `cryptobyte.Builder` as seen in the source.

---

## Server-Side TLS with ECH Configuration

On the server, configure `echServerKeys` in your inbound TLS settings. This enables the server to accept and decrypt ECH handshakes.

### Minimal Server Configuration

```json
{
  "inbounds": [
    {
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [{ "id": "YOUR_UUID" }]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "certificates": [
            {
              "certificateFile": "/path/to/fullchain.pem",
              "keyFile": "/path/to/key.pem"
            }
          ],
          "echServerKeys": "BASE64_ECH_SERVER_KEYS"
        }
      }
    }
  ]
}

```

The `echServerKeys` value is processed by `ConvertToGoECHKeys` in [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go), which transforms the base64 data into Go's `tls.EncryptedClientHelloKey` struct and assigns it to `config.EncryptedClientHelloKeys`.

---

## Client-Side TLS with ECH Configuration

Clients use `echConfigList` to specify how to obtain ECH configuration. You have two options: direct embedding or DNS-HTTPS lookup.

### Option 1: Direct Config Embedding

```json
{
  "outbounds": [
    {
      "protocol": "freedom",
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "allowInsecure": false,
          "echConfigList": "BASE64_ECH_CONFIG",
          "echForceQuery": "full"
        }
      }
    }
  ]
}

```

### Option 2: DNS-HTTPS Lookup

```json
{
  "tlsSettings": {
    "echConfigList": "mydomain.com+https://1.1.1.1/dns-query",
    "echForceQuery": "full",
    "echSocketSettings": {
      "proxy": "dns://8.8.8.8"
    }
  }
}

```

When `echConfigList` contains a `+` separator, `ApplyECH` in [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) parses it as `domain+DoH_endpoint` and calls `QueryRecord`. This performs a type 65 HTTPS DNS lookup and caches results in `GlobalECHConfigCache`.

---

## Understanding echForceQuery Behavior

The `echForceQuery` parameter controls failure modes when ECH configuration cannot be obtained. This is critical for privacy-preserving operation.

| Value | Behavior | Use Case |
|-------|----------|----------|
| `none` | Skip ECH, use clear SNI | Compatibility mode, reduced privacy |
| `half` | Use cached config or fall back | Balanced approach |
| `full` | **Force lookup, fail closed on error** | Maximum privacy (default) |

In [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go), when `echForceQuery` is `"full"` and `QueryRecord` fails, the code injects an invalid config to deliberately break the connection rather than exposing the SNI in plaintext.

---

## Step-by-Step Configuration Walkthrough

### 1. Generate ECH Cryptographic Material

```bash
xray tls ech --serverName mydomain.com --pem > ech-material.txt

```

Extract the two base64 blocks from the output.

### 2. Configure the Server Inbound

Add `echServerKeys` to your TLS inbound settings using the **ECH KEYS** block.

### 3. Configure the Client Outbound

Add `echConfigList` using either:
- The **ECH CONFIGS** block (direct)
- A DNS-HTTPS URL for dynamic lookup

### 4. Set Failure Mode

Choose `echForceQuery` based on your privacy requirements:
- `full` for strict privacy
- `half` for tolerant operation
- `none` for testing only

### 5. Reload and Verify

```bash
systemctl restart xray

```

Verify ECH operation using traffic analysis tools. A successful ECH handshake will show "Encrypted Client Hello" rather than visible SNI data.

---

## Key Source Files Reference

| File | Purpose | Location |
|------|---------|----------|
| [`transport/internet/tls/config.pb.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/config.pb.go) | Protobuf definition of ECH configuration fields | [Link](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/config.pb.go) |
| [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) | Core logic: `ApplyECH`, `QueryRecord`, `ConvertToGoECHKeys` | [Link](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) |
| [`transport/internet/tls/ech_test.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech_test.go) | Unit tests for ECH dialing and cache behavior | [Link](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech_test.go) |
| [`main/commands/all/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go) | CLI command `xray tls ech` for key generation | [Link](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go) |
| [`infra/conf/transport_internet.go`](https://github.com/XTLS/Xray-core/blob/main/infra/conf/transport_internet.go) | Configuration parsing and wiring to TLS builder | [Link](https://github.com/XTLS/Xray-core/blob/main/infra/conf/transport_internet.go) |

---

## Summary

- **ECH in Xray-core** is configured through `echServerKeys` (server) and `echConfigList` (client) in TLS settings.
- **Key generation** uses the `xray tls ech` command from [`main/commands/all/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go), producing base64-encoded key material.
- **Server configuration** requires `echServerKeys` in inbound TLS settings, processed by `ConvertToGoECHKeys` in [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go).
- **Client configuration** supports direct embedding or DNS-HTTPS lookup via `QueryRecord`, with caching through `GlobalECHConfigCache`.
- **Failure handling** is controlled by `echForceQuery`: `full` fails closed for privacy, `half` allows fallback, `none` disables ECH.

---

## Frequently Asked Questions

### What is Encrypted Client Hello and why does Xray-core support it?

Encrypted Client Hello (ECH) is a TLS extension that encrypts the initial handshake, hiding the Server Name Indication (SNI) from network observers. Xray-core implements ECH to provide enhanced privacy for proxy connections, preventing traffic analysis based on cleartext SNI values. The implementation uses Go's standard `crypto/tls` ECH support, configured through the fields defined in [`transport/internet/tls/config.pb.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/config.pb.go).

### How do I generate ECH keys for my Xray-core server?

Run the built-in command: `xray tls ech --serverName yourdomain.com --pem`. This generates two base64-encoded blocks from [`main/commands/all/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/main/commands/all/tls/ech.go): "ECH CONFIGS" for clients and "ECH KEYS" for your server configuration. Save the output to a file and extract the values for use in your JSON configuration. You can also regenerate existing keys with `-i` flag followed by previous server keys.

### Why would I use DNS-HTTPS lookup instead of embedding echConfigList directly?

DNS-HTTPS lookup enables dynamic ECH configuration rotation without updating client configurations. When `echConfigList` contains a URL like `domain+https://1.1.1.1/dns-query`, `QueryRecord` in [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) queries the HTTPS DNS record (type 65) and caches results in `GlobalECHConfigCache`. This allows servers to rotate keys frequently for improved security while clients automatically retrieve current configurations.

### What happens if ECH configuration fails and echForceQuery is set to "full"?

When `echForceQuery` is `"full"` and ECH configuration cannot be obtained, `ApplyECH` in [`transport/internet/tls/ech.go`](https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/ech.go) injects an invalid configuration that causes the TLS handshake to fail entirely. This "fail closed" behavior prevents accidental cleartext SNI exposure, preserving privacy at the cost of connectivity. For less strict environments, use `"half"` to allow fallback or `"none"` to disable ECH entirely.