step-ca Internal Certificate Authority for mTLS and Service TLS
- Author :Liam K.
- Date :July 03, 2026
- Time :27 minutes
Every TLS connection depends on a Certificate Authority (CA) to vouch for identity. Public CAs like Let's Encrypt work for internet-facing services, but internal microservices, gRPC APIs, database connections, and admin tools need certificates signed by a private CA your infrastructure trusts. step-ca is a production-grade, open-source CA that issues certificates via CLI, ACME protocol, and automated provisioners.
This guide initializes a step-ca instance on Linux, issues certificates for internal services, configures mTLS between two applications, and distributes the root CA certificate to clients. The result is encrypted, authenticated communication across your private network without depending on public certificate authorities for internal traffic.
When You Need a Private CA
- mTLS between microservices on a private network
- TLS for internal APIs not exposed to the public internet
- Short-lived certificates with automated rotation
- Development and staging environments matching production TLS patterns
- Service mesh sidecar certificate provisioning
Step 1: Install step-ca and step-cli
curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/smallstep.asc
echo 'deb [signed-by=/etc/apt/keyrings/smallstep.asc] https://packages.smallstep.com/stable/debian debs main' \
| sudo tee /etc/apt/sources.list.d/smallstep.list
sudo apt update
sudo apt install -y step-cli step-caStep 2: Initialize the Certificate Authority
sudo mkdir -p /opt/step-ca
cd /opt/step-ca
sudo step ca init \
--name "Internal CA" \
--dns ca.example.com \
--address :9000 \
--provisioner admin@example.com
# This creates:
[...]Step 3: Create step-ca Configuration and Start Service
sudo tee /etc/systemd/system/step-ca.service >/dev/null <<'EOF'
[Unit]
Description=step-ca Certificate Authority
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/step-ca
[...]Step 4: Distribute Root CA to Clients
Every machine that needs to trust certificates issued by your CA must have the root certificate installed in its trust store.
# On Linux clients:
sudo cp /opt/step-ca/certs/root_ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
# On macOS:
# sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root_ca.crt
# Verify trust:
step certificate inspect /opt/step-ca/certs/root_ca.crtStep 5: Issue a Certificate for a Service
# Bootstrap your CLI to the CA:
step ca bootstrap --ca-url https://ca.example.com:9000 --fingerprint FINGERPRINT_FROM_CA
# Issue a certificate:
step ca certificate api.internal.example.com api.crt api.key \
--provisioner admin@example.com \
--not-after 720h
# Verify:
step certificate inspect api.crtStep 6: Configure Nginx with Internal TLS
server {
listen 443 ssl;
server_name api.internal.example.com;
ssl_certificate /etc/ssl/internal/api.crt;
ssl_certificate_key /etc/ssl/internal/api.key;
ssl_client_certificate /etc/ssl/internal/root_ca.crt;
ssl_verify_client optional;
location / {
[...]Step 7: Enable ACME for Automated Renewal
step-ca supports the ACME protocol (RFC 8555). Services like Caddy and Certbot can request certificates from your internal CA the same way they request from Let's Encrypt — enabling fully automated short-lived certificate rotation.
# Add ACME provisioner to ca.json:
# "authority": {
# "provisioners": [
# { "type": "ACME", "name": "acme" }
# ]
# }
# Caddy with internal ACME:
# {
[...]Step 8: Automate Certificate Renewal
# step-ca certificates expire — automate renewal:
sudo tee /etc/cron.daily/renew-internal-certs >/dev/null <<'EOF'
#!/bin/bash
step ca renew api.crt api.key --provisioner admin@example.com --force
systemctl reload nginx
EOF
sudo chmod +x /etc/cron.daily/renew-internal-certsmTLS Between Two Services
Mutual TLS requires both client and server to present certificates. The server verifies the client certificate against the root CA, and the client verifies the server certificate — both sides authenticate each other.
# Issue client certificate:
step ca certificate client-app.internal client.crt client.key \
--provisioner admin@example.com --not-after 720h
# Server requires client cert (Nginx):
# ssl_verify_client on;
# Client presents cert (curl test):
curl --cert client.crt --key client.key \
--cacert root_ca.crt https://api.internal.example.com/healthProduction Checklist
- Back up
/opt/step-ca/secretsoffline — losing the CA key invalidates all issued certs. - Use short certificate lifetimes (30–90 days) with automated renewal.
- Restrict step-ca API access to internal network only.
- Document certificate revocation procedure.
- Monitor certificate expiry dates — alert 14 days before expiration.
"A private CA is only valuable when every client trusts the root, every cert auto-renews, and the CA key is backed up like the crown jewels."
Technical Author

System administrator and technical writer specializing in server infrastructure, security and deployment. Creating comprehensive guides to help you master server administration.