--- title: "Cómo configurar TLS mutuo (mTLS) con Nginx en Ubuntu 24.04" description: "Crea tu propia CA con OpenSSL, emite certificados de servidor y de cliente y configura Nginx en Ubuntu 24.04 para exigir mTLS, con pruebas en curl y revocación." category: "Proxy y Equilibrio de Carga" language: es source: https://cubepath.com/es/docs/proxy-y-equilibrio-de-carga/configuracion-de-tls-mutuo-mtls updated: 2026-09-25 --- # Cómo configurar TLS mutuo (mTLS) con Nginx en Ubuntu 24.04 En TLS normal solo el servidor demuestra su identidad con un certificado. Con TLS mutuo (mTLS) el cliente también presenta un certificado, y el servidor rechaza la conexión si no está firmado por una autoridad de certificación (CA) en la que confía. Es una forma robusta de proteger APIs internas, paneles de administración o la comunicación entre servicios sin depender de contraseñas ni tokens. En esta guía crearás una CA privada con OpenSSL, emitirás un certificado de servidor y otro de cliente, configurarás Nginx en Ubuntu 24.04 para exigirlos y aprenderás a bloquear un certificado comprometido. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS con Nginx instalado (`sudo apt install nginx`), por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - Un nombre DNS para el servicio, en los ejemplos `api.your_domain`, que resuelva a la IP del servidor. Para pruebas puedes añadirlo al `/etc/hosts` de tu equipo. - El puerto 443/TCP abierto: `sudo ufw allow 443/tcp`. - Una aplicación escuchando en `127.0.0.1:8080` a la que Nginx hará de proxy. Si aún no la tienes, puedes probar con `python3 -m http.server 8080 --bind 127.0.0.1`. ## Paso 1: Crear la autoridad de certificación La CA es el par de clave y certificado con el que firmarás todos los demás certificados. Quien tenga su clave privada puede emitir certificados de cliente válidos, así que guárdala en un directorio al que solo accede root y, en producción, en una máquina distinta del servidor. ```bash sudo -i mkdir -p /root/mtls-ca chmod 700 /root/mtls-ca cd /root/mtls-ca ``` `sudo -i` abre una shell de root. A partir de aquí, hasta el paso 4, los comandos se ejecutan como root dentro de `/root/mtls-ca`. Genera la clave de la CA (ECDSA P-256) y un certificado autofirmado válido 10 años: ```bash openssl ecparam -name prime256v1 -genkey -noout -out ca.key chmod 600 ca.key openssl req -x509 -new -key ca.key -sha256 -days 3650 -subj "/O=Your Company/CN=Your Company Internal CA" -out ca.crt ``` Con `-x509`, OpenSSL aplica la sección `v3_ca` de `/etc/ssl/openssl.cnf`, que marca el certificado como CA. Compruébalo: ```bash openssl x509 -in ca.crt -noout -subject -ext basicConstraints ``` ```output subject=O = Your Company, CN = Your Company Internal CA X509v3 Basic Constraints: critical CA:TRUE ``` ## Paso 2: Emitir el certificado del servidor Los clientes modernos ignoran el campo CN y validan el nombre con la extensión Subject Alternative Name (SAN), así que el certificado del servidor debe incluirla. Crea un archivo de extensiones: ```bash nano server.ext ``` ```ini basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature extendedKeyUsage = serverAuth subjectAltName = DNS:api.your_domain ``` Genera la clave, la solicitud de firma (CSR) y firma el certificado con la CA: ```bash openssl ecparam -name prime256v1 -genkey -noout -out server.key openssl req -new -key server.key -subj "/CN=api.your_domain" -out server.csr openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 397 -sha256 -extfile server.ext -out server.crt ``` `-CAcreateserial` crea el archivo `ca.srl`, donde OpenSSL lleva la cuenta de números de serie. Verifica que el certificado encadena con la CA: ```bash openssl verify -CAfile ca.crt server.crt ``` ```output server.crt: OK ``` > Nota: si el servicio es público, puedes usar un certificado de Let's Encrypt para el servidor y reservar la CA privada solo para los clientes. Nginx valida cada lado por separado. ## Paso 3: Emitir un certificado de cliente Cada cliente (una persona, un servicio, un equipo) recibe su propio certificado. El CN identifica al cliente y la extensión `clientAuth` limita su uso a autenticación de cliente: ```bash nano client.ext ``` ```ini basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature extendedKeyUsage = clientAuth ``` Emite el certificado para un cliente llamado `billing-service`, válido un año: ```bash openssl ecparam -name prime256v1 -genkey -noout -out billing-service.key openssl req -new -key billing-service.key -subj "/O=Your Company/CN=billing-service" -out billing-service.csr openssl x509 -req -in billing-service.csr -CA ca.crt -CAkey ca.key -CAserial ca.srl -days 365 -sha256 -extfile client.ext -out billing-service.crt openssl verify -CAfile ca.crt billing-service.crt ``` ```output billing-service.crt: OK ``` Los navegadores y algunas herramientas importan el certificado de cliente en formato PKCS#12, un único archivo con certificado y clave protegido por contraseña: ```bash openssl pkcs12 -export -in billing-service.crt -inkey billing-service.key -certfile ca.crt -name billing-service -out billing-service.p12 ``` OpenSSL te pedirá una contraseña de exportación: usa una robusta y entrégala al cliente por un canal distinto al del archivo. ## Paso 4: Instalar los certificados en Nginx Nginx necesita el certificado y la clave del servidor, y **solo el certificado** de la CA para verificar a los clientes. La clave `ca.key` nunca debe copiarse a `/etc/nginx`: ```bash install -d -m 750 -o root -g www-data /etc/nginx/mtls install -m 644 ca.crt server.crt /etc/nginx/mtls/ install -m 640 -g www-data server.key /etc/nginx/mtls/ exit ``` El `exit` final cierra la shell de root que abriste en el paso 1. ## Paso 5: Configurar mTLS en Nginx Crea el sitio: ```bash sudo nano /etc/nginx/sites-available/api.your_domain ``` ```nginx server { listen 443 ssl; listen [::]:443 ssl; server_name api.your_domain; ssl_certificate /etc/nginx/mtls/server.crt; ssl_certificate_key /etc/nginx/mtls/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_client_certificate /etc/nginx/mtls/ca.crt; ssl_verify_client on; ssl_verify_depth 1; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Client-DN $ssl_client_s_dn; proxy_set_header X-Client-Serial $ssl_client_serial; proxy_set_header X-Client-Verify $ssl_client_verify; } } ``` - `ssl_client_certificate`: la CA con la que se validan los certificados de cliente. Nginx solo acepta certificados firmados por ella. - `ssl_verify_client on`: exige certificado. Sin él, Nginx responde `400 Bad Request`. - `ssl_verify_depth 1`: tus certificados de cliente están firmados directamente por la CA, sin intermedias. - Las cabeceras `X-Client-*` pasan a la aplicación la identidad del cliente para que pueda autorizar por CN. Asegúrate de que la aplicación solo es accesible desde Nginx, o cualquiera podría enviar esas cabeceras falsificadas. Activa el sitio, comprueba la configuración y recarga: ```bash sudo ln -s /etc/nginx/sites-available/api.your_domain /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` ## Paso 6: Probar la conexión Copia a tu equipo `ca.crt`, `billing-service.crt` y `billing-service.key` (por ejemplo con `scp`). Primero intenta conectar sin certificado de cliente: ```bash curl --cacert ca.crt https://api.your_domain/ ``` ```output 400 No required SSL certificate was sent ... ``` Ahora con el certificado de cliente: ```bash curl --cacert ca.crt --cert billing-service.crt --key billing-service.key https://api.your_domain/ ``` La respuesta es la de tu aplicación. Un certificado firmado por otra CA produce `400 The SSL certificate error`. Para ver qué CA pide el servidor durante el handshake: ```bash openssl s_client -connect api.your_domain:443 -servername api.your_domain -CAfile ca.crt /dev/null | grep -A2 "Acceptable client certificate CA names" ``` ```output Acceptable client certificate CA names O = Your Company, CN = Your Company Internal CA ``` ## Paso 7: Revocar un certificado de cliente Si se filtra la clave de un cliente o un servicio se da de baja, debes rechazar su certificado antes de que caduque. Para un número reducido de clientes, lo más sencillo es bloquear por número de serie en Nginx. Obtén el serial del certificado: ```bash sudo openssl x509 -in /root/mtls-ca/billing-service.crt -noout -serial ``` ```output serial=5C2E0A6B1F3D4E7A9B8C0D1E2F3A4B5C6D7E8F90 ``` Crea la lista de seriales revocados, uno por línea seguido de ` 1;`: ```bash sudo nano /etc/nginx/mtls/revoked.map ``` ```nginx 5C2E0A6B1F3D4E7A9B8C0D1E2F3A4B5C6D7E8F90 1; ``` Declara el `map` en `/etc/nginx/sites-available/api.your_domain`, fuera del bloque `server`: ```nginx map $ssl_client_serial $client_revoked { default 0; include /etc/nginx/mtls/revoked.map; } ``` Y dentro del bloque `server`, antes de `location`: ```nginx if ($client_revoked) { return 403; } ``` Recarga Nginx y repite desde tu equipo la petición con ese certificado: ```bash sudo nginx -t && sudo systemctl reload nginx ``` ```bash curl --cacert ca.crt --cert billing-service.crt --key billing-service.key https://api.your_domain/ ``` ```output 403 Forbidden ... ``` Si gestionas muchos clientes, conviene pasar a una CA con base de datos y listas de revocación (CRL), que Nginx admite con la directiva `ssl_crl`, o a una CA automatizada como step-ca que emite certificados de corta duración. ## Paso 8: Renovar certificados antes de que caduquen Un certificado caducado corta el servicio igual que uno revocado. Comprueba cuándo caducan y si lo harán en los próximos 30 días: ```bash openssl x509 -in /etc/nginx/mtls/server.crt -noout -enddate -checkend 2592000 ``` ```output notAfter=Oct 27 10:02:11 2027 GMT Certificate will not expire ``` Para renovar, repite el paso 2 o el 3 con una clave nueva, distribuye el certificado y, en el caso del servidor, recarga Nginx. Como la CA no cambia, los clientes existentes siguen funcionando sin tocar nada. Si algún día tienes que sustituir la CA, concatena la antigua y la nueva en `ca.crt` durante la transición para que Nginx acepte clientes de ambas. ## Solución de problemas - **`400 No required SSL certificate was sent` con certificado.** El cliente no lo está enviando: revisa las rutas de `--cert` y `--key`. En navegadores, importa el `.p12` en el almacén del sistema y reinicia el navegador. - **`400 The SSL certificate error`.** El certificado no encadena con `ssl_client_certificate`, ha caducado o su `extendedKeyUsage` no incluye `clientAuth`. Nginx registra el motivo con nivel `info`: pon temporalmente `error_log /var/log/nginx/error.log info;` en el bloque `server` para verlo, o reprodúcelo con `openssl verify -CAfile ca.crt billing-service.crt`. - **`curl: (60) SSL certificate problem: unable to get local issuer certificate`.** El cliente no confía en el certificado del servidor. Pasa la CA con `--cacert ca.crt`. ## Conclusión Tienes una CA privada, certificados de servidor y de cliente, y un Nginx que solo acepta conexiones de clientes con un certificado válido y no revocado, pasando su identidad a la aplicación. Como siguientes pasos, usa el CN de `X-Client-DN` para autorizar por servicio en tu aplicación, lleva la clave de la CA fuera del servidor y automatiza la emisión y renovación, por ejemplo con step-ca, cuando el número de clientes crezca.