My question:
Does the order of curves in the ssl_ecdh_curve directive affect their order of preference? The nginx docs for ssl_ecdh_curve don’t mention anything on this topic.
How I encountered the question:
I’ve been experimenting with the performance of various TLS named groups, particularly the MLKEM ones. Some of the algorithms supported by the latest versions of OpenSSL have to be enabled explicitly for nginx to offer them to clients.
Solutions I’ve tried:
I set:
ssl_ecdh_curve mlkem1024:x25519mlkem768:SecP256r1MLKEM768:SecP384r1MLKEM1024:x25519:prime256v1;
Thinking it is going to work like ssl_ciphers directive with ssl_prefer_server_ciphers on. I tested different clients and different LC’s with different parameters, but I wasn’t able to determine if the order of curves in ssl_ecdh_curve matters or not. I’m not sure if different clients and LC’s have their own orders of preferrence they stick to.
I tried looking at various TLS configuration resources, which (judging from the order of algorithms they choose) make it seem like it matters, but I can’t be sure if that is just a convention they chose.
For example, the Mozilla SSL configurator lists the curves in what seems to be a reasonable order of preference.
OpenSSL’s openssl list -tls-groups command lists the curves in an order different from what it seems to choose when ssl_ecdh_curve is not explicitly stated (not to mention that some one the algorithms it lists aren’t enabled by default).
AWS-LC’s bssl s_client -connect prusa.net:443 -server-name prusa.net -grease seems to have an internal order of preference set, which can be overriden by appending the -curves parameter.
SSLlabs says that the order of named groups it received from my server is in “server preferred order”, but I’m not sure I trust it, since it hasn’t been updated for a while.
The sslscan CLI tools doesn’t indicate an order of preference, and seems to stick to the order of preference used by OpenSSL’s s_client implementation.
Version of NGINX or NGINX adjacent software (e.g. NGINX Gateway Fabric):
$ nginx -V
nginx version: nginx/1.29.8
built by gcc 15.2.1 20260209 (GCC)
built with OpenSSL 3.6.1 27 Jan 2026 (running with OpenSSL 3.6.2 7 Apr 2026)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --sbin-path=/usr/bin/nginx --modules-path=/usr/lib/nginx/modules --pid-path=/run/nginx.pid --lock-path=/run/lock/nginx.lock --user=http --group=http --http-log-path=/var/log/nginx/access.log --error-log-path=stderr --http-client-body-temp-path=/var/lib/nginx/client-body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-cc-opt='-march=x86-64 -mtune=generic -O2 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -g -ffile-prefix-map=/build/nginx-mainline/src=/usr/src/debug/nginx-mainline -flto=auto' --with-ld-opt='-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,-z,pack-relative-relocs -flto=auto' --with-compat --with-debug --with-file-aio --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_mp4_module --with-http_perl_module=dynamic --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-http_xslt_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre-jit --with-stream=dynamic --with-stream_geoip_module=dynamic --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-threads