Private CA
    ACME

    Deploy step-ca on a VPS

    Run Smallstep's step-ca, a private ACME-compatible certificate authority, on a RamNode VPS to issue internal TLS certificates for your services.

    step-ca (Smallstep) is a private, ACME-compatible certificate authority — useful for issuing internal TLS certs to services like Warpgate, Coroot, internal APIs, etc.

    Assumptions: Ubuntu 24.04 LTS RamNode VPS, a dedicated internal hostname (e.g. ca.internal.example.com), root or sudo access.


    1. Prerequisites

    shell
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl ufw

    Create a dedicated non-root user to run the CA:

    shell
    sudo useradd -r -m -d /etc/step-ca -s /usr/sbin/nologin step

    2. Install step CLI and step-ca

    shell
    curl -fsSL https://dl.smallstep.com/gh-release/cli/gh-release-header/latest/step-cli_amd64.deb -o step-cli.deb
    curl -fsSL https://dl.smallstep.com/gh-release/certificates/gh-release-header/latest/step-ca_amd64.deb -o step-ca.deb
    
    sudo dpkg -i step-cli.deb
    sudo dpkg -i step-ca.deb

    Verify:

    shell
    step version
    step-ca version

    (If the download URLs above have changed, grab the latest .deb links from https://github.com/smallstep/cli/releases and https://github.com/smallstep/certificates/releases.)


    3. Initialize the CA

    Run as the step user so file ownership/permissions are correct:

    shell
    sudo -u step step ca init \
      --name "Example Internal CA" \
      --dns "ca.internal.example.com" \
      --address ":9000" \
      --provisioner "admin@example.com" \
      --password-file /dev/stdin <<< "changeme-strong-password"

    This creates /etc/step-ca (or ~/.step for the step user, typically /etc/step-ca/.step) containing:

    • config/ca.json — main CA config
    • config/defaults.json
    • certs/root_ca.crt, certs/intermediate_ca.crt
    • secrets/root_ca_key, secrets/intermediate_ca_key (encrypted with the password you set)

    Store the CA password somewhere safe (a password manager or secrets vault) — you'll need it any time the CA process restarts, unless you configure a password file (see below).

    To avoid typing the password on every restart, save it to a root-only file:

    shell
    sudo bash -c 'echo "changeme-strong-password" > /etc/step-ca/password.txt'
    sudo chown step:step /etc/step-ca/password.txt
    sudo chmod 600 /etc/step-ca/password.txt

    This lets other services (Caddy, nginx with acme.sh, Warpgate, etc.) request certs automatically:

    shell
    sudo -u step step ca provisioner add acme --type ACME

    Restart the CA after adding provisioners (see systemd setup below).


    5. Run step-ca as a systemd service

    shell
    sudo tee /etc/systemd/system/step-ca.service > /dev/null <<'EOF'
    [Unit]
    Description=step-ca certificate authority
    After=network.target
    
    [Service]
    Type=simple
    User=step
    Group=step
    ExecStart=/usr/bin/step-ca /etc/step-ca/config/ca.json --password-file /etc/step-ca/password.txt
    Restart=on-failure
    RestartSec=5
    LimitNOFILE=65536
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    sudo systemctl daemon-reload
    sudo systemctl enable --now step-ca
    sudo systemctl status step-ca

    Adjust the ExecStart path if step ca init placed config under /home/step/.step instead of /etc/step-ca — check with sudo -u step step path.


    6. Firewall

    shell
    sudo ufw allow OpenSSH
    sudo ufw allow 9000/tcp    # step-ca HTTPS API/ACME port
    sudo ufw enable

    If the CA should only be reachable from your internal network/VPN (recommended), restrict with ufw allow from <trusted-subnet> to any port 9000 instead of a blanket allow.


    7. Distribute the root certificate to clients

    On any machine that needs to trust certs issued by this CA:

    shell
    step ca bootstrap --ca-url https://ca.internal.example.com:9000 --fingerprint <root-ca-fingerprint>

    Get the fingerprint from the CA host:

    shell
    sudo -u step step certificate fingerprint /etc/step-ca/certs/root_ca.crt

    To trust it system-wide on a Debian/Ubuntu client:

    shell
    sudo cp $(step path)/certs/root_ca.crt /usr/local/share/ca-certificates/internal-root-ca.crt
    sudo update-ca-certificates

    8. Issue a certificate for another service (example: Warpgate or Coroot's reverse proxy)

    shell
    step ca certificate "warpgate.internal.example.com" warpgate.crt warpgate.key \
      --ca-url https://ca.internal.example.com:9000 \
      --root root_ca.crt

    For automatic renewal, step-cli has a step ca renew --daemon mode, or use the ACME provisioner with a client like acme.sh/Caddy pointed at https://ca.internal.example.com:9000/acme/acme/directory.


    9. Backups

    Back up (securely, encrypted) at minimum:

    • secrets/root_ca_key and secrets/intermediate_ca_key
    • config/ca.json
    • The CA password

    Losing the root key means every issued cert becomes unrenewable and every client's trust bootstrap must be redone.


    10. Troubleshooting

    SymptomLikely cause
    step-ca won't startWrong --password-file path, or ca.json path mismatch — check journalctl -u step-ca
    Clients can't validate certsRoot CA not installed in client trust store
    ACME clients failConfirm provisioner was added and CA restarted; check port 9000 reachable
    Permission denied on secretsEnsure step user owns /etc/step-ca recursively (chown -R step:step)