step-ca es una autoridad de certificados (CA) de código abierto de Smallstep pensada para emitir certificados TLS internos de forma automatizada. Incluye un servidor ACME, el mismo protocolo que usa Let's Encrypt, así que herramientas como Certbot pueden pedir y renovar certificados para nombres internos que ninguna CA pública firmaría. En esta guía instalarás step-ca en Ubuntu 24.04, lo ejecutarás como servicio systemd, emitirás un certificado con el CLI step y configurarás Certbot en otro servidor para obtener certificados de tu CA por ACME.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS para la CA, por ejemplo un VPS de CubePath, con un usuario no root con privilegios sudo. Con 1 GB de RAM es suficiente.
  • Un nombre DNS interno para la CA que resuelva a su IP privada. En los ejemplos se usa ca.example.internal.
  • Para la parte de ACME, un segundo servidor con Ubuntu 24.04 y Nginx, con un nombre que la CA pueda resolver (app.example.internal en los ejemplos) y el puerto 80 accesible desde la CA.
  • Una red privada entre ambos servidores. Sustituye 10.10.0.0/24 por tu rango privado.

Sustituye ca.example.internal, app.example.internal y [email protected] por tus propios valores.

Paso 1: Instalar step-ca y el CLI step

Smallstep publica un repositorio APT firmado con ambos paquetes: step-ca (el servidor) y step-cli (el cliente, que se usa con el comando step). Descarga la clave del repositorio:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o /etc/apt/keyrings/smallstep.asc

Añade el repositorio en formato deb822:

sudo tee /etc/apt/sources.list.d/smallstep.sources > /dev/null <<'EOF'
Types: deb
URIs: https://packages.smallstep.com/stable/debian
Suites: debs
Components: main
Signed-By: /etc/apt/keyrings/smallstep.asc
EOF

Instala los paquetes:

sudo apt update
sudo apt install step-cli step-ca

Comprueba las versiones y la ruta del binario del servidor, que usarás en la unidad de systemd:

step version
step-ca version
command -v step-ca
Smallstep CLI/0.28.7 (linux/amd64)
Release Date: 2026-07-10 18:12 UTC
Smallstep CA/0.28.4 (linux/amd64)
Release Date: 2026-07-02 21:40 UTC
/usr/bin/step-ca

Tus números de versión pueden ser distintos.

Paso 2: Crear el usuario de servicio y la contraseña

step-ca no necesita privilegios de root. Crea un usuario de sistema step cuyo directorio será /etc/step-ca, donde vivirán la configuración, las claves y la base de datos:

sudo useradd --system --user-group --home-dir /etc/step-ca --shell /usr/sbin/nologin step
sudo install -d -m 0700 -o step -g step /etc/step-ca

Genera una contraseña aleatoria para cifrar las claves privadas de la CA. El servicio la leerá de este archivo al arrancar:

openssl rand -base64 32 | sudo -u step tee /etc/step-ca/password.txt > /dev/null
sudo chmod 400 /etc/step-ca/password.txt

Paso 3: Inicializar la CA

step ca init crea una CA raíz, una CA intermedia (que es la que firma los certificados) y la configuración del servidor. Ejecútalo como el usuario step, con STEPPATH apuntando a /etc/step-ca:

sudo -u step env STEPPATH=/etc/step-ca step ca init \
  --deployment-type standalone \
  --name "Example Internal CA" \
  --dns ca.example.internal \
  --address :9000 \
  --provisioner [email protected] \
  --password-file /etc/step-ca/password.txt \
  --acme

Qué significa cada opción:

  • --name: nombre que aparecerá en los certificados raíz e intermedio.
  • --dns: nombres (o IPs) con los que los clientes contactarán con la CA. Debe coincidir con la URL que usarás después.
  • --address :9000: la CA escucha en el puerto 9000 de todas las interfaces.
  • --provisioner: crea un provisioner JWK con ese nombre, protegido con la misma contraseña. Los provisioners son los métodos autorizados para pedir certificados.
  • --acme: crea además un provisioner ACME llamado acme.

Al final verás la huella (fingerprint) del certificado raíz. Apúntala, los clientes la necesitan para confiar en la CA:

Root certificate: /etc/step-ca/certs/root_ca.crt
Root private key: /etc/step-ca/secrets/root_ca_key
Root fingerprint: 2a8f1c0e9b7d4c3e...
Intermediate certificate: /etc/step-ca/certs/intermediate_ca.crt
Intermediate private key: /etc/step-ca/secrets/intermediate_ca_key
Database folder: /etc/step-ca/db
Default configuration: /etc/step-ca/config/defaults.json
Certificate Authority configuration: /etc/step-ca/config/ca.json

Si necesitas consultarla más tarde:

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

Paso 4: Ajustar la validez de los certificados ACME

Por defecto step-ca emite certificados de 24 horas. Es una buena práctica para el CLI step, que renueva con frecuencia, pero Certbot está pensado para certificados de 90 días y los renovaría en cada ejecución. Amplía la validez solo para el provisioner ACME.

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

Busca el provisioner de tipo ACME dentro de authority.provisioners y añádele el bloque claims:

{
  "type": "ACME",
  "name": "acme",
  "claims": {
    "defaultTLSCertDuration": "2160h",
    "maxTLSCertDuration": "2160h"
  }
}

2160h equivale a 90 días. Comprueba que el archivo sigue siendo JSON válido:

sudo python3 -m json.tool /etc/step-ca/config/ca.json > /dev/null && echo "JSON OK"
JSON OK

Paso 5: Ejecutar step-ca como servicio systemd

Crea la unidad de systemd:

