Python "ssl.SSLError" in Tests - Fix TLS Handshake Failures in CI
A test opened a TLS connection that failed during the handshake. The hostname, port, protocol, or trust store is wrong - often a test client speaking HTTPS to a plain-HTTP fixture, or a self-signed cert the runner doesn’t trust.
What this error means
A test that talks to a server (a fixture, a localhost stub, or a real endpoint) raises ssl.SSLError - frequently WRONG_VERSION_NUMBER or CERTIFICATE_VERIFY_FAILED - during the handshake, before any request body is sent.
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1006)
# or
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify
failed: self signed certificate (_ssl.c:1006)Common causes
HTTPS client against an HTTP server
WRONG_VERSION_NUMBER almost always means the client tried a TLS handshake with a server speaking plain HTTP - a test pointed https:// at a fixture that only serves http://.
Self-signed or untrusted test certificate
A local test server presents a self-signed certificate the runner’s trust store doesn’t contain, so verification fails during the handshake.
How to fix it
Match the scheme to the server
If the fixture serves plain HTTP, use http://. Only use https:// when the test server actually terminates TLS.
# fixture serves plain HTTP - use http, not https
resp = client.get("http://localhost:8000/health")Trust the test certificate explicitly
For a self-signed test server, point the client at its CA bundle rather than disabling verification globally.
import ssl
ctx = ssl.create_default_context(cafile="tests/certs/test-ca.pem")
# pass ctx to your client, e.g. urlopen(url, context=ctx)How to prevent it
- Keep test fixtures’ scheme (http vs https) consistent with what they serve.
- Ship a dedicated test CA and trust it explicitly instead of disabling verification.
- Centralize the test server URL so the scheme isn’t hardcoded in many places.