Error message
[SSL: DH_KEY_TOO_SMALL] dh key too small
Followed by a source location such as "(_ssl.c:1082)". curl and other OpenSSL clients print the same reason as "dh key too small".
Python ssl module (OpenSSL, security level 2) — ssl.SSLError, SSL_R_DH_KEY_TOO_SMALL
What it means
The server chose a finite-field Diffie-Hellman (DHE) key exchange with a group smaller than the client accepts, typically 1024 bits. Python 3.10 and later use OpenSSL security level 2, which requires at least 2048-bit DH, so the client aborts the handshake. The server's certificate is not the problem.
Why it happens
The server uses a 1024-bit DH group
commonOld Apache httpd releases (before 2.4.7 they used fixed 1024-bit parameters), older Java-based servers, and configurations that still load a dhparam file generated years ago. The Logjam research (2015) showed 1024-bit DH to be within reach of well-funded attackers.
Python's stricter default security level
commonPython 3.10 raised its default to OpenSSL security level 2. Code that connected fine on Python 3.9 can fail after an upgrade, although the server did not change.
Embedded devices and appliances
occasionalManagement interfaces of printers, PBX systems and industrial gear often only offer DHE with small groups.
How to fix it
- 1.
Confirm the key-exchange size
Connect once at security level 1 with openssl s_client and read "Server Temp Key".
bash # "Server Temp Key: DH, 1024 bits" confirms the weak group openssl s_client -connect legacy.example.com:443 -servername legacy.example.com \ -cipher 'DEFAULT:@SECLEVEL=1' </dev/null 2>/dev/null | grep 'Server Temp Key' - 2.
Server owners: use ECDHE or a 2048-bit group
Prefer ECDHE suites (and TLS 1.3); if DHE is required, configure an RFC 7919 group of at least 2048 bits.
text # nginx: prefer ECDHE; if DHE must stay, use a 2048-bit (or larger) group ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_dhparam /etc/nginx/ffdhe2048.pem; # RFC 7919 group - 3.
Temporary workaround: lower the security level for one host
"DEFAULT:@SECLEVEL=1" in a dedicated context accepts 1024-bit DH for that host only. Keep it off shared or default contexts: it also accepts other weak parameters.
python # Temporary workaround for one legacy host: security level 1 accepts 1024-bit DH import ssl, urllib.request ctx = ssl.create_default_context() ctx.set_ciphers("DEFAULT:@SECLEVEL=1") urllib.request.urlopen("https://legacy.example.com/", context=ctx)
Related errors
[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] ssl/tls alert handshake failurePython ssl module (OpenSSL)[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabledPython ssl module (OpenSSL 3)curl: (35) TLS connect error: error:0A000410:SSL routines::ssl/tls alert handsh…curl (OpenSSL backend)