--- title: "Cómo configurar mTLS con certificados de cliente en Nginx" description: "Crea una CA privada con OpenSSL, emite certificados de cliente y exige autenticación mutua TLS (mTLS) en Nginx o Apache en Ubuntu 24.04, con revocación por CRL." category: "SSL/TLS y Certificados" language: es source: https://cubepath.com/es/docs/ssl-tls-y-certificados/configuracion-de-autenticacion-de-certificado-de-cliente updated: 2026-09-25 --- # Cómo configurar mTLS con certificados de cliente en Nginx Con la autenticación por certificado de cliente, también llamada TLS mutuo (mTLS), el servidor no solo presenta su certificado: exige que el cliente presente uno firmado por una autoridad de certificación (CA) de confianza. Quien no tenga un certificado válido no llega ni a enviar una petición HTTP. En esta guía crearás una CA privada con OpenSSL en Ubuntu 24.04, emitirás certificados de cliente, protegerás un sitio de Nginx (y verás el equivalente en Apache) y revocarás un certificado con una lista de revocación (CRL). Es una buena opción para paneles de administración, APIs internas o comunicación entre servicios, donde los clientes son conocidos y pocos. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Nginx sirviendo tu dominio por HTTPS con un certificado de servidor válido, por ejemplo de Let's Encrypt en `/etc/letsencrypt/live/your_domain/`. Si usas Apache, consulta el paso 5. - `curl` y `openssl`, que vienen instalados en Ubuntu. En los ejemplos, `your_domain` es el dominio del sitio que vas a proteger. El certificado del servidor y la CA de clientes son independientes: el navegador sigue validando el servidor con el certificado público de Let's Encrypt, y Nginx valida a los clientes con tu CA privada. > Nota: lo ideal es mantener la CA en una máquina distinta del servidor web (o al menos fuera de sus rutas), porque quien tenga su clave privada puede emitir certificados válidos. En esta guía se crea en el directorio personal de tu usuario para simplificar. ## Paso 1: Crear la estructura de la CA `openssl ca` lleva un registro de los certificados emitidos y revocados en un archivo índice, lo que es necesario para generar la CRL más adelante. Crea los directorios y archivos de control: ```bash mkdir -p ~/client-ca/{certs,crl,csr,newcerts,private} cd ~/client-ca chmod 700 private touch index.txt echo 1000 > serial echo 1000 > crlnumber ``` Crea el archivo de configuración de la CA: ```bash nano ~/client-ca/openssl.cnf ``` ```ini [ ca ] default_ca = client_ca [ client_ca ] dir = . database = $dir/index.txt serial = $dir/serial crlnumber = $dir/crlnumber new_certs_dir = $dir/newcerts certificate = $dir/certs/ca.crt private_key = $dir/private/ca.key default_md = sha256 default_days = 365 default_crl_days = 30 unique_subject = no policy = policy_client x509_extensions = client_cert copy_extensions = none [ policy_client ] commonName = supplied [ client_cert ] basicConstraints = CA:FALSE keyUsage = critical, digitalSignature extendedKeyUsage = clientAuth subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer [ req ] distinguished_name = req_dn x509_extensions = v3_ca prompt = no [ req_dn ] CN = Example Client CA [ v3_ca ] basicConstraints = critical, CA:TRUE keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer ``` Qué define cada sección: - `client_ca`: dónde están los archivos de la CA, validez por defecto de los certificados (365 días) y de la CRL (30 días). `dir = .` significa que los comandos se ejecutan desde `~/client-ca`. - `policy_client`: solo exige el campo `CN` en las solicitudes; será el identificador de cada cliente. - `client_cert`: extensiones de los certificados de cliente. `extendedKeyUsage = clientAuth` impide usarlos como certificado de servidor. - `v3_ca`: extensiones del certificado raíz de la CA. ## Paso 2: Generar la clave y el certificado de la CA Genera la clave privada de la CA con curva P-256, cifrada con una contraseña que te pedirá dos veces. Guárdala en un gestor de contraseñas, la necesitarás cada vez que emitas o revoques un certificado: ```bash cd ~/client-ca openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -aes256 -out private/ca.key chmod 400 private/ca.key ``` Crea el certificado autofirmado de la CA, válido durante 10 años: ```bash openssl req -config openssl.cnf -new -x509 -key private/ca.key -days 3650 -out certs/ca.crt ``` Comprueba el sujeto y las extensiones: ```bash openssl x509 -in certs/ca.crt -noout -subject -ext basicConstraints,keyUsage ``` ```output subject=CN = Example Client CA X509v3 Basic Constraints: critical CA:TRUE X509v3 Key Usage: critical Certificate Sign, CRL Sign ``` ## Paso 3: Emitir un certificado de cliente Cada cliente debería generar su propia clave y enviar solo la solicitud de firma (CSR). Aquí lo haces todo en el mismo equipo para el ejemplo. Genera la clave y la CSR del cliente `client1`: ```bash cd ~/client-ca openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout private/client1.key -subj "/CN=client1" -out csr/client1.csr ``` Firma la CSR con la CA. Te pedirá la contraseña de la CA y dos confirmaciones: ```bash openssl ca -config openssl.cnf -in csr/client1.csr -out certs/client1.crt ``` ```output Using configuration from openssl.cnf Enter pass phrase for ./private/ca.key: Check that the request matches the signature Signature ok The Subject's Distinguished Name is as follows commonName :ASN.1 12:'client1' Certificate is to be certified until Sep 25 10:00:00 2027 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Database updated ``` Verifica que el certificado está firmado por tu CA: ```bash openssl verify -CAfile certs/ca.crt certs/client1.crt ``` ```output certs/client1.crt: OK ``` Para importar el certificado en un navegador o en el llavero del sistema, empaquétalo con su clave en un archivo PKCS#12. Te pedirá una contraseña de exportación que el usuario tendrá que introducir al importarlo: ```bash openssl pkcs12 -export -in certs/client1.crt -inkey private/client1.key \ -certfile certs/ca.crt -name client1 -out certs/client1.p12 ``` Si algún sistema antiguo rechaza el archivo al importarlo, vuelve a generarlo añadiendo `-legacy`, que usa los algoritmos de cifrado de OpenSSL 1.1. ## Paso 4: Exigir certificado de cliente en Nginx Copia el certificado de la CA (nunca su clave) a un directorio de Nginx: ```bash sudo mkdir -p /etc/nginx/client-ca sudo cp ~/client-ca/certs/ca.crt /etc/nginx/client-ca/ca.crt ``` Abre la configuración del sitio: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` Añade las directivas de verificación al bloque `server` HTTPS. En este ejemplo el sitio responde con el nombre del cliente para que puedas ver que funciona; en un caso real, reemplaza el `location` por tu aplicación: ```nginx server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name your_domain; ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem; # Autenticación de clientes ssl_client_certificate /etc/nginx/client-ca/ca.crt; ssl_verify_client on; ssl_verify_depth 1; location / { default_type text/plain; return 200 "Hola, $ssl_client_s_dn\n"; } } ``` - `ssl_client_certificate`: CA con la que se validan los clientes. Nginx también envía su nombre al cliente para que el navegador sepa qué certificado ofrecer. - `ssl_verify_client on`: rechaza cualquier conexión sin un certificado válido. Con `optional` se aceptan conexiones sin certificado y puedes decidir por `location` usando `$ssl_client_verify`, que vale `SUCCESS` cuando el cliente se ha autenticado. - `ssl_verify_depth 1`: el certificado del cliente está firmado directamente por la CA raíz. Si el sitio hace de proxy hacia una aplicación, pásale la identidad del cliente en cabeceras: ```nginx location / { proxy_set_header X-SSL-Client-Verify $ssl_client_verify; proxy_set_header X-SSL-Client-DN $ssl_client_s_dn; proxy_pass http://127.0.0.1:8080; } ``` La aplicación debe escuchar solo en `127.0.0.1` para que nadie pueda saltarse Nginx y falsificar esas cabeceras. Comprueba y recarga Nginx: ```bash sudo nginx -t sudo systemctl reload nginx ``` Prueba sin certificado: ```bash curl -s https://your_domain | grep title ``` ```output