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
echto the DNS HTTPS record - Confirm
ech:SUCCESSin 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/echwithnginx-build - Use Docker
openssl-echimage to runopenssl ech - Extract ECHCONFIG and add it to the DNS HTTPS record (
ech=) - Add
ssl_ech_fileand ECH-related access log fields in nginx - Confirm
ech:SUCCESS:ech.catatsuy.orgin 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.