Observed since 20 Oct, 18:18 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Enterprise ACME (RFC 8555) server for internal PKI
Enterprise ACME (RFC 8555) server for internal PKI
Documentation | PyPI | Docker Hub | GitHub | Admin Client
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.
ext: plugins. All backends include a circuit breaker for resilience.acmeeh-admin for the official Python client and CLI./metrics endpoint for certificate counts, issuance rates, challenge stats, and CA backend health.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
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
# 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.
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 |
# 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.
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.
| 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
| 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.
| 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 |
dns-persist-01 | Long-lived DNS TXT record at _validation-persist.{domain} (draft-ietf-acme-dns-persist) |
Unlike the other types, dns-persist-01 has no per-challenge token. The subscriber publishes one long-lived TXT record that binds an Issuer Domain Name to their ACME account, and that record authorizes future issuance — so subsequent renewals need no DNS changes at all:
_validation-persist.example.com. IN TXT "ca.example; accounturi=https://acme.example/acct/1"
The record value uses the RFC 8659 issue-value syntax (the CAA record format). The challenge object omits token and instead carries accounturi and issuer-domain-names, telling the client exactly what to publish.
Add policy=wildcard to authorize wildcards and subdomains:
_validation-persist.example.com. IN TXT "ca.example; accounturi=https://acme.example/acct/1; policy=wildcard"
This record authorizes example.com and *.example.com. Letting a record also authorize subdomains beneath it (e.g. one record on example.com covering foo.example.com) is off by default — opt in with challenges.dnspersist01.allow_subdomain_policy: true. An optional persistUntil=<unix-timestamp> parameter stops the record being used for new validations after a chosen time.
Enable it by listing the type and configuring at least one Issuer Domain Name — the type stays inert until you do:
challenges:
enabled: [http-01, dns-persist-01]
dnspersist01:
issuer_domain_names: [ca.example]
See docs/configuration.rst for the full option list.
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.
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+
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/
Full documentation is available in the docs/ folder (Sphinx/reStructuredText, compatible with Read the Docs):
To build the docs locally:
pip install -r docs/requirements.txt
sphinx-build -b html docs docs/_build/html
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 2.1.0 | 20 Jul 2026 | 15 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 18:18 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:bd2222c86f860fd37bb79ff30378482e9fd655d3bf2319efcde013ee4bc5c59d