SSL/TLS Connections with the ssl Module

· 7 min read · Updated March 15, 2026 · intermediate
python security networking ssl tls https

The ssl module in Python’s standard library provides a way to wrap sockets with SSL/TLS encryption. This is the foundation for secure HTTP connections, encrypted database connections, and any network communication that needs privacy and integrity verification.

What Is SSL/TLS?

SSL (Secure Sockets Layer) and its successor TLS (Transport Layer Security) are cryptographic protocols that provide communication security over a network. They use:

  • Encryption: Scrambles data so eavesdroppers cannot read it
  • Authentication: Verifies the identity of the server (and optionally the client)
  • Integrity: Detects if data has been tampered with during transmission

When you visit a website with HTTPS, your browser uses TLS to secure the connection. Python’s ssl module lets you do the same in your code.

Creating an SSL Context

The core of the ssl module is the SSLContext. This object holds your configuration options and is used to wrap sockets:

import ssl

# Create a context that verifies server certificates (the default)
context = ssl.create_default_context()

# For client connections, load trusted certificates
context.load_verify_locations("/etc/ssl/certs/ca-certificates.crt")

Context Options

The ssl module provides several preset context configurations:

import ssl

# Default context - verifies certificates, suitable for clients
default_context = ssl.create_default_context()

# Client SSL context - for making HTTPS requests
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

# Server SSL context - for accepting connections
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

# Legacy options (avoid if possible)
legacy_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)

Checking What’s Available

import ssl

# List available TLS versions
print(f"Minimum TLS version: {ssl.TLSVersion.MINIMUM_VERSION}")
print(f"Maximum TLS version: {ssl.TLSVersion.MAXIMUM_VERSION}")

# Check what protocols are available
print(f"Available protocols: {ssl.PROTOCOL_TLS}")

Making Secure Client Connections

Simple HTTPS Request

The easiest way to make an HTTPS request is using ssl with socket or through higher-level libraries:

import ssl
import socket

def fetch_https(host, path="/"):
    """Fetch a page over HTTPS using raw SSL socket."""
    # Create context that verifies the server certificate
    context = ssl.create_default_context()
    
    # Connect to the server
    with socket.create_connection((host, 443)) as sock:
        # Wrap the socket with SSL
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            # Send HTTP request
            request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\n\r\n"
            ssock.send(request.encode())
            
            # Receive response
            response = b""
            while True:
                data = ssock.recv(4096)
                if not data:
                    break
                response += data
            
            return response.decode()

# Fetch a page
html = fetch_https("example.com", "/")
print(html[:200])

Verifying Certificates

Certificate verification is crucial for security. Never skip it in production:

import ssl
import socket

def fetch_with_verification(host, port=443):
    """Fetch with full certificate verification."""
    # Create default context - it verifies certificates by default
    context = ssl.create_default_context()
    
    # Optionally, specify a custom CA bundle
    # context.load_verify_locations("custom-ca-bundle.pem")
    
    with socket.create_connection((host, port)) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            # Get certificate info
            cert = ssock.getpeercert()
            print(f"Connected to: {host}")
            print(f"Certificate subject: {cert.get('subject', 'N/A')}")
            print(f"Certificate issuer: {cert.get('issuer', 'N/A')}")
            
            return ssock

# This will verify the certificate automatically
connection = fetch_with_verification("google.com")

Handling Certificate Errors

Sometimes you need to handle certificate errors gracefully:

import ssl
import socket

def fetch_ignore_verification(host):
    """Fetch ignoring certificate errors - FOR TESTING ONLY."""
    # WARNING: Never do this in production!
    context = ssl.create_default_context()
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE
    
    with socket.create_connection((host, 443)) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            return ssock.recv(4096).decode()

# For testing with self-signed certificates
# result = fetch_ignore_verification("localhost")

Creating an SSL Server

Basic HTTPS Server

import ssl
import socket

def create_ssl_server(host="0.0.0.0", port=8443):
    """Create a simple HTTPS server."""
    
    # Create server context
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    
    # Load certificate and private key
    # Generate with: openssl req -new -x509 -keyout key.pem -out cert.pem
    context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")
    
    # Create a listening socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((host, port))
    server_socket.listen(5)
    
    print(f"Server listening on https://{host}:{port}")
    
    while True:
        client_socket, addr = server_socket.accept()
        print(f"Connection from {addr}")
        
        # Wrap with SSL
        try:
            ssl_socket = context.wrap_socket(client_socket, server_side=True)
            
            # Read request
            request = ssl_socket.recv(1024).decode()
            print(f"Request: {request[:100]}")
            
            # Send response
            response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, Secure World!"
            ssl_socket.send(response.encode())
            
        except ssl.SSLError as e:
            print(f"SSL Error: {e}")
        finally:
            ssl_socket.close()

# Uncomment to run:
# create_ssl_server()

Configuring Server Security

For production servers, configure strong security options:

import ssl

def create_secure_context():
    """Create a secure server context with strong defaults."""
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    
    # Load certificate
    context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")
    
    # Set minimum TLS version (TLS 1.2 or higher)
    context.minimum_version = ssl.TLSVersion.TLSv1_2
    
    # Prefer server cipher order
    context.set_ciphers("ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20")
    
    # Enable OCSP stapling (requires valid certificate)
    # context.ocsp_response = ocsp_response
    
    return context

Certificate Handling

Loading Certificates

import ssl

# Load certificate chain for server
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile="server.crt", keyfile="server.key")

# Load CA certificate for client verification
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations(cafile="ca.crt")

# Load both certificate and CA in one call
context = ssl.create_default_context()
context.load_cert_chain(certfile="combined.crt", keyfile="key.pem")

