Deploying Step CA: Your Own Certificate Authority for Internal Services

Smallstep step-ca is a CA server for automated certificate issuance in internal environments (homelabs, labs, DevOps workflows). The common model is to run an online Intermediate CA for day-to-day signing, while keeping the Root CA private key offline (or at least tightly protected). This post uses a bootstrap approach (Root + Intermediate initialized on the same host) for simplicity. You can move the Root key offline later.

This post is only about standing up the CA server (step-ca) on Ubuntu 24.04 and running it securely under systemd on port 443. It does not cover issuance workflows such as ACME, OIDC/JWK provisioners, device enrollment, templates, or renewal/rotation.

Security note: clients only need the Root CA certificate (public) as a trust anchor. Never distribute any CA private keys. If you follow the bootstrap flow here, the Root private key is created on the CA host. Treat it as highly sensitive, and consider moving it offline later.


What you’ll build

A private PKI with:

  • Root CA private key (keep it offline or tightly protected)
  • Intermediate CA (online, used for daily issuance)
  • step-ca listening on :443 (mTLS-friendly), managed by systemd

This article focuses on standing up the CA service. Issuing server/client certs and device templates are intentionally out of scope.


Prerequisites

  • Ubuntu (24.04 recommended)
  • DNS: stepca.maksonlee.com → your CA host
  • Shell access with sudo

  1. Install step-ca and step-cli
sudo apt-get update && sudo apt-get install -y --no-install-recommends curl vim gpg ca-certificates

sudo curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o /etc/apt/trusted.gpg.d/smallstep.asc
echo 'deb [signed-by=/etc/apt/trusted.gpg.d/smallstep.asc] https://packages.smallstep.com/stable/debian debs main' \
  | sudo tee /etc/apt/sources.list.d/smallstep.list

sudo apt-get update && sudo apt-get -y install step-cli step-ca

  1. Initialize the CA (Root + Intermediate)

Run the initializer and answer as below:

step ca init
  • PKI mode: standalone
  • Name: Maksonlee.com Root CA
  • DNS name: stepca.maksonlee.com
  • Address: :443
  • Provisioner name: admin
  • Password: changeme (use a strong one in real life)

step ca init creates Root/Intermediate certs+keys under ~/.step/ by default:

  • certs/root_ca.crt, secrets/root_ca_key
  • certs/intermediate_ca.crt, secrets/intermediate_ca_key

Store the CA password in a file for the service:

echo "changeme" > ~/.step/password.txt

  1. Move PKI to /etc/step-ca and set permissions
# Allow binding to port 443 (CAP_NET_BIND_SERVICE)
sudo setcap CAP_NET_BIND_SERVICE=+eip "$(command -v step-ca)"

# Create service user and path
sudo useradd --user-group --system --home /etc/step-ca --shell /bin/false step
sudo mkdir -p /etc/step-ca

# Move initialized PKI from ~/.step to /etc/step-ca
sudo mv "$(step path)"/* /etc/step-ca

# Update paths in config
sudo sed -i "s|$HOME/.step|/etc/step-ca|g" /etc/step-ca/config/ca.json

# Ownership
sudo chown -R step:step /etc/step-ca

  1. Create a hardened systemd unit
sudo tee /etc/systemd/system/step-ca.service >/dev/null <<'UNIT'
[Unit]
Description=step-ca service
Documentation=https://smallstep.com/docs/step-ca
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=30
StartLimitBurst=3
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
ConditionFileNotEmpty=/etc/step-ca/password.txt

[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
WorkingDirectory=/etc/step-ca
ExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt
ExecReload=/bin/kill --signal HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=30
StartLimitBurst=3

; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes

; Sandboxing
ProtectSystem=full
ProtectHome=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
PrivateTmp=true
PrivateDevices=true
ProtectClock=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
ReadWriteDirectories=/etc/step-ca/db

[Install]
WantedBy=multi-user.target
UNIT

Start and enable:

sudo systemctl daemon-reload
sudo systemctl enable --now step-ca
sudo systemctl status step-ca
sudo journalctl -u step-ca -f

Next

  • Export the Root CA certificate to devices/clients that must trust your private PKI.
  • When you’re ready, follow issuance workflow (server certs, client certs, IoT templates/provisioners) in a separate guide.

Note: This guide does not enable SSH certificate issuance. If you want SSH short-lived certificates, you must initialize with --ssh or add the ssh section to ca.json later.

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top