Step CA Private Certificate Authority

Step CA (smallstep/certificates) is a modern, open-source private certificate authority (PKI) system providing automated certificate lifecycle management, ACME protocol support, and cloud-native deployment. This guide covers installation, CA initialization, ACME server configuration, provisioners, and certificate templates.

Table of Contents

Prerequisites

Before setting up Step CA, ensure you have:

  • Linux system (Ubuntu 20.04+, CentOS 8+)
  • Root or sudo access
  • At least 2GB RAM
  • 10GB disk space
  • Internet connectivity
  • Basic PKI knowledge

Understanding Step CA

Step CA provides:

  1. Private PKI: Self-hosted certificate authority
  2. ACME Support: Automated certificate management (like Let's Encrypt but internal)
  3. Zero-Trust: Certificate-based authentication for microservices
  4. Provisioners: Multiple authentication methods for certificate requests
  5. Templates: Custom certificate extensions and SANs

Typical Step CA workflow:

Client Application
       |
       +-- Request certificate via ACME
       |        |
       +-- Authenticate with provisioner
       |        |
       +-- Receive certificate
       |
    Use certificate for TLS

Installing Step CA

Install Step CA from official repositories.

Install step tools (CLI):

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y curl

curl -sL https://dl.step.sm/cli/apt-key.asc | sudo apt-key add -
echo "deb https://dl.step.sm/cli/apt focal main" | sudo tee /etc/apt/sources.list.d/step.list
sudo apt-get update
sudo apt-get install -y step-cli

# Verify
step --version

Install Step CA server:

# Ubuntu/Debian
echo "deb https://dl.step.sm/ca/apt focal main" | sudo tee /etc/apt/sources.list.d/step-ca.list
sudo apt-get update
sudo apt-get install -y step-ca

# Verify
step-ca --version

For CentOS/RHEL:

curl -sL https://dl.step.sm/ca/rpm-key.asc | sudo rpm --import -
echo -e "[stepca]\nname=Step CA Releases\nbaseurl=https://dl.step.sm/ca/rpm\nenabled=1\ngpgcheck=1\ngpgkey=https://dl.step.sm/ca/rpm-key.asc" | sudo tee /etc/yum.repos.d/step.repo
sudo yum install -y step-ca

Initializing a CA

Initialize a new certificate authority.

Create CA user and directories:

sudo useradd -r -s /bin/false step
sudo mkdir -p /etc/step-ca
sudo mkdir -p /var/lib/step-ca
sudo chown -R step:step /etc/step-ca /var/lib/step-ca
sudo chmod 700 /etc/step-ca /var/lib/step-ca

Initialize CA as step user:

sudo -u step step-ca init \
  --name "Internal CA" \
  --dns localhost,127.0.0.1,ca.example.com \
  --address 0.0.0.0:9000 \
  --provisioner admin \
  --password-file <(openssl rand -base64 32) \
  --path /etc/step-ca

This creates:

  • /etc/step-ca/certs/root_ca.crt: Root CA certificate
  • /etc/step-ca/secrets/root_ca_key: Root CA private key
  • /etc/step-ca/config/ca.json: CA configuration
  • /etc/step-ca/config/defaults.json: Client defaults

Verify installation:

ls -la /etc/step-ca/
cat /etc/step-ca/config/ca.json

ACME Server Configuration

Enable ACME protocol for automated certificate issuance.

Edit CA configuration:

sudo nano /etc/step-ca/config/ca.json

Default configuration includes ACME. Example section:

{
  "root": "/etc/step-ca/certs/root_ca.crt",
  "crt": "/etc/step-ca/certs/intermediate_ca.crt",
  "key": "/etc/step-ca/secrets/intermediate_ca_key",
  "db": {
    "type": "badger",
    "dataSource": "/var/lib/step-ca/db"
  },
  "authority": {
    "provisioners": [
      {
        "type": "ACME",
        "name": "acme"
      },
      {
        "type": "JWK",
        "name": "admin",
        "key": {
          "use": "sig",
          "kty": "EC",
          "kid": "...",
          "crv": "P-256",
          "x": "...",
          "y": "..."
        }
      }
    ]
  },
  "tls": {
    "cipherSuites": [
      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
    ],
    "minVersion": 1.2,
    "maxVersion": 1.3
  }
}

Start Step CA:

# Test run
sudo systemctl start step-ca

# Enable for automatic startup
sudo systemctl enable step-ca

# Check status
sudo systemctl status step-ca

Verify ACME endpoint:

curl http://localhost:9000/acme/acme/directory

Provisioners and Policies

Provisioners control how certificates can be requested.

JWK Provisioner: Uses JSON Web Key for authentication

{
  "type": "JWK",
  "name": "admin",
  "key": { /* JWK data */ }
}

OIDC Provisioner: Uses OpenID Connect for single sign-on

{
  "type": "OIDC",
  "name": "google",
  "clientID": "client-id.apps.googleusercontent.com",
  "clientSecret": "client-secret",
  "configuration": {
    "issuer": "https://accounts.google.com",
    "authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
    "tokenEndpoint": "https://oauth2.googleapis.com/token",
    "userInfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo",
    "jwksUri": "https://www.googleapis.com/oauth2/v3/certs"
  },
  "admins": ["[email protected]"],
  "users": ["[email protected]"]
}

Kubernetes Provisioner: Authenticate via Kubernetes ServiceAccount

{
  "type": "K8sSA",
  "name": "kubernetes",
  "publicKeys": ["-----BEGIN PUBLIC KEY-----..."],
  "admins": ["system:masters"]
}

ACME Provisioner: For automated certificate renewal (built-in)

Add provisioner via API:

# Using step CLI
step ca provisioner add my-provisioner \
  --type=JWK \
  --public-key /path/to/key.pub \
  --private-key /path/to/key.priv

View provisioners:

step ca provisioner list

Certificate Templates

Define custom certificate fields and extensions.

Template example in ca.json:

{
  "templates": {
    "x509": {
      "profile": "leaf",
      "encoding": "PEM",
      "issuerTemplate": "{{ .Issuer }}",
      "subjectTemplate": "{{ .Subject }}",
      "durationTemplate": "{{ .Insecure.Duration }}",
      "serialNumberTemplate": "{{ .SerialNumber }}",
      "keyUsageTemplate": "{{ .KeyUsage }}",
      "extKeyUsageTemplate": "{{ .ExtKeyUsage }}",
      "sanTemplate": "{{ .SANs }}",
      "subjectAltNameTemplate": "{{ .SANs }}",
      "subjectKeyIdTemplate": "{{ .SubjectKeyID }}",
      "authorityKeyIdTemplate": "{{ .AuthorityKeyID }}",
      "basicConstraintsTemplate": "{{ .BasicConstraints }}"
    }
  }
}

Custom certificate request with template:

step ca certificate \
  --template=/path/to/template.json \
  --provisioner=acme \
  example.com \
  cert.crt \
  cert.key

Client Configuration

Configure clients to use Step CA with ACME.

Export Step CA certificate:

step ca root > /tmp/root.crt
cat /tmp/root.crt

Certbot with Step CA ACME:

# Configure certbot for Step CA
sudo certbot certonly \
  --server http://ca.example.com:9000/acme/acme/directory \
  --authenticator standalone \
  -d example.com \
  --email [email protected] \
  --agree-tos \
  --no-eff-email

Step CLI for certificate requests:

# Initialize step CLI configuration
step ca bootstrap \
  --ca-url http://ca.example.com:9000 \
  --fingerprint $(step certificate fingerprint root_ca.crt)

# Request certificate
step ca certificate example.com cert.crt cert.key

# Renew certificate
step ca renew cert.crt cert.key

Automated renewal script:

#!/bin/bash
# /usr/local/bin/step-renew.sh

DOMAIN=$1
CERT_FILE=${2:-${DOMAIN}.crt}
KEY_FILE=${3:-${DOMAIN}.key}

# Renew if expires within 30 days
step ca renew \
  --expires-in 720h \
  "${CERT_FILE}" "${KEY_FILE}"

# Reload services
systemctl reload nginx

Add to crontab:

# Renew daily
0 2 * * * /usr/local/bin/step-renew.sh example.com /etc/ssl/certs/example.com.crt /etc/ssl/private/example.com.key

Monitoring and Management

Monitor Step CA health and certificate operations.

Check CA status:

step ca health

View CA certificates:

step ca certificate inspect /etc/step-ca/certs/root_ca.crt
step ca certificate inspect /etc/step-ca/certs/intermediate_ca.crt

Monitor Step CA logs:

sudo journalctl -u step-ca -f

Check certificate database:

# List issued certificates
step-ca-admin db list-certs

# Revoke a certificate
step-ca-admin db revoke --serial=<serial>

Configure audit logging:

{
  "auditLog": {
    "format": "json",
    "events": ["write"],
    "disable": false
  }
}

View audit events:

tail -f /var/lib/step-ca/audit.log | jq .

Troubleshooting

Common Step CA issues:

ACME endpoint not accessible:

# Check if CA is running
sudo systemctl status step-ca

# Test ACME directory
curl http://localhost:9000/acme/acme/directory

# Check firewall
sudo ufw allow 9000

Certificate request failures:

# Test with step CLI
step ca certificate test.example.com test.crt test.key \
  --provisioner=admin

# Check provisioner configuration
step ca provisioner list

# View detailed error
step ca certificate test.example.com test.crt test.key \
  --provisioner=admin \
  --debug

Provisioner authentication issues:

# Reset admin provisioner
step-ca-admin provisioner update admin \
  --private-key=/path/to/key

# List active provisioners
step ca provisioner list

# Test OIDC provisioner
curl -X POST http://localhost:9000/acme/acme/new-account

Database corruption:

# Backup database
sudo cp -r /var/lib/step-ca/db /var/lib/step-ca/db.backup

# Rebuild database (if needed)
sudo systemctl stop step-ca
sudo rm -rf /var/lib/step-ca/db
sudo systemctl start step-ca

Conclusion

Step CA provides a modern, cloud-native approach to managing internal certificates. This guide covered installation, CA initialization, ACME server setup, provisioners for flexible authentication, and certificate templates for custom requirements. For production deployments, secure the CA private key, implement robust backup strategies, configure audit logging, use multiple provisioners for different use cases, and establish automated certificate renewal. Step CA excels for zero-trust architectures, microservices PKI, and organizations requiring fine-grained certificate control without relying on external CAs.