--- title: "Cómo solucionar errores de cadena de certificados SSL" description: "Diagnostica con OpenSSL una cadena de certificados SSL incompleta o desordenada, recupera el intermedio que falta y corrige la configuración de Nginx o Apache." category: "SSL/TLS y Certificados" language: es source: https://cubepath.com/es/docs/ssl-tls-y-certificados/solucion-de-problemas-de-cadena-de-certificados-ssl updated: 2026-09-25 --- # Cómo solucionar errores de cadena de certificados SSL Un servidor HTTPS no solo debe enviar su propio certificado, también los certificados intermedios que lo enlazan con una autoridad raíz de confianza. Cuando falta un intermedio o el orden es incorrecto, el sitio puede funcionar en Chrome y fallar en `curl`, en aplicaciones Java o Python, en móviles Android y en integraciones de terceros. En esta guía aprenderás a diagnosticar la cadena que sirve tu servidor con OpenSSL, localizar el problema y corregir la configuración de Nginx o Apache en Ubuntu 24.04. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS con Nginx o Apache sirviendo HTTPS, por ejemplo un VPS de CubePath, y un usuario con privilegios `sudo`. - Los archivos del certificado en formato PEM: el certificado del dominio, los intermedios que te entregó la autoridad certificadora (CA) y la clave privada. - OpenSSL y curl, que vienen instalados por defecto en Ubuntu. Sustituye `your_domain` por tu dominio en todos los comandos. ## Cómo funciona una cadena de certificados Una cadena válida tiene tres niveles: | Posición | Certificado | ¿Lo envía el servidor? | |---|---|---| | 0 | Certificado del dominio (hoja), emitido para `your_domain` | Sí, siempre el primero | | 1..n | Uno o varios intermedios de la CA | Sí, a continuación y en orden | | Raíz | Autofirmado, ya incluido en el almacén del sistema o navegador | No es necesario | Cada certificado debe estar firmado por el siguiente: el **issuer** (emisor) del certificado 0 es el **subject** (sujeto) del certificado 1, y así hasta llegar a una raíz que el cliente ya conoce. Los navegadores de escritorio suelen ocultar una cadena incompleta porque descargan el intermedio que falta a partir de la URL que trae el propio certificado o lo tienen en caché. La mayoría de clientes de línea de comandos y librerías no lo hacen, y por eso el error aparece "solo en algunos clientes". ## Paso 1: Reproducir el error Empieza comprobando qué ve un cliente que no completa cadenas por su cuenta. `curl` es suficiente: ```bash curl -sSI https://your_domain ``` Con una cadena incompleta, el resultado es: ```output curl: (60) SSL certificate problem: unable to get local issuer certificate ``` OpenSSL da más detalle. La opción `-servername` envía el nombre por SNI, imprescindible si el servidor aloja varios dominios: ```bash openssl s_client -connect your_domain:443 -servername your_domain /dev/null | grep 'Verify return code' ``` Estos son los códigos más habituales: | Código | Significado habitual | |---|---| | `0 (ok)` | La cadena es válida | | `20 (unable to get local issuer certificate)` | Falta un intermedio, o la raíz no está en el almacén del cliente | | `21 (unable to verify the first certificate)` | El servidor solo envía el certificado hoja, sin intermedios | | `10 (certificate has expired)` | Algún certificado de la cadena ha caducado | | `18 (self-signed certificate)` / `19 (self-signed certificate in certificate chain)` | Certificado autofirmado o raíz desconocida para el cliente | ## Paso 2: Ver la cadena que envía el servidor Muestra todos los certificados que entrega el servidor con `-showcerts`: ```bash openssl s_client -connect your_domain:443 -servername your_domain -showcerts /dev/null | sed -n '/^Certificate chain/,/^---/p' | grep -E '^ *[0-9]+ s:|^ *i:' ``` Una cadena correcta de Let's Encrypt tiene este aspecto: el emisor de la posición 0 coincide con el sujeto de la posición 1. ```output 0 s:CN = your_domain i:C = US, O = Let's Encrypt, CN = R11 1 s:C = US, O = Let's Encrypt, CN = R11 i:C = US, O = Internet Security Research Group, CN = ISRG Root X1 ``` Fíjate en estos síntomas: - **Solo aparece la posición 0**: el servidor no envía intermedios. Es el caso más frecuente. - **El emisor de 0 no coincide con el sujeto de 1**: intermedio equivocado (de otra CA o de otra generación) o certificados en orden incorrecto. - **La posición 0 no es tu dominio**: el orden del archivo está invertido, o el servidor responde con otro `server` o `VirtualHost` por defecto. ## Paso 3: Revisar los archivos del servidor Localiza qué archivos usa tu servidor web: ```bash sudo grep -rn 'ssl_certificate' /etc/nginx/sites-enabled/ sudo grep -rn 'SSLCertificate' /etc/apache2/sites-enabled/ ``` Cuenta cuántos certificados contiene el archivo configurado. Para un certificado de CA pública debería haber al menos dos: ```bash sudo grep -c 'BEGIN CERTIFICATE' /etc/ssl/your_domain/fullchain.pem ``` Lista el sujeto y el emisor de cada certificado del archivo, en el orden en que aparecen: ```bash sudo openssl crl2pkcs7 -nocrl -certfile /etc/ssl/your_domain/fullchain.pem | openssl pkcs7 -print_certs -noout ``` ```output subject=CN = your_domain issuer=C = US, O = Let's Encrypt, CN = R11 subject=C = US, O = Let's Encrypt, CN = R11 issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X1 ``` Comprueba también que la clave privada corresponde al certificado. Los dos hashes deben ser idénticos: ```bash sudo openssl x509 -noout -pubkey -in /etc/ssl/your_domain/fullchain.pem | sha256sum sudo openssl pkey -pubout -in /etc/ssl/your_domain/privkey.pem | sha256sum ``` Si no coinciden, Nginx se niega a arrancar con `key values mismatch` y Apache con `AH02565: Certificate and private key ... do not match`. En ese caso, busca la clave con la que generaste la petición de firma (CSR). ## Paso 4: Recuperar el intermedio que falta Lo más fiable es descargar el paquete de intermedios desde el panel de tu CA. Si no lo tienes, el propio certificado indica dónde obtener su emisor en la extensión Authority Information Access: ```bash openssl x509 -in /etc/ssl/your_domain/cert.pem -noout -ext authorityInfoAccess ``` ```output Authority Information Access: CA Issuers - URI:http://r11.i.lencr.org/ ``` Descarga el intermedio y conviértelo a PEM. Normalmente se publica en formato DER: ```bash curl -fsSL -o /tmp/intermediate.der http://r11.i.lencr.org/ openssl x509 -inform DER -in /tmp/intermediate.der -out /tmp/intermediate.pem openssl x509 -in /tmp/intermediate.pem -noout -subject ``` Algunas CA publican el intermedio como PKCS#7 (extensión `.p7c`). En ese caso, extráelo con `openssl pkcs7 -inform DER -in archivo.p7c -print_certs -out /tmp/intermediate.pem`. Verifica sin conexión que el certificado del dominio se valida con ese intermedio y el almacén de raíces del sistema: ```bash openssl verify -untrusted /tmp/intermediate.pem /etc/ssl/your_domain/cert.pem ``` ```output /etc/ssl/your_domain/cert.pem: OK ``` ## Paso 5: Construir el archivo fullchain Concatena primero el certificado del dominio y después los intermedios, de la hoja hacia la raíz. No incluyas la raíz: no aporta nada, aumenta el tamaño del handshake y algunos analizadores lo marcan como aviso. ```bash sudo sh -c 'cat /etc/ssl/your_domain/cert.pem /tmp/intermediate.pem > /etc/ssl/your_domain/fullchain.pem' ``` Si tu CA entrega un archivo `ca-bundle` con varios intermedios, revisa su orden con el comando `crl2pkcs7` del paso 3 antes de concatenarlo: debe ir del intermedio más cercano a tu certificado hacia la raíz. > Nota: si usas Certbot no construyas la cadena a mano. Apunta siempre a `/etc/letsencrypt/live/your_domain/fullchain.pem`, nunca a `cert.pem`, y Certbot mantendrá la cadena correcta en cada renovación. ## Paso 6: Configurar el servidor web ### Nginx Nginx espera la cadena completa en `ssl_certificate`. No existe una directiva separada para los intermedios: ```nginx server { listen 443 ssl; server_name your_domain; ssl_certificate /etc/ssl/your_domain/fullchain.pem; ssl_certificate_key /etc/ssl/your_domain/privkey.pem; } ``` Comprueba y recarga: ```bash sudo nginx -t && sudo systemctl reload nginx ``` ### Apache Desde Apache 2.4.8, `SSLCertificateFile` admite la cadena completa y `SSLCertificateChainFile` está obsoleta. Ubuntu 24.04 incluye Apache 2.4.58: ```apache ServerName your_domain SSLEngine on SSLCertificateFile /etc/ssl/your_domain/fullchain.pem SSLCertificateKeyFile /etc/ssl/your_domain/privkey.pem ``` Comprueba y recarga: ```bash sudo apachectl configtest && sudo systemctl reload apache2 ``` ## Paso 7: Verificar la corrección Repite las comprobaciones del paso 1. Ambas deben pasar: ```bash openssl s_client -connect your_domain:443 -servername your_domain /dev/null | grep 'Verify return code' curl -sSI https://your_domain | head -n 1 ``` ```output Verify return code: 0 (ok) HTTP/2 200 ``` Para una comprobación externa, analiza el dominio en SSL Labs (`https://www.ssllabs.com/ssltest/`): en la sección **Certification Paths** no debe aparecer `Extra download` ni `Incomplete`. ## Solución de problemas **El servidor sigue enviando la cadena antigua.** Nginx y Apache solo leen los certificados al arrancar o recargar. Confirma que has recargado el servicio correcto y que no hay otro bloque `server` o `VirtualHost` para el mismo nombre con rutas distintas (`sudo nginx -T | grep -n ssl_certificate`). **Error 20 solo en un servidor cliente concreto.** Si la cadena es correcta desde otros equipos, el cliente tiene un almacén de raíces desactualizado. En Debian y Ubuntu, actualízalo con `sudo apt update && sudo apt install --only-upgrade ca-certificates`. Las aplicaciones Java usan su propio almacén (`cacerts`), que se actualiza con el JDK. **Error 10 (certificado caducado) con un certificado del dominio vigente.** Algún intermedio del archivo ha caducado, normalmente porque se reutilizó un paquete antiguo tras renovar. Revisa las fechas de cada uno con `openssl x509 -noout -subject -enddate` y sustitúyelo por el intermedio actual de la CA. **El certificado hoja no aparece en la posición 0.** El archivo está en orden inverso. Nginx en ese caso suele fallar con `key values mismatch`, porque compara la clave con el primer certificado del archivo. Reordénalo como en el paso 5. ## Conclusión Has aprendido a leer la cadena que entrega un servidor con `openssl s_client`, a identificar intermedios ausentes o desordenados, a recuperar el intermedio desde la extensión AIA y a configurar Nginx y Apache con un `fullchain` correcto. Como siguientes pasos, automatiza la emisión y renovación con Certbot para no volver a gestionar cadenas a mano, y añade una comprobación periódica de `Verify return code` a tu sistema de monitorización.