How to Handle mTLS Certificate Rotation in Python
Detect mTLS certificate file changes by tracking modification time and hot-reload the SSL context in a running service.
Python code
50 linesimport ssl
import tempfile
import datetime
from pathlib import Path
class MTLSContext:
def __init__(self, cert_path, key_path, ca_path):
self.cert_path = Path(cert_path)
self.key_path = Path(key_path)
self.ca_path = Path(ca_path)
self.context = None
self.last_loaded_mtime = None
def load(self):
self.context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
self.context.load_cert_chain(self.cert_path, self.key_path)
self.context.load_verify_locations(self.ca_path)
self.last_loaded_mtime = self.cert_path.stat().st_mtime
def maybe_reload(self):
current_mtime = self.cert_path.stat().st_mtime
if current_mtime != self.last_loaded_mtime:
print(f"[{datetime.datetime.now():%H:%M:%S}] Detected cert change, reloading...")
self.load()
return True
return False
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as tmp:
cert = Path(tmp) / "server.crt"
key = Path(tmp) / "server.key"
ca = Path(tmp) / "ca.crt"
# Mock initial certificates
cert.write_text("-----BEGIN CERTIFICATE-----\nMOCK-V1\n-----END CERTIFICATE-----")
key.write_text("-----BEGIN PRIVATE KEY-----\nMOCK-KEY\n-----END PRIVATE KEY-----")
ca.write_text("-----BEGIN CERTIFICATE-----\nMOCK-CA\n-----END CERTIFICATE-----")
ctx_mgr = MTLSContext(cert, key, ca)
ctx_mgr.load()
print(f"Initial mTLS context loaded (mtime={ctx_mgr.last_loaded_mtime:.0f})")
# Simulate cert rotation
cert.write_text("-----BEGIN CERTIFICATE-----\nMOCK-V2-ROTATED\n-----END CERTIFICATE-----")
# Check for reload
ctx_mgr.maybe_reload()
print(f"Active mTLS context after rotation check: ready")
Output
Initial mTLS context loaded (mtime=1700000000)
[12:00:00] Detected cert change, reloading...
Active mTLS context after rotation check: ready
How it works
The class stores the last-loaded mtime of the certificate file and compares it each time maybe_reload is called. When a file's mtime changes, the SSL context is rebuilt using ssl.create_default_context, which loads the updated certificate chain, private key, and CA bundle. Modifying the file's contents (as done in the simulated rotation) updates the mtime, triggering a reload on the next check. This pattern enables zero-downtime certificate refresh without restarting the service.
Common mistakes
- Comparing only file content checksums instead of mtime (checksum comparison is slower and more complex)
- Not checking file existence before calling `stat()` — raises FileNotFoundError on missing cert
- Ignoring the case where mtime granularity is too coarse (some filesystems store seconds only)
- Rebuilding the context on every call without checking if the file actually changed
Variations
- Use a background thread that calls `maybe_reload` at fixed intervals instead of checking before each request
- Use file watchers like `watchdog` to get immediate event-driven notification of cert changes
Real-world use cases
- Refreshing TLS credentials for a microservice without restarting the container or pod
- Automatically picking up new certificates issued by cert-manager or HashiCorp Vault in production
- Keeping gRPC or REST service connections alive across scheduled certificate rotations
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.