Does `ssl_ecdh_curve` have an order of preference?

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

Taking a quick gander at the nginx source, there is this comment in ngx_ssl_ecdh_curve()

    /*                                                                          
     * OpenSSL 1.0.2+ allows configuring a curve list instead of a single       
     * curve previously supported.  By default an internal list is used,        
     * with prime256v1 being preferred by server in OpenSSL 1.0.2b+             
     * and X25519 in OpenSSL 1.1.0+.                                            
     *                                                                          
     * By default a curve preferred by the client will be used for              
     * key exchange.  The SSL_OP_CIPHER_SERVER_PREFERENCE option can            
     * be used to prefer server curves instead, similar to what it              
     * does for ciphers.                                                        
     */

Thank you for looking at this. Does this mean that having ssl_prefer_server_ciphers on set already has nginx ask clients to use the ECDH curves is the order they are listed under ssl_ecdh_curve?

I think, if I’m following this correctly, AFAICT yes.