A self-contained Public Key Infrastructure (PKI) management platform built on step-ca and packaged with Docker Compose.
StepCA-Control provides a complete two-tier Certificate Authority architecture with:
- Root CA and Intermediate CA
- PostgreSQL-backed certificate storage
- CRL publication over HTTP
- ACME and JWK certificate issuance
- Certificate revocation
- Certificate issuance directly from the dashboard
- Read-only certificate inventory and inspection
- Authenticated web dashboard
- Optional nginx HTTP Basic Authentication
- Offline root CA key isolation
StepCA-Control is designed as a reference deployment. Review and adapt certificate validity periods, subjects, network exposure, authentication, and operational procedures before using it in production.
StepCA-Control/
├── .env.example
├── init-ca.sh
├── BUILD/
│ └── Dockerfile
├── volumes/
│ ├── app/
│ │ ├── app.py
│ │ ├── asgi.py
│ │ ├── config.py
│ │ ├── models.py
│ │ ├── services.py
│ │ ├── routes.py
│ │ ├── utils.py
│ │ ├── static/
│ │ └── templates/
│ ├── db/
│ ├── nginx/
│ │ ├── nginx-pki.conf.template
│ │ └── intermediate_ca.crt
│ └── stepca/
│ └── config/
│ └── templates/
│ └── certs/
│ └── x509/
│ ├── leaf.tpl.tmpl
│ └── leaf.tpl
├── secrets/
├── root-ca-offline/
├── docs/
├── docker-compose.yml
├── LICENSE
└── README.md
Generated CA material, passwords, private keys, database files, and other sensitive data are intentionally excluded from version control.
Docker Network
┌──────────────────────────────────────────────────────────────┐
│ │
│ step CLI / ACME Clients │
│ │ │
│ │ HTTPS :443 │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ step-ca │ │
│ │ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ JWK Provisioner │ │ │
│ │ ├───────────────────────┤ │ │
│ │ │ ACME Provisioner │ │ │
│ │ ├───────────────────────┤ │ │
│ │ │ Certificate Authority │ │ │
│ │ │ CRL Endpoint │ │ │
│ │ └───────────────────────┘ │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ PostgreSQL │ │
│ │ Certificate DB │ │
│ └─────────────────────────────┘ │
│ │
│ HTTP :80 │
│ ▲ │
│ │ │
│ ┌──┴──────────────────────────┐ │
│ │ nginx │ │
│ │ │ │
│ │ /intermediate_ca.crt │ │
│ │ /intermediate_ca.crl │ │
│ │ / → StepCA-Control Dashboard│ │
│ └──────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
StepCA-Control comes with two certificate issuance methods.
Designed for manual or scripted certificate issuance.
- Password-protected
- Suitable for CLI and automation
- Default certificate lifetime: 2 years
Designed for automated certificate issuance using standard ACME clients.
- Challenge-based issuance
- Compatible with ACME tooling
- Default certificate lifetime: 90 days
- Docker
- Docker Compose v2
- Bash
- OpenSSL
- A DNS name pointing to the host running StepCA-Control
Example:
pki.example.com
cp .env.example .envEdit .env:
CA_DNS=pki.example.comCA_DNS is the single source of truth for the PKI hostname.
It is automatically used to configure:
step-caca.json- Certificate templates
- CRL and AIA URLs
- nginx
- The StepCA-Control dashboard
- JWT audiences used by the dashboard
Run the initialization script once:
bash init-ca.shThe script is idempotent.
To force a complete regeneration:
bash init-ca.sh --forceThe initialization process creates:
- Root CA
- Intermediate CA
- CA private keys
- CA configuration
- JWK provisioner
- ACME provisioner
- PostgreSQL credentials
- CA key passwords
- Certificate templates
- CRL configuration
The root CA fingerprint is printed during initialization.
Save the fingerprint. It is required to bootstrap trust on clients.
docker compose up -dCheck the running containers:
docker compose psThe Root CA certificate is available at:
volumes/stepca/certs/root_ca.crt
Install this certificate in the trust store of your clients.
StepCA-Control uses a two-tier CA architecture.
Root CA
│
│ signs
▼
Intermediate CA
│
┌─────────┴─────────┐
│ │
Server Certs Client Certs
The Root CA is used only to sign the Intermediate CA.
After initialization:
root-ca-offline/
├── root_ca
├── root_ca.crt
└── root_password
The Root CA private key should be:
- Backed up securely.
- Removed from the online CA host.
- Stored offline or in a secure secrets management system.
The running step-ca instance only requires the Intermediate CA key.
This limits the impact of a compromise of the online CA infrastructure.
docker exec pki \
curl -sk https://localhost:443/healthExpected response:
{"status":"ok"}docker exec pki step ca provisioner listdocker exec pki \
curl -sk https://localhost:443/crl -o /tmp/crl.der
openssl crl \
-inform DER \
-in /tmp/crl.der \
-noout \
-lastupdate \
-nextupdatesource .env
curl -sI http://$CA_DNS/intermediate_ca.crt
curl -s http://$CA_DNS/intermediate_ca.crl \
| openssl crl -inform DER -noout -nextupdateBootstrap trust on a client:
step ca bootstrap \
--ca-url https://$CA_DNS \
--fingerprint <ROOT_FINGERPRINT>Issue a certificate:
step ca certificate \
web01.example.com \
web01.crt \
web01.key \
--provisioner jwk \
--ca-url https://$CA_DNS \
--san web01.example.com \
--password-file volumes/stepca/secrets/provisioner_pwdThe ACME directory is available at:
https://<CA_DNS>/acme/acme/directory
Example:
certbot certonly \
--standalone \
--server https://$CA_DNS/acme/acme/directory \
-d web01.example.comThe client must trust the Root CA before connecting to the private CA.
The StepCA-Control dashboard provides a centralized view of certificates issued by the PKI.
The dashboard displays:
- Certificate subject
- Subject Alternative Names
- Issuer
- Serial number
- Fingerprint
- Validity period
- Certificate status
- Key type
- Provisioner
Supported statuses include:
- Valid
- Expiring
- Expired
- Revoked
Each certificate can be inspected to view:
- Subject DN
- Issuer DN
- SANs
- Serial number
- Fingerprint
- Key algorithm
- Validity dates
- Provisioner
- Current certificate status
The dashboard can issue certificates directly through the step-ca API.
Supported certificate types:
- Server certificates
- Client certificates
Supported key algorithms:
- EC P-256
- EC P-384
- EC P-521
- RSA 2048
- RSA 3072
- RSA 4096
The generated certificate package can include:
- Certificate
- Full certificate chain
- Private key
- PKCS#12 bundle
Private keys and generated certificate packages are not stored server-side after issuance.
The dashboard requires authentication.
On first startup, a default admin user is created with a randomly generated password.
Retrieve it from the logs:
docker compose logs pki-dashboard \
| grep "default dashboard user"Create another user:
docker exec -it pki-dashboard \
flask --app app create-user <username>Passwords are stored as salted password hashes using Werkzeug.
Application authentication should not be considered a replacement for network segmentation or access control.
Certificate revocation is available through the dashboard.
The dashboard communicates with step-ca through its HTTP API using a provisioner-signed token.
No Docker socket is required.
No step subprocess is required for dashboard revocation.
Manual revocation is also possible:
docker exec -it pki \
step ca revoke <serial> \
--ca-url https://$CA_DNS \
--root /home/step/certs/root_ca.crtThe intermediate CA certificate is published at:
http://<CA_DNS>/intermediate_ca.crt
The CRL is published at:
http://<CA_DNS>/intermediate_ca.crl
Certificates issued by StepCA-Control include the relevant distribution points.
The HTTP endpoint must therefore remain available for clients performing certificate validation.
After modifying ca.json:
docker kill -s HUP pkistep certificate inspect web01.crt --shortdocker exec pki-db \
pg_dump -U stepca stepca \
> stepca-$(date +%F).sqldocker exec -it pki \
step ca revoke <serial>Or use the StepCA-Control dashboard.
Sensitive material is stored outside version control.
The following directories contain secrets or generated CA material:
secrets/
volumes/stepca/secrets/
root-ca-offline/
These directories must be:
- Excluded from Git
- Backed up securely
- Protected with restrictive filesystem permissions
Important security recommendations:
- Keep the Root CA offline.
- Use a dedicated provisioner for each automation domain.
- Do not reuse provisioner credentials.
- Restrict access to the dashboard.
- Keep CRL distribution available.
- Protect PostgreSQL credentials.
- Review certificate validity periods before production use.
- Use network segmentation for the CA infrastructure.
- Never commit private keys, passwords, or generated secrets.
The dashboard already requires application-level authentication.
An additional nginx authentication layer can be enabled.
Create an htpasswd file:
htpasswd -c volumes/nginx/htpasswd adminEnable the auth_basic directives in the nginx configuration and mount the file:
volumes:
- ./volumes/nginx/htpasswd:/etc/nginx/htpasswd:roThen restart the stack:
docker compose up -dStop the services while preserving data:
docker compose downRemove the PostgreSQL database:
rm -rf volumes/dbRemove CA material:
rm -rf volumes/stepca
rm -rf secrets
rm -f volumes/nginx/intermediate_ca.crtRemove the offline Root CA vault:
rm -rf root-ca-offlineThese operations are destructive. Always verify your backups before deleting CA material.
If PostgreSQL fails with:
initdb: error: could not change permissions of directory
Operation not permitted
A previous PostgreSQL container may still be running or holding the bind mount.
Run:
docker compose downWait until all containers have stopped, then remove the database directory:
rm -rf volumes/dbFinally restart the stack:
docker compose up -dStepCA-Control is a reference deployment for managing a private PKI based on step-ca.
Before using it in production, review:
- Certificate lifetimes
- CA subjects
- Network exposure
- Authentication
- Backup strategy
- Root CA protection
- Secrets management
- Monitoring and alerting
See LICENSE.
Built with:
step-ca- Docker
- Docker Compose
- PostgreSQL
- nginx
- Flask
- Uvicorn
StepCA-Control — Control your private PKI.



