
September 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Public CAs work for customer-facing HTTPS, but staging clusters, VPN endpoints, and admin panels need a different model. When you Run an Internal Certificate Authority with step-ca, you get a small, auditable PKI that issues short-lived X.509 certificates on demand. PKI and certificate management basics still apply — root trust, intermediates, and revocation — but step-ca wraps them in a single binary with ACME, JWT, and SSH provisioners. This guide walks through a production-minded setup on Ubuntu 24.04 that I would ship on a real client project.
step ca init to create a root and intermediate, start the CA server, configure an ACME provisioner, distribute the root CA to clients, and issue certificates via certbot or the step CLI with automatic renewal.Why should you Run an Internal Certificate Authority with step-ca?
Commercial CAs charge per hostname and enforce rate limits. Internal hostnames like api.staging.internal or grafana.ops.local do not belong on a public trust chain anyway. A private CA gives you full control over validity periods, SAN lists, and revocation policy.
step-ca from Smallstep is a Go-based CA server that speaks ACME v2, so tools like Certbot and Caddy work without custom plugins. It also supports JWT and SSH certificate provisioners for mTLS and short-lived SSH access. Compared to OpenSSL scripts or cfssl, step-ca ships with a coherent CLI (step), sane defaults, and active maintenance.
On legal-tech portals and client portals I have maintained, internal staging environments mirror production TLS behaviour. That catches certificate chain errors before they reach users. step-ca makes that repeatable without manual OpenSSL invocations each sprint.
| Approach | Best for | Automation | Operational cost |
|---|---|---|---|
| Public CA (Let's Encrypt) | Public DNS names | ACME, excellent | Free, rate-limited |
| step-ca internal CA | Private DNS, mTLS, SSH | ACME + JWT + SSH | One VM, low |
| Manual OpenSSL CA | One-off certs | None | High human error |
| Cloud KMS CA (AWS PCA) | Enterprise AWS shops | API-driven | Rs 15,000+/month (~USD 110) |
For teams already running Ubuntu servers with Linux system administration workflows, step-ca fits naturally beside existing nginx and PHP-FPM stacks.
How do you install and initialize step-ca on Ubuntu?
Start on a dedicated VM — not your application server. The CA holds signing keys and should sit on a restricted network segment. Ubuntu 22.04 or 24.04 both work; I prefer 24.04 for current OpenSSL behaviour.
Install the step CLI and step-ca
Download the latest release from the official Smallstep GitHub repository. Pin the version in your deployment notes so upgrades are deliberate.
curl -LO https://dl.smallstep.com/gh-release/certificates/docs-ca-install/v0.27.2/step-ca_linux_amd64.tar.gz
tar -xf step-ca_linux_amd64.tar.gz
sudo install step-ca /usr/local/bin/step-ca
curl -LO https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.27.2/step_linux_amd64.tar.gz
tar -xf step_linux_amd64.tar.gz
sudo install step /usr/local/bin/step Verify both binaries respond before continuing:
step-ca version
step version Initialize the CA hierarchy
The step ca init command creates a root CA, an intermediate CA, default provisioners, and a ca.json configuration file. Use a strong password and store it in your secrets manager — not in shell history.
step ca init \
--deployment-type standalone \
--name "MyOrg Internal CA" \
--dns ca.internal.example.com \
--address :443 \
--provisioner admin@example.com
mkdir -p /etc/step-ca
mv ca.json /etc/step-ca/
mv secrets/ /etc/step-ca/ This produces root_ca.crt, intermediate key material under secrets/, and a JWK provisioner for admin operations. Read root CA vs intermediate CA if the two-tier model is new to you — the intermediate is what step-ca uses daily while the root stays offline.
Run step-ca as a systemd service
Create a dedicated system user and point systemd at your config. Reload PHP-FPM is unrelated here — but the same discipline applies on servers where I run Deployer releases.
sudo useradd --system --home /etc/step-ca --shell /usr/sbin/nologin stepca
sudo chown -R stepca:stepca /etc/step-ca
sudo chmod 600 /etc/step-ca/secrets/* # /etc/systemd/system/step-ca.service
[Unit]
Description=step-ca Internal Certificate Authority
After=network.target
[Service]
User=stepca
Group=stepca
Environment=STEPPATH=/etc/step-ca
ExecStart=/usr/local/bin/step-ca /etc/step-ca/ca.json \
--password-file /etc/step-ca/secrets/password.txt
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target sudo systemctl daemon-reload
sudo systemctl enable --now step-ca
sudo systemctl status step-ca Place the CA behind nginx or Caddy if you want TLS termination on port 443 with a public-facing hostname for the CA itself. The CA API can also listen on an internal port like 8443 if you prefer.
How do you configure ACME and issue certificates with step-ca?
ACME is the highest-value provisioner for most teams. It lets Certbot, acme.sh, Traefik, and Caddy request certificates without custom integration code. The ACME endpoint lives at https://ca.internal.example.com/acme/acme/directory by default.
Enable the ACME provisioner
Edit /etc/step-ca/ca.json and confirm an ACME-type provisioner exists. If you initialized with defaults, add one explicitly:
step ca provisioner add acme --type ACME Restart step-ca after any config change. Then confirm the directory URL responds:
curl -k https://ca.internal.example.com/acme/acme/directory | jq . The response should list newNonce, newOrder, and newAccount endpoints. Cross-check against the ACME RFC 8555 specification if you debug protocol errors.
Issue a certificate with Certbot
Install the root CA on the requesting machine first. Then point Certbot at your private ACME directory:
sudo cp root_ca.crt /usr/local/share/ca-certificates/myorg-internal.crt
sudo update-ca-certificates
sudo certbot certonly --standalone \
--server https://ca.internal.example.com/acme/acme/directory \
--domain staging-api.internal.example.com \
--email ops@example.com \
--agree-tos \
--non-interactive Certbot stores the cert under /etc/letsencrypt/live/ even though the issuer is your internal CA. Wire those paths into nginx the same way you would for a public cert. See install SSL certificates on Ubuntu for nginx virtual-host wiring.
Issue with the step CLI directly
For quick one-off certs or CI pipelines, the native CLI is faster than ACME:
step ca certificate staging.internal.example.com staging.crt staging.key \
--ca-url https://ca.internal.example.com \
--root root_ca.crt Inspect the result with OpenSSL:
openssl x509 -in staging.crt -noout -subject -issuer -dates -ext subjectAltName Understanding fields like subjectAltName and validity windows helps when debugging mTLS failures. Anatomy of an X.509 certificate covers each extension in detail.
How do you distribute trust and automate certificate renewal?
An internal CA is useless if clients reject the chain. Every laptop, CI runner, browser, and service that connects must trust your root — and only your root.
Distribute the root CA certificate
- Export
root_ca.crtfrom the initialization output. - Add it to OS trust stores: macOS Keychain, Windows certmgr, Linux
update-ca-certificates. - Configure language runtimes that maintain separate stores — Java cacerts, Node.js
NODE_EXTRA_CA_CERTS, PythonREQUESTS_CA_BUNDLE. - Document the fingerprint so engineers can verify they imported the correct root.
For automated trust in CI, store the root as a base64-encoded secret and decode at job start. The base64 encoder and decoder on this site helps when preparing those secrets locally.
Automate renewal
Short-lived certificates — 24 hours to 90 days — limit exposure when a key leaks. step-ca defaults favour shorter lifetimes than public CAs. Schedule renewal before expiry:
# Cron on the application server — renew at 02:00 daily
0 2 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx" Pair this with monitoring that alerts when a cert expires in under seven days. I treat cert expiry like disk space — predictable until it is not. Read automate certificate rotation for patterns that apply beyond step-ca.
On Kubernetes, cert-manager supports custom ACME endpoints. Point its ClusterIssuer at your step-ca directory URL and let it handle ingress TLS automatically. That pattern scales better than shell scripts on every node.
What security practices matter when operating step-ca in production?
A compromised CA key is worse than a compromised server key. An attacker can mint arbitrary trusted certificates across your entire infrastructure. Treat the CA VM like a crown jewel.
Protect root and intermediate keys
- Keep the root CA private key offline after signing the intermediate CSR.
- Store intermediate keys with
chmod 600and restrict ownership to thestepcauser. - Use a hardware security module or cloud KMS for intermediate keys at scale.
- Rotate the intermediate every one to two years; keep the root lifespan at 10+ years.
Generate strong passwords for encrypted key files. A password generator helps, but store the result in Vault or your team's secrets manager — never in Git.
Enable revocation and audit logging
step-ca supports CRL and OCSP for certificate revocation. Enable at least one method before production cutover. Certificate revocation: CRL vs OCSP explains the trade-offs.
# ca.json excerpt — enable CRL
"crl": {
"enabled": true,
"expiry": "24h",
"cacheDuration": "6h"
} Ship step-ca logs to your central logging stack. Every issuance event should record the requester, SAN list, and serial number. That audit trail matters for compliance reviews on client portals handling sensitive documents.
Network and access controls
Restrict CA API access to internal networks only. Use UFW or security groups to allow port 443 from application subnets and deny the public internet. Require mTLS for admin provisioner operations in larger teams.
Do not expose the CA admin JWK provisioner to untrusted networks. If an admin key leaks, an attacker can issue any certificate your CA trusts. Separate admin access from ACME issuance paths.
How does step-ca fit Laravel, API, and client portal deployments?
Most of my production work runs Laravel on Ubuntu with nginx terminating TLS. Public sites use Let's Encrypt. Internal staging and service-to-service calls use step-ca.
For a Laravel API behind nginx, terminate TLS at nginx with a step-ca cert. Laravel sees HTTP on localhost and trusts the X-Forwarded-Proto header. For mTLS between microservices, configure nginx ssl_verify_client and pass verified client cert details to PHP via headers.
On portals like Mijar Law Associates, document uploads and payment callbacks depend on valid HTTPS in every environment. Matching TLS behaviour in staging prevents surprises during release week. API development projects with webhook integrations benefit the same way — test callback URLs against real certificate chains locally.
If your application integrates with Nepal government digital signature workflows, internal PKI is separate from NIC-issued DSC tokens. Read Nepal digital signature certificate for web apps for that distinct trust model.
For ongoing CA maintenance — renewals, trust store updates, revocation drills — support and maintenance retainer agreements cover the operational side so your team focuses on features.
Key Takeaways
- Run step-ca on a dedicated VM with root offline and intermediate online for daily signing.
- Enable the ACME provisioner so Certbot, Caddy, and cert-manager integrate without custom code.
- Distribute
root_ca.crtto every client, CI runner, and runtime trust store before issuing certs. - Use short validity periods (30 days or less) with automated renewal cron jobs and expiry monitoring.
- Enable CRL or OCSP, restrict network access, and audit every issuance event.
- Keep public Let's Encrypt certs for customer-facing domains; use step-ca only for internal names.
People Also Ask
Is step-ca free for commercial use?
Yes. step-ca is open source under the Apache 2.0 licence. Smallstep also sells enterprise support and hosted options, but the core CA server and CLI are free to run on your own infrastructure without licence fees.
Can step-ca replace Let's Encrypt for public websites?
No. Browsers do not trust your private root CA. Public websites still need Let's Encrypt or another publicly trusted CA. step-ca is for internal hostnames, mTLS, VPN, and staging environments where you control client trust.
How do you back up a step-ca installation?
Back up the entire /etc/step-ca directory including secrets/, ca.json, and the root CA certificate. Store encrypted backups off-site. Test restoration quarterly — a backup you have never restored is a guess.
Does step-ca work with Docker and Kubernetes?
Yes. Run step-ca in a container with secrets mounted as volumes. On Kubernetes, cert-manager supports custom ACME directory URLs pointing at step-ca. Keep persistent storage for the CA database if you enable revocation tracking.
Deploy your internal CA with confidence
You now have a complete path to Run an Internal Certificate Authority with step-ca: install, initialize, enable ACME, distribute trust, and automate renewal. Start in staging, verify chain trust end to end, then expand to internal services. The official step-ca documentation covers advanced provisioners, RA mode, and HSM integration when you outgrow a single VM.
If you want help hardening PKI for a Laravel portal, API platform, or multi-environment deployment, contact us to plan the rollout. You can also review how we handle production infrastructure on the Notary Nepal portal and explore related reading on SSL/TLS certificates explained and the certificate chain of trust.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

