Command-line tools

OpenSSL dh key too small: fix weak DHE on the server

By · Last verified

Error messageerror:0A00018A:SSL routines::dh key too small

error:0A00018A:SSL routines::dh key too small means the server picked a DHE cipher suite and sent Diffie-Hellman parameters smaller than your OpenSSL security level allows. The client is behaving correctly. Fix the server: prefer ECDHE or TLS 1.3, or use DH parameters of 2048 bits or more. On the client, offer only ECDHE suites.

What the error means

The server offered DH parameters below your OpenSSL security level, so the client refused them. In a TLS 1.2 handshake with a DHE-* cipher suite, the server sends a prime and generator that both sides use for a Diffie-Hellman key exchange. If that prime is small, an attacker with enough computing power can break the exchange; the 2015 Logjam research broke 512-bit groups in practice and warned that 1024-bit groups are within reach of well-funded attackers (see Logjam).

OpenSSL enforces minimum sizes through security levels. According to the OpenSSL manual, level 1 prohibits DH keys shorter than 1024 bits, and level 2 prohibits DH keys shorter than 2048 bits. OpenSSL 3.2 changed the library default from level 1 to level 2, and a distribution or administrator can raise the level further in openssl.cnf. That is why a server whose 1024-bit DH file worked for years suddenly fails after a client upgrade.

The same reason appears in different tools:

error:0A00018A:SSL routines::dh key too small            # openssl, curl (exit code 35)
ssl.SSLError: [SSL: DH_KEY_TOO_SMALL] dh key too small (_ssl.c:1006)   # Python

Causes, most common first

1. The server is configured with old 1024-bit (or smaller) DH parameters

The usual sources are a dhparam.pem generated with 1024 bits years ago, a default from an old distribution, or an appliance with fixed parameters. On Nginx this can only happen when ssl_dhparam is set, because since Nginx 1.11.0 DHE suites are not used without it.

  • Confirm: see what the server sends. Lowering the security level here is only for diagnosis:
openssl s_client -connect example.com:443 -servername example.com -tls1_2 -cipher 'DHE:@SECLEVEL=0' </dev/null 2>/dev/null | grep "Server Temp Key"
# Server Temp Key: DH, 1024 bits

On the server itself: openssl dhparam -in /etc/nginx/dhparam.pem -noout -text | head -n 1.

  • Fix: regenerate with at least 2048 bits, or remove DHE suites (commands below).

2. The server offers only DHE suites, or prefers them

If the server's cipher list has no ECDHE suites, or it enforces its own order with DHE first, the client cannot avoid DHE. The SSL checker lists the suites and their order; the cipher suite lookup explains each one.

  • Fix: put ECDHE AEAD suites first and enable TLS 1.3.

3. The client's security level went up

Upgrading to OpenSSL 3.2 or later, or to a distribution that sets CipherString = DEFAULT@SECLEVEL=2, raises the bar from 1024 to 2048 bits. This is the trigger, not the bug.

  • Confirm: openssl version and grep -i seclevel "$(openssl version -d | cut -d'"' -f2)/openssl.cnf".

4. Mail and other non-HTTPS servers

SMTP, IMAP, LDAP and database servers often have their own, older DH settings. Test them with STARTTLS, for example openssl s_client -connect mail.example.com:587 -starttls smtp, or use the STARTTLS checker.

Fix commands by platform

Every server fix removes small DH groups: prefer ECDHE, or set 2048-bit parameters.

Nginx:

openssl dhparam -out /etc/nginx/dhparam.pem 2048
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# Either drop DHE entirely (as above), or keep it with a 2048-bit file:
# ssl_dhparam /etc/nginx/dhparam.pem;

Then nginx -t && systemctl reload nginx.

Apache httpd 2.4.7+: append 2048-bit parameters generated with openssl dhparam 2048 to the end of the first file named in SSLCertificateFile, or remove DHE from SSLCipherSuite.

Java servers (Tomcat, Jetty, Spring Boot): start the JVM with -Djdk.tls.ephemeralDHKeySize=2048, and keep ECDHE suites enabled.

Appliances and hosted services: update the firmware or pick a newer TLS policy; the TLS config generator shows a modern baseline.

Client side, when you cannot change the server yet: offer only ECDHE suites, which keeps the security level intact:

curl --ciphers 'ECDHE+AESGCM:ECDHE+CHACHA20' https://example.com/
import ssl
ctx = ssl.create_default_context()
ctx.set_ciphers("ECDHE+AESGCM:ECDHE+CHACHA20")   # TLS 1.3 suites are unaffected

Don't disable certificate verification

curl -k or verify=False will not help: the failure happens during key exchange, not certificate validation. They only add the risk that someone in the path impersonates the server and reads your traffic.

Avoid the other tempting shortcut too: DEFAULT@SECLEVEL=1 in a global openssl.cnf, or @SECLEVEL=0. Level 0 accepts any key size, and lowering the level system-wide also re-enables weak RSA keys and SHA-1 signatures for every program. If a server offers nothing but small DH groups, scope @SECLEVEL=1 to that single command or context, keep certificate verification on, and file a ticket to fix the server. In OpenSSL 3.x, TLS 1.0 and 1.1 only work at security level 0, so a server that is also stuck on TLS 1.0 needs a server-side fix.

Verify the fix

openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null 2>/dev/null | grep -E "Server Temp Key|Cipher is"
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>/dev/null | grep -E "Protocol|Cipher is"

With default client settings the first command should show an ECDH/X25519 temp key or DH, 2048 bits, and the second should negotiate TLS 1.3. Re-run your original curl or Python command without any cipher overrides; it should succeed. Background on how key exchange and cipher suites fit together is in cipher suites explained.

FAQ

Why does this server work in my browser but fail with curl or Python?

Chrome removed DHE cipher suites in Chrome 53 (2016), and current browsers negotiate ECDHE or TLS 1.3, so they never see the server's weak DH parameters. OpenSSL-based tools still offer DHE suites by default, the server picks one, and the security level check then rejects the small key.

Is 2048-bit DH enough?

2048 bits meets OpenSSL security level 2 (112-bit security). ECDHE with X25519 or P-256 is faster and stronger, and TLS 1.3 removes custom DH parameters entirely, so most modern configurations disable DHE.

Can I add @SECLEVEL=1 to my client instead?

Only as a short-term measure for one host. It also re-allows other weak parameters such as 1024-bit RSA keys and SHA-1 signatures in the handshake. Restricting the client to ECDHE cipher suites usually works without weakening anything, because almost every server that supports DHE also supports ECDHE.

What does the equivalent Java error look like?

Java clients apply jdk.tls.disabledAlgorithms, which includes DH keySize < 1024, so a server with tiny DH parameters fails the handshake there too, with an SSLHandshakeException rather than this OpenSSL message. Java servers can raise their own ephemeral DH size with -Djdk.tls.ephemeralDHKeySize=2048.

Sources

  1. OpenSSL: SSL_CTX_set_security_level (security levels)
  2. OpenSSL 3.2 NEWS (default security level changed to 2)
  3. RFC 7919: Negotiated Finite Field DHE Parameters for TLS
  4. nginx: ngx_http_ssl_module (ssl_dhparam)
  5. Apache httpd: mod_ssl (custom DH parameters)
  6. Chrome 53 deprecations and removals (DHE-based ciphers)