Enterprise ACME (RFC 8555) server for internal PKI
Project description
ACMEEH
Enterprise ACME (RFC 8555) server for internal PKI
Documentation | PyPI | Docker Hub | GitHub
Overview
ACMEEH is a production-ready ACME server built for organizations that need automated certificate management on their internal network. It fully implements RFC 8555 and is compatible with every standards-compliant ACME client — certbot, acme.sh, Caddy, Traefik, Lego, and others.
Plug in your own CA — a local root key, an HSM, HashiCorp Vault, or an upstream ACME provider — and ACMEEH handles the rest: challenge validation, certificate issuance, revocation, CRL distribution, renewal information, and lifecycle hooks.
Built with Python 3.12+, Flask, PostgreSQL, and gunicorn.
Features
- Pluggable CA Backends — Internal file-based CA, external HTTP API (Vault, EJBCA), PKCS#11 HSM, ACME proxy to upstream CA, or bring your own with
ext:plugins. All backends include a circuit breaker for resilience. - Challenge Validation — HTTP-01, DNS-01, and TLS-ALPN-01 with configurable timeouts, retries, and background validation workers.
- Revocation Infrastructure — Built-in CRL generation and ACME Renewal Information (ARI) — each independently toggleable.
- Admin REST API — Token-authenticated API for user management, audit logs, EAB credentials, identifier allowlists, CSR profiles, certificate search, bulk revocation, and maintenance mode.
- Hook System — 10 lifecycle events (account registration, order creation, challenge validation, certificate issuance/revocation/delivery, CT submission) with pluggable handlers.
- Security Controls — Per-endpoint rate limiting, key size and algorithm policies, identifier allowlists, External Account Binding (EAB), CAA enforcement (RFC 8659), CSR validation profiles, and per-account quotas.
- Prometheus Metrics —
/metricsendpoint for certificate counts, issuance rates, challenge stats, and CA backend health. - Email Notifications — SMTP alerts for certificate expiration with configurable warning days, retry with backoff, and Jinja2 templates.
- Certificate Transparency — RFC 6962 pre-certificate submission to multiple CT logs with SCT collection.
- Structured Logging & Audit — JSON and text log formats, audit trail with file/syslog output, and optional webhook export.
- Background Workers — Daemon threads for challenge validation, certificate expiration checks, nonce/order/challenge cleanup, and data retention — all HA-safe with PostgreSQL advisory locks.
Installation
From PyPI
Linux / macOS:
python3.12 -m venv .venv
source .venv/bin/activate
pip install acmeeh
# Optional: HSM (PKCS#11) support
pip install acmeeh[hsm]
Windows (PowerShell):
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install acmeeh
# Optional: HSM (PKCS#11) support
pip install acmeeh[hsm]
After installation the acmeeh command is available:
acmeeh -c config.yaml --validate-only
acmeeh -c config.yaml --dev
From Source
Linux / macOS:
git clone https://github.com/miichoow/ACMEEH.git
cd ACMEEH
python3.12 -m venv .venv
source .venv/bin/activate
# Install in editable mode (includes all dependencies)
pip install -e ".[dev]"
# Or install dependencies manually
pip install -r requirements.txt
Windows (PowerShell):
git clone https://github.com/miichoow/ACMEEH.git
cd ACMEEH
python -m venv .venv
.venv\Scripts\Activate.ps1
# Install in editable mode (includes all dependencies)
pip install -e ".[dev]"
# Or install dependencies manually
pip install -r requirements.txt
Docker
# 1. Copy the example env file and set your database password
cp docker/.env.example .env
vi .env # set POSTGRES_PASSWORD
# 2. Place your CA root certificate and key
mkdir -p certs
cp /path/to/root.pem certs/root.pem
cp /path/to/root-key.pem certs/root-key.pem
# 3. Build and start (ACMEEH + PostgreSQL)
docker compose up -d
# 4. Verify
curl http://localhost:8443/livez
curl http://localhost:8443/directory
Build with optional features:
# HSM (PKCS#11) support
docker compose build --build-arg INSTALL_HSM=1
# Gevent async workers
docker compose build --build-arg INSTALL_GEVENT=1
See the Docker documentation for the full reference — environment variables, configuration, common operations, reverse proxy setup, scaling, and troubleshooting.
Dependencies
Core dependencies (installed automatically via pip install acmeeh):
| Package | Purpose |
|---|---|
| Flask | Web framework |
| cryptography | X.509, JWS, key operations |
| dnspython | DNS-01 challenge validation |
| Jinja2 | Notification email templates |
| psycopg[binary] | PostgreSQL driver |
| pyConfigKit | Configuration management |
| PyPGKit | Database repository layer |
Optional:
| Package | Install with | Purpose |
|---|---|---|
| python-pkcs11 | pip install acmeeh[hsm] |
HSM backend via PKCS#11 |
| acmeow | pip install acmeeh[acme-proxy] |
ACME proxy CA backend |
| gunicorn | pip install acmeeh[server] |
Production server (Linux/macOS only) |
| pytest, pytest-cov | pip install acmeeh[dev] |
Testing and coverage |
Quick Start
# Set up PostgreSQL
psql -U postgres -c "CREATE USER acmeeh WITH PASSWORD 'secret';"
psql -U postgres -c "CREATE DATABASE acmeeh OWNER acmeeh;"
# Create config.yaml (see Configuration section below)
# Validate config
acmeeh -c config.yaml --validate-only
# Start development server
DB_PASSWORD=secret acmeeh -c config.yaml --dev
Note: On Windows, gunicorn is not available. Use
--devfor the Flask development server, or deploy behind a WSGI server like waitress.
Configuration
ACMEEH uses a single YAML configuration file with 27 settings sections. Only three fields are required:
server:
external_url: https://acme.example.com
database:
database: acmeeh
user: acmeeh
password: ${DB_PASSWORD} # env var substitution
auto_setup: true # create tables on first run
ca:
backend: internal
internal:
root_cert_path: /path/to/root-ca.pem
root_key_path: /path/to/root-ca-key.pem
challenges:
enabled:
- http-01
Environment variables are supported via ${VAR} or ${VAR:-default} syntax anywhere in the YAML.
See the full configuration reference for all settings.
CLI Reference
| Command | Description |
|---|---|
acmeeh -c config.yaml |
Start the server (gunicorn) |
acmeeh -c config.yaml --dev |
Start Flask development server |
acmeeh -c config.yaml --validate-only |
Validate config and exit |
acmeeh -c config.yaml db status |
Check database connectivity |
acmeeh -c config.yaml db migrate |
Run database migrations |
acmeeh -c config.yaml ca test-sign |
Test CA signing with ephemeral CSR |
acmeeh -c config.yaml crl rebuild |
Force CRL rebuild |
acmeeh -c config.yaml admin create-user --username admin --email admin@example.com |
Create admin user |
acmeeh -c config.yaml inspect order <id> |
Inspect an order |
acmeeh -c config.yaml inspect certificate <id> |
Inspect a certificate |
acmeeh -c config.yaml inspect account <id> |
Inspect an account |
Global flags: -c/--config (required), --debug, --dev, --validate-only, -v/--version
CA Backends
| Backend | Description |
|---|---|
internal |
Sign with a root CA key stored as PEM files on disk |
external |
Delegate signing to a remote HTTP API (e.g., HashiCorp Vault, EJBCA) |
hsm |
Sign using a Hardware Security Module via PKCS#11 |
acme_proxy |
Proxy to an upstream ACME CA (e.g., Let's Encrypt) |
ext:<path> |
Load a custom backend class (e.g., ext:mycompany.pki.VaultBackend) |
All backends support a circuit breaker that prevents cascading failures on repeated signing errors.
See the CA backends documentation for detailed setup instructions.
Challenge Types
| Type | Validation |
|---|---|
http-01 |
HTTP request to http://{domain}/.well-known/acme-challenge/{token} on port 80 |
dns-01 |
DNS TXT record query at _acme-challenge.{domain} |
tls-alpn-01 |
TLS connection to port 443 with ALPN protocol acme-tls/1 |
For the acme_proxy backend, set challenges.auto_accept: true to auto-accept all downstream challenges (real validation happens upstream).
Custom challenge validators can be added as plugins.
Architecture
ACME Clients (certbot, acme.sh, Caddy, ...)
│
HTTPS / RFC 8555
│
┌───────────────────────────────┐
│ Flask API Layer │
│ directory, nonce, account, │
│ order, authz, challenge, │
│ certificate, key-change │
├───────────────────────────────┤
│ Service Layer │
│ AccountService, OrderService │
│ ChallengeService, CertSvc │
├──────────┬────────────────────┤
│Repository│ CA Backend │
│ Layer │ internal/external/ │
│(PyPGKit) │ hsm/acme_proxy/ │
│ │ ext:custom │
├──────────┴────────────────────┤
│ DI Container (context.py) │
└───────────────┬───────────────┘
│
PostgreSQL 14+
Testing
If installed with pip install -e ".[dev]", pytest is already available and PYTHONPATH is handled automatically.
Linux / macOS:
# Run all tests
pytest
# Run a specific test
pytest tests/test_config.py::test_name -v
# Run with coverage
pytest --cov=acmeeh --cov-report=html
Windows (PowerShell):
# Run all tests
pytest
# Run a specific test
pytest tests\test_config.py::test_name -v
# Run with coverage
pytest --cov=acmeeh --cov-report=html
If you installed dependencies manually (without pip install -e .), prefix commands with PYTHONPATH=src on Linux or set it on Windows:
# Linux / macOS
PYTHONPATH=src python -m pytest tests/
# Windows (PowerShell)
$env:PYTHONPATH = "src"; python -m pytest tests/
Documentation
Full documentation is available in the docs/ folder (Sphinx/reStructuredText, compatible with Read the Docs):
- Installation — Prerequisites, setup, first run
- Configuration Reference — All 27 settings sections with defaults
- ACME API Reference — RFC 8555 endpoints and JWS auth
- CA Backends — Internal, external, HSM, ACME proxy, custom
- Extensibility — Custom validators, hooks, handlers, templates
- Admin API — REST API for server management
- Docker — Dockerfile, Compose, env vars, operations
- Deployment — Production setup, reverse proxy, monitoring
- Development — Project structure, testing, hooks
To build the docs locally:
pip install -r docs/requirements.txt
sphinx-build -b html docs docs/_build/html
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acmeeh-2.0.0.tar.gz.
File metadata
- Download URL: acmeeh-2.0.0.tar.gz
- Upload date:
- Size: 243.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3dc55bed639252aeb9ea4d0e3d647f903ff835f1f9485f0f9c896cd2a06e3dec
|
|
| MD5 |
00d34ef04a6fb89de3e598fc93c7f91c
|
|
| BLAKE2b-256 |
d4492c933b967d7453edd11739b82f6d1f14a5e7dc53b0e8ee1041961f7cc6fa
|
Provenance
The following attestation bundles were made for acmeeh-2.0.0.tar.gz:
Publisher:
publish.yml on miichoow/ACMEEH
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acmeeh-2.0.0.tar.gz -
Subject digest:
3dc55bed639252aeb9ea4d0e3d647f903ff835f1f9485f0f9c896cd2a06e3dec - Sigstore transparency entry: 1019102113
- Sigstore integration time:
-
Permalink:
miichoow/ACMEEH@f6e30424343e7e31eed8d33b9db2393438b194ff -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/miichoow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f6e30424343e7e31eed8d33b9db2393438b194ff -
Trigger Event:
push
-
Statement type:
File details
Details for the file acmeeh-2.0.0-py3-none-any.whl.
File metadata
- Download URL: acmeeh-2.0.0-py3-none-any.whl
- Upload date:
- Size: 278.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84a7dea3c18ca2dd612d534095c6cabb934ef44df04daeeaff0ac33b75e50417
|
|
| MD5 |
2922350b8ed36d7370cba79d535a9e82
|
|
| BLAKE2b-256 |
be7cfe02e7134d38eb6c0fd42c0a8c20b1d59b186b3bfdba061d556147404da6
|
Provenance
The following attestation bundles were made for acmeeh-2.0.0-py3-none-any.whl:
Publisher:
publish.yml on miichoow/ACMEEH
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
acmeeh-2.0.0-py3-none-any.whl -
Subject digest:
84a7dea3c18ca2dd612d534095c6cabb934ef44df04daeeaff0ac33b75e50417 - Sigstore transparency entry: 1019102118
- Sigstore integration time:
-
Permalink:
miichoow/ACMEEH@f6e30424343e7e31eed8d33b9db2393438b194ff -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/miichoow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f6e30424343e7e31eed8d33b9db2393438b194ff -
Trigger Event:
push
-
Statement type: