How to Use step-ca: The Beginner's Guide (First CA, the Bootstrap Lesson, ACME Automation, SSH Certs, and the Trust Rollout)
CodexFuse Team · 2026-11-01T14:15:21.433Z · 13 min read
Learn how to use step-ca: install the step CLI, run step ca init, bootstrap client trust, automate TLS with ACME, issue SSH certificates, and run your own private certificate authority.
What Is step-ca?
step-ca is your own private certificate authority: a single Go binary from Smallstep that issues TLS certificates for your internal services and SSH certificates for your servers, speaking the same ACME protocol that Let's Encrypt uses. Think of it as a tiny, self-hosted Let's Encrypt that only you trust, plus an SSH certificate authority built into the same box.
That mental model — your own private ACME server plus SSH CA in one Go binary — explains every feature this guide covers: the install, the init wizard, the bootstrap that teaches clients to trust you, the ACME automation, the SSH lane, and mTLS. Everything below is one arc from zero to a running private CA.
How Do I Install step-ca and the step CLI?
Two packages exist and they are versioned separately: step-ca (the server, smallstep/certificates, v0.30.2 released 2026-03-18) and the step CLI (the client, v0.31.0 released 2026-09-25). Do not assume the version numbers move together. You need both: the CLI installs, initializes, and talks to the CA.
On macOS, one Homebrew command installs both: brew install step. On Debian/Ubuntu, add the official Smallstep package repository and run apt-get install step-cli step-ca. Windows users get winget install Smallstep.step-ca or the Smallstep Scoop bucket; Fedora, Arch, Alpine, FreeBSD, and Wolfi all ship packages too. If you would rather run it as a container — the most common homelab lane — the official image is smallstep/step-ca (pin it, e.g. smallstep/step-ca:0.30.2), and the Docker beginner guide covers the container habits you will lean on here.
# Debian/Ubuntu install lane
curl -fsSL https://packages.smallstep.com/keys/apt.sh | sudo -E bash -
sudo apt-get update
sudo apt-get install step-cli step-ca
# verify both
step version
step-ca --version
What Does step ca init Actually Do?
step ca init is a wizard that builds your private PKI from scratch: it asks for a PKI name, the DNS names or IP addresses clients will reach the CA on, the listen address (often :443 or :8443), a name for your first provisioner (an email address works), and a password. From those answers it generates a root certificate plus key, an intermediate certificate plus key, ca.json, and defaults.json, and it prints the root fingerprint you will need in the next step.
Two flags matter at init time. Pass --ssh if you want the CA to also issue SSH certificates, and use the password-file lesson: --password-file sets the CA key password separately from --provisioner-password-file, so the provisioner password you type every day is not the crown-jewel CA password. Write both files down, because losing the CA password locks you out of signing.
step ca init \
--name "My Homelab CA" \
--dns "ca.internal" \
--address ":8443" \
--provisioner [email protected] \
--password-file ~/.step/secrets/ca-password.txt \
--provisioner-password-file ~/.step/secrets/provisioner-password.txt \
--ssh
The wizard drops everything under ~/.step: certificates in ~/.step/certs/, secrets in ~/.step/secrets/. Newer versions of the wizard (seen in 2026 homelab installs) ask for a "Deployment Type" and confirm the provisioner password separately — the concepts above have not changed, only the wording.
Why Does step-ca Only Issue 24-Hour Certificates?
Short-lived certificates are the whole philosophy. The official docs have a section literally titled "Why does the certificate only last for ONE DAY!?" and the answer is blunt: the 24-hour default TLS lifetime is rather arbitrary, but it is intentional. A certificate that lives one day has a tiny blast radius if it leaks, and it forces you to automate renewal instead of filing a calendar reminder.
The defaults live in the claims block of ca.json — minTLSCertDuration, maxTLSCertDuration, and defaultTLSCertDuration, all 24 hours out of the box — and you can tune them per provisioner. Because renewals are constant, step-ca renews over mTLS, which means only the client holding the certificate can renew it. Do not plan revocation workflows here: with 24-hour lifetimes the design answer is to let the bad cert expire, not to build CRL infrastructure.
How Do I Use step-ca From Zero to First Certificate?
The full beginner arc is seven steps: install, init, start the CA, bootstrap a client, issue a first leaf certificate, roll trust out to your devices, then turn on automation. Each step below maps to a section of this guide.
- Install the step CLI and step-ca (or pull the container image).
- Run
step ca initto create your root, intermediate, and first provisioner. - Start the CA with
step-ca $(step path)/config/ca.json(or your container's entrypoint). - Bootstrap every client with
step ca bootstrap --ca-url --fingerprintso it knows and trusts your root. - Issue a first leaf certificate with
step ca certificateand prove it works withcurl. - Roll trust out to Linux, macOS, Windows, and Firefox so browsers stop warning.
- Turn on ACME so certificates renew themselves and you never copy a cert by hand.
How Do Clients Trust the CA? The Bootstrap Lesson
The bootstrap lesson is the single most important idea in private PKI: your CA is not trusted by anything until you teach each client to trust it. The command is step ca bootstrap --ca-url https://ca.internal:8443 --fingerprint <root-fingerprint>. It downloads the root certificate, verifies it against the fingerprint you pass, and writes ~/.step/config/defaults.json so every later step command knows where your CA lives.
Get the fingerprint on the CA host with step certificate fingerprint $(step path)/certs/root_ca.crt — the init wizard printed it too, but you will have lost it by now. A shortcut exists for quick setups: step ca bootstrap --ca-url ... --fingerprint ... --install installs the root straight into the system trust store in one step. Run bootstrap on every machine that will request certificates or talk to services with your certs, including the CA host itself.
How Do I Roll Out Trust to Linux, macOS, Windows, and Firefox?
Once the root certificate exists, each operating system needs to learn it. The CLI shortcut is step certificate install $(step path)/certs/root_ca.crt, which puts the root into the system's default trust store; add --all and it also reaches Firefox's and Java's stores, which do not follow the operating system.
The manual per-OS commands are worth knowing because servers do not always have the CLI handy. On Debian/Ubuntu, copy the root to /usr/local/share/ca-certificates/ and run sudo update-ca-certificates. On macOS, run sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root_ca.crt. On Windows, run certutil -addstore -f "Root" root_ca.crt. Firefox keeps its own certificate store apart from the OS, so import the root through Firefox settings, or use step certificate install --all once and be done.
How Does ACME Automation Work With step-ca?
ACME is what makes short-lived certificates painless, and step-ca speaks it natively. Add an ACME provisioner with step ca provisioner add acme --type ACME and restart the CA. The directory URL follows the pattern https://ca.internal:8443/acme/acme/directory — that one URL is all your clients need.
Point real ACME clients at it. With certbot: certbot certonly -n --standalone -d app.internal --server https://ca.internal:8443/acme/acme/directory. With Caddy, add a global block with acme_ca https://ca.internal:8443/acme/acme/directory plus acme_ca_root pointing at your root certificate, and Caddy fetches and renews private certs by itself — the Caddy beginner guide walks through the reverse-proxy side if you have not set it up. In Kubernetes, cert-manager's ClusterIssuer accepts the same directory URL as its server field.
The never-copy-certs-by-hand lesson applies here: the moment a human is scp-ing certificates around, the automation is broken. If your renewal loop runs daily and 24 hours feels tight, tune the provisioner with step ca provisioner update acme --x509-default-dur=720h --x509-max-dur=720h — 30-day certs with automated renewal keep the spirit without the pager.
How Do SSH Certificates Replace Authorized Keys?
SSH certificates kill the authorized_keys sprawl: instead of copying public keys into every ~/.ssh/authorized_keys file, servers trust your CA and users carry short-lived certificates. With --ssh at init (or by enabling SSH on an existing provisioner with step ca provisioner update <name> --ssh), step-ca creates two SSH CA key pairs — a host CA that signs host certificates and a user CA that signs user certificates.
On each server, export the user CA's public key with step ssh config --roots > /etc/ssh/ssh_user_key.pub, add TrustedUserCAKeys /etc/ssh/ssh_user_key.pub to /etc/ssh/sshd_config, and restart sshd. Users then get certificates with step ssh certificate [email protected] id_ecdsa — the cert lands in the SSH agent, not on disk, and lasts 16 hours by default. Note the corrected form: the SSH capability is enabled via init --ssh or provisioner update --ssh, not a provisioner add ... --ssh command — that variant does not exist.
How Does mTLS Work With step-ca?
Mutual TLS is where a private CA earns its keep: both the client and the server present certificates, so your internal API knows exactly which service or user is calling. Issue a client certificate with step ca certificate [email protected] alice.crt alice.key, then call your service with curl --cacert root_ca.crt --cert alice.crt --key alice.key https://api.internal/endpoint.
On the server side, configure client verification — nginx, for example, wants ssl_verify_client on with your root as the trusted chain. This is the same identity model zero-trust proxies like Pomerium consume: a request carrying a verified client certificate is a strongly authenticated principal, and the Pomerium beginner guide shows the proxy lane that builds on it.
step-ca vs Let's Encrypt: Which Should I Use?
Use both, for different jobs. The line to draw once: Let's Encrypt is for public-facing services with public DNS — free, automatic, trusted by every browser on earth. step-ca is for internal services without public DNS, for mTLS, for device certificates, and for SSH certificates. Its root is self-signed and not automatically trusted by anyone, so it cannot and should not issue publicly trusted certificates.
| step-ca | Let's Encrypt | |
|---|---|---|
| Trust | Private — only hosts you bootstrap | Public — every browser trusts it |
| Best for | Homelab, internal APIs, mTLS, SSH certs | Public websites and APIs |
| Internal hostnames | Yes — any name you want | No — needs public DNS validation |
| Default lifetime | 24 hours | 90 days |
| SSH certificates | Built in | Not offered |
| Protocol | ACME plus its own API | ACME |
| You operate | The CA, backups, trust rollout | Nothing — it is a service |
step-ca vs Vault PKI vs EJBCA: Which Lane Am I In?
Three products, three lanes. step-ca is the lightweight, PKI-focused lane: a single Go binary, simple config, built-in ACME and SSH certificates — the right pick when PKI is the job and you do not already run HashiCorp Vault. Vault's PKI secrets engine is the enterprise lane: PKI as one engine inside a broader secrets-management platform, which wins when secret-store workflows and existing Vault integration matter. EJBCA is the Java-based enterprise lane: a full-featured PKI with a web GUI, OCSP and CRL management, multi-CA support, and broad enrollment protocols (ACME, EST, CMP, SCEP), which wins when compliance and protocol breadth are the requirements.
| step-ca | Vault PKI | EJBCA | |
|---|---|---|---|
| Footprint | Single Go binary | Vault cluster | Java application |
| License | Apache-2.0 | Business Source License | LGPL / commercial |
| ACME server | Yes, built in | Via plugin work | Yes |
| SSH certificates | Built in | Separate SSH engine | No |
| Admin surface | CLI-first | CLI and UI | Web GUI |
| Sweet spot | Homelab and small-team private PKI | Teams already on Vault | Enterprise compliance PKI |
How Do I Deploy step-ca With Docker?
The Docker lane is the shortest path to a running CA. The official image reads its init answers from environment variables, so a compose file captures the whole setup declaratively. This assumes a host running Docker — see the Docker beginner guide for the engine itself — and you will want a VPS hosting plan with a stable address if the CA serves more than one machine.
services:
step-ca:
image: smallstep/step-ca:0.30.2
container_name: step-ca
environment:
- DOCKER_STEPCA_INIT_NAME=My Homelab CA
- DOCKER_STEPCA_INIT_DNS_NAMES=ca.internal
- DOCKER_STEPCA_INIT_ADDRESS=:8443
- [email protected]
- DOCKER_STEPCA_INIT_PASSWORD_FILE=/home/step/secrets/password
- DOCKER_STEPCA_INIT_SSH=true
volumes:
- step-ca-data:/home/step
- ./secrets:/home/step/secrets:ro
ports:
- "8443:8443"
volumes:
step-ca-data:
The volume matters: /home/step holds your root key, so back up that volume like your infrastructure depends on it — because it does. Clients then bootstrap against the container's published address exactly as they would a binary install.
What Backs Up the Root Key?
The root private key is the crown jewel: anyone holding it can issue certificates your whole fleet trusts. The one lesson to carry: create the root on a genuinely offline machine, sign an online intermediate with it, move the root key to cold storage — an encrypted USB stick in a safe, a hardware key, even a printed paper backup — and remove it from the running CA host. A different SSH session on the same box is not an offline root.
In practice that means copying secrets/root_ca_key (and its password) to your cold-storage medium, verifying you can read it back, and then deleting it from the CA. The intermediate keeps signing day to day; the root only comes out for intermediate rotation, which is rare by design.
What Are the 7 Beginner Mistakes With step-ca?
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Skipping bootstrap on some clients | Half your fleet gets TLS errors and you blame the CA | Bootstrap every client — CA host included — before issuing |
| Losing the CA password | You can never sign again with that root | Separate password files at init; store them with the root backup |
| Leaving the root key on the CA host | One compromised host ends the whole PKI | Move it to offline cold storage after signing the intermediate |
| Ignoring renewal automation | 24-hour certs expire and things break at 2 a.m. | ACME provisioner plus certbot/Caddy/cert-manager from day one |
| Expecting browsers to trust you automatically | Chrome and curl both distrust unknown roots | Roll the root into every trust store, Firefox's included |
| Planning CRL-based revocation | The product is built around short lifetimes, not lists | Let bad certs expire; shorten lifetimes if worried |
| Using step-ca for public websites | Visitors will never trust your self-signed root | Let's Encrypt for public names, step-ca for internal ones |
Sources
Every version, flag, and behavior above was verified against sources fetched on 2026-11-01: the Smallstep step-ca getting-started guide (smallstep.com/docs/step-ca/getting-started), the official installation reference (smallstep.com/docs/step-ca/installation), the certificate-authority operations guide including "Why does the certificate only last for ONE DAY!?" (smallstep.com/docs/step-ca/basic-certificate-authority-operations), the provisioners reference (smallstep.com/docs/step-ca/provisioners), the ACME protocol tutorial (smallstep.com/docs/tutorials/acme-protocol-acme-clients), the SSH how-it-works page (smallstep.com/docs/ssh/how-it-works), the step-ca v0.30.2 changelog and step CLI v0.31.0 changelog (github.com/smallstep/certificates, github.com/smallstep/cli), the project README (Apache-2.0 license, private ACME server), the StackHarbor internal-ACME walkthrough (stackharbor.com), the Amires Lampanah private-PKI and internal-CA tutorials (amireslampanah.com), the CheapWindowsVPS and 4sysops client guides, the LostInTransit homelab install (lostintransit.se), the Lumenyx Docker and SSH-certificate guides (medium.com), the CopyProgramming 2026 intranet-CA guide (copyprogramming.com), the Keyfactor EJBCA introduction docs (docs.keyfactor.com), and the empirical CA study listing Smallstep at roughly 7,000 stars (mmotwani.com).
Frequently Asked Questions
Is step-ca free for commercial use?
Yes. step-ca is Apache-2.0 licensed open source, so you can run it commercially, modify it, and redistribute it. Smallstep also sells managed and enterprise offerings, but the CA itself costs nothing beyond the machine it runs on.
Can step-ca replace Let's Encrypt for my public website?
No. step-ca's root is self-signed and trusted only by clients you bootstrap, so public visitors would see certificate warnings. Keep Let's Encrypt for public hostnames and use step-ca for internal services, mTLS, and SSH certificates.
How long do step-ca certificates last by default?
TLS certificates default to 24 hours, SSH certificates to 16 hours. Both are configurable per provisioner — for example, raising the ACME provisioner's default and maximum durations — but the short-lived philosophy is the product's core design, not a limitation to work around.
Does step-ca support ACME clients like certbot and Caddy?
Yes. Add an ACME provisioner, then point certbot at the directory URL with --server, set Caddy's global acme_ca and acme_ca_root, or use the directory URL as a cert-manager ClusterIssuer server. Traefik, acme.sh, and win-acme work the same way.
What happens if I lose the step-ca root private key?
You lose the ability to sign new intermediates and to rotate trust — every client would need to be re-bootstrapped to a brand-new CA. That is why the root key goes to offline cold storage immediately after the intermediate is signed, with the password stored alongside it.