ACME LetsEncrypt Mock Challenge Server in Python
A minimal HTTP server that serves key authorizations for ACME/Let's Encrypt DNS-01 or HTTP-01 challenges during testing and validation.
Python code
37 linesfrom http.server import HTTPServer, BaseHTTPRequestHandler
import json
# In-memory store simulating the ACME challenge token -> key authorization pair
challenge_store = {
"token_example": "token_example.key_authorization"
}
class AcmeChallengeHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Extract the token from the path like /.well-known/acme-challenge/<token>
if self.path.startswith("/.well-known/acme-challenge/"):
token = self.path.split("/")[-1]
key_authorization = challenge_store.get(token)
if key_authorization:
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(key_authorization.encode())
else:
self.send_response(404)
self.end_headers()
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
# Silence default logging for cleaner output
pass
def run_server(host="127.0.0.1", port=8080):
server = HTTPServer((host, port), AcmeChallengeHandler)
print(f"ACME challenge server running on {host}:{port}")
server.serve_forever()
if __name__ == "__main__":
run_server()
Output
ACME challenge server running on 127.0.0.1:8080
How it works
This mock server uses the standard library http.server module to respond to ACME challenge validation requests. The do_GET method parses the request path to extract the token, looks up the corresponding key authorization in an in-memory dictionary, and returns it as plain text with HTTP 200. Unknown tokens receive a 404 response. The log_message override suppresses noisy output, keeping test logs clean. This approach simulates the server-side portion of the ACME HTTP-01 challenge for local development and integration testing.
Common mistakes
- Forgetting to encode the response body as bytes before writing to `wfile`
- Using `json.load` instead of manual path parsing when reading the token
- Not silencing default logging, which clutters test output
Variations
- Use a database or file-backed store to persist challenge tokens across restarts
- Implement the ACME TLS-ALPN-01 challenge by serving a self-signed certificate in the TLS handshake
Real-world use cases
- Testing certificate issuance workflows locally without hitting Let's Encrypt's production servers.
- Simulating ACME challenge responses in CI/CD pipelines for staging environments that need ephemeral test domains.
- Building a lightweight validation handler for reverse-proxy setups that delegate ACME challenges to an internal service.
Sponsored
More from Auth & security at scale
- AES GCM encryption and decryption in Python medium
- Build a Mock OIDC Userinfo Endpoint in Python with Flask easy
- ChaCha20-Poly1305 mock in Python medium
- ECDH key agreement in Python with cryptography medium
- Enforce TLS 1.2 Minimum in Python easy
- Fetch Secrets from a Mock Secrets Manager in Python easy
Keep learning
Related tutorials and quizzes for this topic.