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.

Medium Python 3.9+ Aug 9, 2026 Auth & security at scale 15 views 0 copies

Python code

37 lines
Python 3.9+
from 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

stdout
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

  1. Use a database or file-backed store to persist challenge tokens across restarts
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Auth & security at scale

Related tutorials and quizzes for this topic.