Getting Certificate Information

import ssl
import socket

def get_certificate_info(hostname, port=443):
    """Get detailed certificate information."""
    context = ssl.create_default_context()
    
    with socket.create_connection((hostname, port)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            cert = ssock.getpeercert()
            
            # Parse certificate fields
            print("Subject:")
            for key, value in cert.get('subject', []):
                print(f"  {key}: {value}")
            
            print("\nIssuer:")
            for key, value in cert.get('issuer', []):
                print(f"  {key}: {value}")
            
            print(f"\nValid from: {cert.get('notBefore')}")
            print(f"Valid until: {cert.get('notAfter')}")
            print(f"Serial number: {cert.get('serialNumber')}")

# get_certificate_info("google.com")

Validating Hostnames

The match_hostname function helps verify that the certificate matches the intended hostname:

import ssl
import socket

def fetch_with_hostname_check(host):
    """Fetch with proper hostname verification."""
    context = ssl.create_default_context()
    
    with socket.create_connection((host, 443)) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            # getpeercert() returns the certificate dict
            cert = ssock.getpeercert()
            
            # Use ssl.match_hostname to verify
            ssl.match_hostname(cert, host)
            
            print(f"Certificate verified for {host}")
            return ssock

Working with PEM Files

PEM is a common format for certificates and keys. The ssl module works natively with PEM:

import ssl
from pathlib import Path

def load_pem_certificates(pem_file):
    """Load certificates from a PEM file."""
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.load_verify_locations(pem_file)
    
    return context

# Using pathlib for cleaner file handling
cert_path = Path("/etc/ssl/certs/ca-certificates.crt")
if cert_path.exists():
    context = ssl.create_default_context()
    context.load_verify_locations(cert_path)

Real-World Examples

Secure Database Connection

import ssl
import socket

def connect_secure_db(host, user, password, database):
    """Connect to a database over SSL."""
    
    # Create SSL context
    context = ssl.create_default_context()
    context.check_hostname = True
    context.verify_mode = ssl.CERT_REQUIRED
    
    # For MySQL/MariaDB, the driver handles SSL automatically
    # when ssl parameter is provided
    # connection = pymysql.connect(
    #     host=host,
    #     user=user,
    #     password=password,
    #     database=database,
    #     ssl={"ca": context}
    # )
    
    return context

Secure Email (SMTP with TLS)

import ssl
import smtplib

def send_secure_email(smtp_server, port, username, password, 
                     from_addr, to_addr, subject, body):
    """Send email over TLS/SSL."""
    
    # For port 587 (STARTTLS)
    context = ssl.create_default_context()
    
    with smtplib.SMTP(smtp_server, port) as server:
        server.starttls(context=context)
        server.login(username, password)
        
        message = f"Subject: {subject}\n\n{body}"
        server.sendmail(from_addr, to_addr, message)
    
    print("Email sent securely")

WebSocket over SSL

import ssl
import websocket  # websocket-client library

def secure_websocket_connect(url):
    """Connect to a WebSocket over WSS (WebSocket Secure)."""
    
    # Create SSL context
    context = ssl.create_default_context()
    context.check_hostname = True
    context.verify_mode = ssl.CERT_REQUIRED
    
    # Connect with SSL
    ws = websocket.WebSocket(
        sslopt={"context": context}
    )
    
    ws.connect(url)
    return ws

# ws = secure_websocket_connect("wss://example.com/ws")

Common Errors and Solutions

Certificate Verify Failed

import ssl
import socket

# Error: certificate verify failed
# Solution 1: Update CA certificates
context = ssl.create_default_context()
# On Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt
# On RHEL/CentOS: /etc/pki/tls/certs/ca-bundle.crt
context.load_verify_locations("/etc/ssl/certs/ca-certificates.crt")

# Solution 2: The server certificate is self-signed
# Add the self-signed cert to trusted list
# context.load_verify_locations("my-ca-bundle.pem")

Connection Refused

This usually means the server isn’t accepting connections on the expected port or TLS version:

import ssl
import socket

def check_tls_support(hostname, port=443):
    """Check which TLS versions a server supports."""
    for version in [ssl.TLSVersion.TLSv1, ssl.TLSVersion.TLSv1_1, 
                   ssl.TLSVersion.TLSv1_2, ssl.TLSVersion.TLSv1_3]:
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        context.minimum_version = version
        context.maximum_version = version
        
        try:
            with socket.create_connection((hostname, port)) as sock:
                with context.wrap_socket(sock, server_hostname=hostname) as ssock:
                    print(f"Server supports: {version.name}")
        except ssl.SSLError:
            print(f"Server does not support: {version.name}")

Hostname or IP Not in Certificate

This happens when the certificate doesn’t cover the hostname you’re connecting to:

# This error means the certificate CN/SAN doesn't match your hostname
# Solutions:
# 1. Use the correct hostname (e.g., use "example.com" not "www.example.com")
# 2. Get a certificate that covers all needed hostnames
# 3. For internal servers, add the hostname to /etc/hosts and use that

Security Best Practices

  1. Always use TLS 1.2 or higher - Older versions (SSL, TLS 1.0, 1.1) have known vulnerabilities

  2. Verify certificates in production - Never disable verification unless you have a specific reason

  3. Keep your CA certificates updated - Outdated CA bundles can cause verification failures

  4. Use strong ciphers - Configure your context to prefer modern ciphers like AES-GCM and ChaCha20

  5. Rotate certificates before expiration - Set up reminders for certificate renewal

  6. Use certificate pinning for high-security applications - Verify specific certificates, not just any valid CA

See Also