Enabling ECH (Encrypted Client Hello) with nginx 1.29.4 using nginx-build

nginx 1.29.4 added support for ECH (Encrypted Client Hello).

To use ECH with nginx, you currently need an OpenSSL branch with ECH support.
A while ago (since nginx-build v0.16.0), I added support for using a custom OpenSSL-compatible implementation in nginx-build (I am the maintainer), so I used that feature for this setup.

The rough flow is:

  • Build nginx itself with nginx-build
  • Build ECH-enabled OpenSSL in Docker
  • Add ech to the DNS HTTPS record
  • Confirm ech:SUCCESS in nginx access logs

The official nginx blog post explains the overall ECH flow very well:

Build ECH-enabled nginx with nginx-build

nginx-build supports -customssl, -customsslname, and -customssltag (I added these recently).

First, prepare a configure script like this:

#!/bin/sh
./configure \
  --sbin-path=/usr/local/nginx/sbin/nginx \
  --conf-path=/usr/local/nginx/conf/nginx.conf \
  --pid-path=/usr/local/nginx/logs/nginx.pid \
  --with-http_ssl_module \
  --with-http_v3_module \
  --with-stream_ssl_module

Then run nginx-build like this:

nginx-build \
  -d work \
  -v 1.29.4 \
  -c ./configure.ech.sh \
  -customssl https://github.com/openssl/openssl.git \
  -customsslname openssl-ech \
  -customssltag feature/ech

This builds nginx 1.29.4 with OpenSSL feature/ech.

Use openssl ech via Docker

At the moment, the normal upstream OpenSSL build does not provide openssl ech, so I build an ECH-enabled OpenSSL in Docker.

FROM debian:bookworm

RUN apt-get update && apt-get install -y \
    build-essential \
    git \
    ca-certificates \
    perl \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /build
RUN git clone --depth=1 --branch feature/ech https://github.com/openssl/openssl.git openssl

WORKDIR /build/openssl
RUN ./Configure --prefix=/opt/openssl-ech --openssldir=/opt/openssl-ech/ssl \
 && make -j"$(nproc)" \
 && make install_sw

ENV PATH="/opt/openssl-ech/bin:${PATH}"
ENV LD_LIBRARY_PATH="/opt/openssl-ech/lib64:/opt/openssl-ech/lib"

WORKDIR /work
CMD ["bash"]

Build the image:

docker build -t openssl-ech .

Then run openssl ech like this:

docker run --rm \
  openssl-ech \
  openssl ech -public_name ech.catatsuy.org -out /dev/stdout

The domain passed to -public_name becomes the outer name (public name), so it must be a domain you control and for which you have a valid certificate.

This prints a PEM-like output to stdout. I copy it and save it as a file for nginx:

-----BEGIN PRIVATE KEY-----
aaaaaaaa
-----END PRIVATE KEY-----
-----BEGIN ECHCONFIG-----
bbbb
cccc
-----END ECHCONFIG-----

Add ECHCONFIG to the DNS HTTPS record

The content between BEGIN ECHCONFIG and END ECHCONFIG is base64.

When adding it to DNS as ech=, remove line breaks and make it a single line.

In other words, add something like this to your DNS HTTPS record:

ech="bbbbcccc"

nginx configuration

Add ssl_ech_file and log $ssl_ech_status and $ssl_ech_outer_server_name.

http {
  ssl_ech_file /usr/local/nginx/conf/ech/ech.catatsuy.org.pem.ech;

  log_format main '$remote_addr - $host "$request" $status '
    'ech:$ssl_ech_status:$ssl_ech_outer_server_name';

  access_log /usr/local/nginx/logs/access.log main;

  # Outer side (public_name side)
  server {
    listen 443 ssl http2;
    server_name ech.catatsuy.org;
    ssl_certificate     /path/to/ech.catatsuy.org.crt;
    ssl_certificate_key /path/to/ech.catatsuy.org.key;
  }

  # Real service
  server {
    listen 443 ssl http2;
    server_name www.catatsuy.org;
    ssl_certificate     /path/to/www.catatsuy.org.crt;
    ssl_certificate_key /path/to/www.catatsuy.org.key;
  }
}

Verification

Check the nginx access log. If you see output like this, it worked:

ech:SUCCESS:ech.catatsuy.org

Summary

The full flow was:

  • Build nginx 1.29.4 + OpenSSL feature/ech with nginx-build
  • Use Docker openssl-ech image to run openssl ech
  • Extract ECHCONFIG and add it to the DNS HTTPS record (ech=)
  • Add ssl_ech_file and ECH-related access log fields in nginx
  • Confirm ech:SUCCESS:ech.catatsuy.org in access logs

It is a little inconvenient that this still needs the OpenSSL ECH branch, but with nginx-build it is pretty easy to try.

2 Likes

Thanks for sharing! A lot of people are really excited about this one!

1 Like