sudo nano /etc/systemd/system/step-ca.service
[Unit]
Description=step-ca certificate authority
Documentation=https://smallstep.com/docs/step-ca
After=network-online.target
Wants=network-online.target
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 -HUP $MAINPID
Restart=on-failure
RestartSec=5

NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/etc/step-ca/db

[Install]
WantedBy=multi-user.target

ProtectSystem=full monta /etc como solo lectura para el servicio; ReadWritePaths deja escribir en la base de datos, que es lo único que step-ca modifica en funcionamiento.

Arranca y habilita el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now step-ca
sudo systemctl status step-ca --no-pager
● step-ca.service - step-ca certificate authority
     Loaded: loaded (/etc/systemd/system/step-ca.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-09-25 10:00:00 UTC; 3s ago

Permite el acceso al puerto 9000 solo desde tu red privada:

sudo ufw allow from 10.10.0.0/24 to any port 9000 proto tcp

Comprueba el endpoint de salud. La CA siempre sirve HTTPS con un certificado firmado por su propia raíz, así que indica a curl en qué raíz confiar:

curl --cacert /etc/step-ca/certs/root_ca.crt https://ca.example.internal:9000/health
{"status":"ok"}

Paso 6: Configurar un cliente y confiar en la CA

En el servidor de aplicación (app.example.internal), instala step-cli añadiendo el mismo repositorio que en el paso 1 y ejecutando sudo apt install step-cli.

Configura el CLI para usar tu CA. bootstrap descarga el certificado raíz y comprueba que su huella coincide con la que anotaste en el paso 3, así no confías en una CA suplantada:

step ca bootstrap --ca-url https://ca.example.internal:9000 --fingerprint your_root_fingerprint
The root certificate has been saved in /home/your_user/.step/certs/root_ca.crt.
The authority configuration has been saved in /home/your_user/.step/config/defaults.json.

Instala la raíz en el almacén de confianza del sistema para que curl, Certbot y el resto de programas confíen en los certificados de tu CA:

sudo step certificate install "$(step path)/certs/root_ca.crt"

Compruébalo sin --cacert:

curl https://ca.example.internal:9000/health
{"status":"ok"}

Paso 7: Emitir un certificado con el CLI step

Para certificados puntuales (un servicio interno, una prueba) puedes usar directamente el provisioner JWK. Te pedirá la contraseña del provisioner, que es la de /etc/step-ca/password.txt:

step ca certificate app.example.internal app.crt app.key

Inspecciona el resultado:

step certificate inspect app.crt --short
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 2841...9173]
  Subject:     app.example.internal
  Issuer:      Example Internal CA Intermediate CA
  Provisioner: [email protected] [ID: 7Rh3...Qe0k]
  Valid from:  2026-09-25T10:05:12Z
          to:  2026-09-26T10:06:12Z

El certificado dura 24 horas. Se renueva con el propio certificado como credencial, sin volver a pedir la contraseña:

step ca renew --force app.crt app.key

Para uso continuado es más cómodo ACME, que es lo que configuras a continuación.

Paso 8: Obtener certificados por ACME con Certbot

En el servidor de aplicación, instala Certbot con el plugin de Nginx desde los repositorios de Ubuntu:

sudo apt install certbot python3-certbot-nginx

Pide el certificado a tu CA indicando la URL del directorio ACME. El formato es https://<ca>/acme/<nombre del provisioner>/directory:

sudo certbot --nginx \
  --server https://ca.example.internal:9000/acme/acme/directory \
  -d app.example.internal \
  --register-unsafely-without-email --agree-tos

step-ca valida el dominio con el desafío HTTP-01: se conecta a http://app.example.internal/.well-known/acme-challenge/..., así que la CA debe resolver ese nombre y llegar al puerto 80 del servidor de aplicación.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/app.example.internal/fullchain.pem
...
Congratulations! You have successfully enabled HTTPS on https://app.example.internal

Certbot guarda la URL del servidor ACME en /etc/letsencrypt/renewal/app.example.internal.conf, y el temporizador certbot.timer renovará el certificado con tu CA cuando le queden menos de 30 días. Comprueba que la renovación funciona:

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/app.example.internal/fullchain.pem (success)

Y comprueba el sitio desde cualquier máquina que confíe en tu raíz:

curl -sI https://app.example.internal | head -n 1
HTTP/1.1 200 OK

Solución de problemas

Consulta siempre primero el log del servicio en el servidor de la CA:

sudo journalctl -u step-ca -n 50 --no-pager

El servicio no arranca y el log muestra un error al descifrar la clave intermedia. El archivo password.txt no es el que se usó en step ca init, o se ha modificado. Debe contener exactamente la contraseña original.

certificate signed by unknown authority o SSL certificate problem. El cliente no confía en tu raíz. Repite step ca bootstrap y step certificate install en ese equipo.

Certbot falla con Connection refused o timeout en el desafío. La CA no llega al puerto 80 del servidor de aplicación. Comprueba desde la CA con curl -I http://app.example.internal y revisa DNS y firewall.

Certbot recibe un 404 al pedir el directorio ACME. El nombre del provisioner en la URL no coincide con el de ca.json. Lista los que existen con step ca provisioner list.

Conclusión

Tienes una CA privada funcionando con step-ca como servicio systemd, clientes que confían en su raíz, emisión manual con el CLI step y certificados ACME renovados automáticamente por Certbot. La clave raíz (/etc/step-ca/secrets/root_ca_key) solo hace falta para firmar una nueva CA intermedia, así que el siguiente paso recomendado es hacer una copia cifrada fuera del servidor y eliminarla de él. También puedes usar la misma CA para autenticación mTLS entre servicios o para certificados SSH con step-ca.