--- title: "Cómo crear una CA privada con OpenSSL en Ubuntu 24.04" description: "Crea una CA privada con OpenSSL, emite certificados de servidor y cliente para servicios internos y haz que Ubuntu, Nginx y curl confíen en ellos." category: "SSL/TLS y Certificados" language: es source: https://cubepath.com/es/docs/ssl-tls-y-certificados/certificados-autofirmados-para-servicios-internos updated: 2026-09-25 --- # Cómo crear una CA privada con OpenSSL en Ubuntu 24.04 Los servicios internos (paneles de administración, APIs entre microservicios, bases de datos en red privada) también deben cifrar su tráfico, pero muchas veces no tienen un nombre público con el que obtener un certificado de Let's Encrypt. La alternativa correcta no es un certificado autofirmado distinto para cada servicio, sino una autoridad certificadora (CA) privada: instalas su certificado raíz una vez en tus equipos y todos los certificados que firme serán de confianza. En esta guía crearás una CA privada con OpenSSL en Ubuntu 24.04, emitirás un certificado de servidor y otro de cliente, y configurarás Nginx y los clientes para usarlos. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, y un usuario no root con privilegios `sudo`. OpenSSL 3 viene instalado por defecto. - Un nombre interno para el servicio (en esta guía, `app.internal.example`) y la IP privada por la que se accede (`10.0.0.10`). Sustitúyelos por los tuyos. - Nginx instalado (`sudo apt install nginx`) si quieres seguir el paso 5. > Importante: la clave privada de la CA permite emitir certificados en los que confiarán todos tus equipos. Idealmente, genérala en una máquina aislada, no en el servidor que expone servicios, y guarda una copia cifrada fuera de línea. ## Paso 1: Preparar el directorio de la CA Crea un directorio para la CA con permisos restringidos, de modo que solo tu usuario pueda leer las claves: ```bash mkdir -p ~/internal-ca chmod 700 ~/internal-ca cd ~/internal-ca ``` Todos los comandos siguientes se ejecutan desde este directorio. ## Paso 2: Crear la CA raíz Genera la clave privada de la CA. Esta guía usa claves de curva elíptica P-256, que son pequeñas, rápidas y compatibles con cualquier cliente actual: ```bash openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ca.key chmod 600 ca.key ``` Para que el certificado raíz tenga exactamente las extensiones de una CA, define su configuración en un archivo: ```bash nano ca.cnf ``` ```ini [req] distinguished_name = dn prompt = no x509_extensions = v3_ca [dn] O = Example Corp CN = Example Corp Internal Root CA [v3_ca] basicConstraints = critical, CA:TRUE, pathlen:0 keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always ``` `pathlen:0` impide que esta CA firme otras CA intermedias, y `keyUsage` limita su uso a firmar certificados y listas de revocación. Crea el certificado raíz autofirmado con una validez de 10 años: ```bash openssl req -x509 -new -config ca.cnf -key ca.key -sha256 -days 3650 -out ca.crt ``` Comprueba el resultado: ```bash openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraints,keyUsage ``` ```output subject=O = Example Corp, CN = Example Corp Internal Root CA notBefore=Sep 25 10:00:00 2026 GMT notAfter=Sep 22 10:00:00 2036 GMT X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Key Usage: critical Certificate Sign, CRL Sign ``` ## Paso 3: Emitir un certificado de servidor Los clientes modernos ignoran el campo CN y validan el nombre solo contra la extensión Subject Alternative Name (SAN). Un certificado sin SAN falla en Chrome, curl, Go o Python aunque el CN sea correcto, así que la incluirás siempre. Genera la clave del servidor y una petición de firma (CSR): ```bash openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out app.key chmod 600 app.key openssl req -new -key app.key -subj "/CN=app.internal.example" -out app.csr ``` Crea el archivo de extensiones con todos los nombres y direcciones IP por los que se accederá al servicio: ```bash nano app.ext ``` ```ini basicConstraints = CA:FALSE keyUsage = critical, digitalSignature extendedKeyUsage = serverAuth subjectAltName = DNS:app.internal.example, IP:10.0.0.10 subjectKeyIdentifier = hash authorityKeyIdentifier = keyid, issuer ``` Firma la petición con la CA. La validez de 397 días evita problemas con clientes de Apple, que rechazan certificados de servidor con validez excesiva: ```bash openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 397 -sha256 -extfile app.ext -out app.crt ``` `-CAcreateserial` crea el archivo `ca.srl`, que guarda el siguiente número de serie. Consérvalo junto a la CA para que cada certificado tenga un número distinto. Verifica que el certificado está firmado por tu CA y contiene los nombres correctos: ```bash openssl verify -CAfile ca.crt app.crt openssl x509 -in app.crt -noout -subject -issuer -ext subjectAltName ``` ```output app.crt: OK subject=CN = app.internal.example issuer=O = Example Corp, CN = Example Corp Internal Root CA X509v3 Subject Alternative Name: DNS:app.internal.example, IP Address:10.0.0.10 ``` ## Paso 4: Hacer que el sistema confíe en la CA En cada equipo que vaya a conectarse a tus servicios, instala el certificado raíz (nunca la clave) en el almacén del sistema. En Ubuntu y Debian el archivo debe tener extensión `.crt`: ```bash sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-ca.crt sudo update-ca-certificates ``` ```output Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. ``` En Rocky Linux y otras distribuciones de la familia RHEL, cópialo a `/etc/pki/ca-trust/source/anchors/` y ejecuta `sudo update-ca-trust`. Ten en cuenta que no todo el software usa el almacén del sistema: - **Python con `requests`** usa su propio paquete `certifi`. Indícale el almacén del sistema con la variable `REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt`. - **Node.js** necesita `NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-ca.crt`. - **Java** usa el almacén `cacerts` del JDK. Impórtalo con `sudo keytool -importcert -cacerts -alias example-internal-ca -file ca.crt` (la contraseña por defecto es `changeit`). - **Firefox** mantiene su propio almacén; importa el certificado desde **Ajustes** > **Privacidad y seguridad** > **Ver certificados** > **Autoridades**. ## Paso 5: Usar el certificado en Nginx Copia el certificado y la clave del servidor a una ubicación del sistema con permisos adecuados. Si el servicio está en otra máquina, cópialos allí con `scp`: ```bash sudo mkdir -p /etc/ssl/internal sudo cp app.crt /etc/ssl/internal/app.crt sudo cp app.key /etc/ssl/internal/app.key sudo chmod 600 /etc/ssl/internal/app.key ``` Crea el sitio: ```bash sudo nano /etc/nginx/sites-available/app.internal.example ``` ```nginx server { listen 443 ssl; server_name app.internal.example; ssl_certificate /etc/ssl/internal/app.crt; ssl_certificate_key /etc/ssl/internal/app.key; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } } ``` Como la CA emite directamente el certificado del servidor, no hay intermedios que añadir. Activa el sitio y recarga: ```bash sudo ln -s /etc/nginx/sites-available/app.internal.example /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ``` Desde un equipo que confía en la CA (paso 4) y resuelve el nombre, comprueba la conexión sin `-k` ni `--insecure`: ```bash curl -sSI https://app.internal.example ``` ```output HTTP/1.1 200 OK Server: nginx ``` Si el nombre aún no está en tu DNS interno, puedes probar forzando la resolución: `curl -sSI --resolve app.internal.example:443:10.0.0.10 https://app.internal.example`. ## Paso 6: Autenticar clientes con certificados (mTLS) La misma CA puede emitir certificados de cliente para que solo los equipos o servicios autorizados puedan conectarse. La diferencia está en el uso extendido `clientAuth`. En el directorio de la CA: ```bash openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out client.key chmod 600 client.key openssl req -new -key client.key -subj "/CN=backup-agent" -out client.csr printf 'basicConstraints = CA:FALSE\nkeyUsage = critical, digitalSignature\nextendedKeyUsage = clientAuth\n' > client.ext openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -days 397 -sha256 -extfile client.ext -out client.crt openssl verify -CAfile ca.crt -purpose sslclient client.crt ``` ```output client.crt: OK ``` En el servidor, copia `ca.crt` a `/etc/ssl/internal/ca.crt` y añade estas dos directivas al bloque `server` de Nginx: ```nginx ssl_client_certificate /etc/ssl/internal/ca.crt; ssl_verify_client on; ``` Recarga Nginx con `sudo nginx -t && sudo systemctl reload nginx`. Ahora una petición sin certificado recibe `400 No required SSL certificate was sent`, y una con él funciona: ```bash curl -sSI --cert client.crt --key client.key https://app.internal.example ``` ```output HTTP/1.1 200 OK ``` ## Renovar certificados Antes de que caduque un certificado, repite el paso 3 (o el 6) reutilizando el mismo archivo `.ext`: genera una CSR nueva, fírmala y sustituye los archivos en el servidor. Después recarga el servicio. Para ver cuándo caduca un certificado en uso: ```bash openssl x509 -in /etc/ssl/internal/app.crt -noout -enddate ``` El certificado raíz dura 10 años. Anota su fecha de caducidad y planifica su sustitución con tiempo, ya que implica redistribuir el nuevo `ca.crt` a todos los clientes. ## Solución de problemas **`curl: (60) SSL certificate problem: unable to get local issuer certificate`.** El cliente no confía en la CA. Repite el paso 4 en ese equipo y comprueba que el archivo en `/usr/local/share/ca-certificates/` termina en `.crt`. **`no alternative certificate subject name matches target host name`.** Te conectas con un nombre o IP que no está en la SAN. Revisa `openssl x509 -in app.crt -noout -ext subjectAltName` y vuelve a emitir el certificado con el nombre que falta. **`key values mismatch` al recargar Nginx.** El certificado y la clave no corresponden. Compara `openssl x509 -in app.crt -noout -pubkey | sha256sum` con `openssl pkey -in app.key -pubout | sha256sum`: deben coincidir. ## Conclusión Has creado una CA privada con OpenSSL, emitido certificados de servidor y cliente con las extensiones correctas, distribuido la raíz al almacén del sistema y protegido un servicio interno con TLS y autenticación mutua en Nginx. Si el número de servicios crece, considera una CA con emisión automática por ACME, como step-ca, para renovar sin intervención manual. También puedes aplicar el mismo esquema de mTLS a otros servicios internos como PostgreSQL o tus APIs entre microservicios.