// Engineering Log
HTTP, HTTPS y TLS: Parte 10 — Depuración de TLS: curl, openssl s_client y análisis de errores de certificados
Publicado el 16.10.2026
// Ruta rapida
Este articulo pertenece al tema Seguridad y proteccion.
La mayoría de los problemas con HTTPS se reducen a varias causas: cadena de certificados incompleta, certificado caducado, nombre incorrecto en el certificado, incompatibilidad de versiones o cifrados, intervención de equipos intermedios. El navegador normalmente muestra un genérico «Conexión no segura», y la causa exacta la proporcionan dos herramientas: curl y openssl s_client. En esta parte — cómo usarlas y cómo leer su salida.
Qué muestra curl -v
curl -sv -o /dev/null https://example.ru/ 2>&1 | grep '^\*'* Host example.ru:443 was resolved.
* Trying 192.0.2.10:443...
* Connected to example.ru (192.0.2.10) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=example.ru
* start date: Sep 1 00:00:00 2026 GMT
* expire date: Nov 30 23:59:59 2026 GMT
* subjectAltName: host "example.ru" matched cert's "example.ru"
* issuer: C=US; O=Let's Encrypt; CN=R11
* SSL certificate verify ok.De la salida se ve: a qué IP se conectó, qué versión de TLS y qué cifrado se negociaron, qué protocolo se seleccionó vía ALPN, a quién y por quién fue emitido el certificado, hasta qué fecha es válido y si el nombre coincide. El formato de la salida varía un poco según la biblioteca TLS con la que se compiló curl (OpenSSL, LibreSSL, GnuTLS, Schannel en Windows): curl --version la muestra en la primera línea.
Opciones de curl para TLS
curl --tlsv1.2 --tls-max 1.2 https://example.ru/ # solo TLS 1.2
curl --tlsv1.3 https://example.ru/ # no inferior a TLS 1.3
curl --ciphers ECDHE-RSA-AES128-GCM-SHA256 --tls-max 1.2 https://example.ru/ # cifrado específico TLS 1.2
curl --cacert internal-ca.crt https://intranet.example.ru/ # confiar en tu CA
curl --cert client.crt --key client.key https://admin.example.ru/ # certificado de cliente (mTLS)
curl -k https://example.ru/ # no verificar el certificado — solo para diagnósticoLa opción --tlsv1.2 significa “no inferior a 1.2”, y no “exactamente 1.2”. Para comprobar que el servidor soporta exactamente 1.2, hace falta también --tls-max 1.2.
-k (--insecure) es útil para separar el problema del certificado del resto: si con -k todo funciona, el problema está en el certificado. No se debe dejar -k en scripts.
openssl s_client
s_client — cliente TLS de OpenSSL que realiza el handshake y muestra todo lo que envía el servidor.
openssl s_client -connect example.ru:443 -servername example.ru </dev/null-servernameestablece el SNI. En OpenSSL 1.1.1 y posteriores el SNI se toma de-connectautomáticamente si allí hay un nombre y no una IP. Si te conectas por IP, especifica-servernameexplícitamente, de lo contrario obtendrás el certificado por defecto;</dev/nullcierra la entrada para que el comando termine inmediatamente después del handshake.
Lo principal en la salida:
Certificate chain
0 s:CN=example.ru
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
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
...
Verify return code: 0 (ok)- Certificate chain — lo que envió el servidor:
s— a quién fue emitido,i— por quién. En cada siguientesdebe coincidir con elidel anterior. Si en la cadena solo hay la línea0, el servidor no está entregando el certificado intermedio; - Verify return code: 0 (ok) — la cadena se verificó. Cualquier otro valor indica un error con explicación.
Salida breve y casos particulares:
openssl s_client -connect example.ru:443 -brief </dev/null # solo versión, cifrado, verificación
openssl s_client -connect example.ru:443 -showcerts </dev/null # todos los certificados de la cadena en PEM
openssl s_client -connect example.ru:443 -tls1_2 </dev/null # solo TLS 1.2
openssl s_client -connect example.ru:443 -alpn h2 </dev/null # comprobar ALPN
openssl s_client -connect example.ru:443 -status </dev/null # respuesta OCSP, si el servidor la adjunta
openssl s_client -connect mail.example.ru:587 -starttls smtp </dev/null # TLS en protocolos de correo-starttls funciona para smtp, imap, pop3, ftp, xmpp, postgres, mysql y otros protocolos donde TLS se activa con un comando dentro de la conexión abierta.
Información sobre el certificado
Vigencia, nombres y emisor del certificado en el servidor:
openssl s_client -connect example.ru:443 -servername example.ru </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltNamesubject=CN=example.ru
issuer=C=US, O=Let's Encrypt, CN=R11
notBefore=Sep 1 00:00:00 2026 GMT
notAfter=Nov 30 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
DNS:example.ru, DNS:www.example.ruLa opción -ext está en OpenSSL 1.1.1 y posteriores. Para un archivo de certificado en disco — openssl x509 -in cert.pem -noout -text.
Comprobar si el certificado expirará en los próximos 14 días (código de retorno 1 — expirará):
openssl s_client -connect example.ru:443 -servername example.ru </dev/null 2>/dev/null \
| openssl x509 -noout -checkend $((14*24*3600)) && echo ok || echo 'истекает'Para control permanente es mejor el monitoreo: Prometheus blackbox_exporter expone la métrica probe_ssl_earliest_cert_expiry — la fecha de expiración del certificado más próximo en la cadena, y con ella se configura la alerta.
Verificar que la clave corresponde al certificado (los hashes deben coincidir):
openssl x509 -in cert.pem -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout | openssl sha256Verificar la cadena desde archivos:
openssl verify -untrusted chain.pem cert.pemQué versiones y cifrados soporta el servidor
Manualmente — probando con -tls1, -tls1_1, -tls1_2, -tls1_3 en s_client. Más cómodo con herramientas listas:
nmap --script ssl-enum-ciphers -p 443 example.ru
testssl.sh example.rutestssl.sh comprueba versiones, cifrados, cadena, HSTS, vulnerabilidades conocidas y funciona sin instalación, en un único script. Para sitios públicos lo mismo ofrece SSL Labs.
Descifrar el tráfico en Wireshark
Cómo capturar tráfico con tcpdump y trabajar con Wireshark está descrito en el artículo «Solución de problemas de red: tcpdump, sngrep y Wireshark».
A veces hace falta ver el propio HTTP dentro de TLS: qué envía exactamente la aplicación, qué cabeceras van. Para eso el cliente escribe las claves de sesión en un archivo, y Wireshark las usa para descifrar el tráfico.
export SSLKEYLOGFILE=$HOME/tls-keys.log
curl https://example.ru/ # curl compilado con OpenSSL, BoringSSL o GnuTLS
# o inicia Chrome/Firefox desde esta misma shellEn Wireshark: Settings → Protocols → TLS → (Pre)-Master-Secret log filename — especifica el archivo. El tráfico grabado mientras las claves están en ese archivo aparecerá descifrado, incluyendo HTTP/2.
El archivo de claves permite descifrar toda la conversación registrada — trátalo como una contraseña y elimínalo después de depurar. El curl del sistema en macOS está compilado con LibreSSL y puede no soportar SSLKEYLOGFILE; usa el curl de Homebrew.
Errores frecuentes y qué significan
unable to get local issuer certificate (curl, código 60; en s_client — verify error:num=20). El cliente no puede construir la cadena hasta una raíz de confianza. Causas:
- el servidor no entrega el certificado intermedio — corrígelo en el servidor: usa
fullchain.pemen lugar decert.pem; - el certificado fue emitido por una CA interna que no está en el cliente — usa
--cacerto instala la raíz en el sistema; - en el sistema el almacén de certificados raíz está obsoleto — actualiza el paquete
ca-certificates.
certificate has expired (código 10). Comprueba las fechas. Si el certificado en el servidor es reciente pero el cliente ve uno viejo — el servidor web no recargó el certificado después de renovarlo (nginx -s reload) o la respuesta viene de otro nodo del balanceador.
no alternative certificate subject name matches target host name (curl, código 60). El nombre no está en el SAN. Causas comunes: acceder con www. cuando el certificado es solo para el dominio base; conexión sin SNI o por IP; certificado por defecto de otro sitio en ese servidor.
self-signed certificate / self-signed certificate in certificate chain (códigos 18 y 19). Certificado autofirmado, o la cadena termina en una raíz en la que el cliente no confía. Si en el emisor aparece el nombre de un antivirus o de un gateway corporativo, el tráfico se está descifrando en el trayecto.
tlsv1 alert protocol version, unsupported protocol (curl, código 35). No hay versión TLS común: cliente antiguo (solo TLS 1.0) y servidor moderno, o al revés. Prueba con --tlsv1.2 --tls-max 1.2 y con -tls1_2 en s_client.
sslv3 alert handshake failure (código 35). No hay cifrado o grupo común, o el servidor requiere certificado de cliente. En la salida de s_client con -state se ve en qué paso falló el handshake.
Connection reset by peer justo después del ClientHello. El servidor o un equipo intermedio cerró la conexión al ver el ClientHello. Si por IP (sin SNI) la conexión pasa, y por nombre no, es un signo de filtrado por SNI en el camino. Ocurre también en firewalls corporativos que no aceptan un ClientHello grande con la agrupación postcuántica X25519MLKEM768: prueba con -groups x25519 en s_client o --curves x25519 en curl.
SSL routines::wrong version number en s_client. Te conectaste por TLS a un puerto donde no hay TLS: por ejemplo al puerto 80, o al 587 donde hace falta -starttls smtp, o el servidor en 443 responde HTTP normal.
El navegador abre, pero curl o la aplicación no. Casi siempre es cadena incompleta: los navegadores reconstruyen certificados intermedios por su cuenta, las bibliotecas no. Comprobación: openssl s_client -showcerts y la línea Certificate chain.
Orden de diagnóstico
curl -sv -o /dev/null https://nombre/— ¿se llega al TLS, en qué paso está el error, código de error de curl?openssl s_client -connect nombre:443 -servername nombre— cadena yVerify return code.openssl x509 -noout -dates -ext subjectAltName— vigencias y nombres.curl --resolve nombre:443:IP— ¿se comportan igual todos los servidores detrás del balanceador?testssl.sh nombre— versiones, cifrados y comprobación completa de la configuración.- Si nada ayuda — captura de tráfico y Wireshark con
SSLKEYLOGFILE.
// Tarea parecida
Si estas resolviendo algo parecido
Este articulo pertenece a uno de los temas principales de trabajo. Puedes seguir leyendo sobre el tema, ir a la pagina principal para entender a que me dedico o abrir directamente los servicios.
Tema del articulo
Seguridad y proteccion
SSL, hardening, accesos, proteccion de servicios y configuraciones seguras.
Tareas frecuentes de esta tema
- Configurar SSL, certificados y conexiones seguras
- Restringir accesos y cerrar puntos de entrada innecesarios
- Reforzar la configuracion del servidor y los servicios
// Siguiente paso
Si necesitas ayuda con este tema y no solo otro articulo, es mejor ir directo a la pagina del servicio. La pagina principal y la seleccion de materiales quedan como rutas secundarias.
Abrir servicios// Contact
¿Necesitas ayuda?
Escríbeme y te ayudaré a resolver el problema
Respondo en un día laborable (03:00-13:00 GMT)
Или оставьте заявку здесь:
// Related