Ubuntu 24.04: fallos de TLS en curl — lista rápida de comprobación SNI/CA/tiempo

¿Te fue útil?

Ejecutas curl en Ubuntu 24.04, aparece SSL_connect, handshake failure, o el siempre útil curl: (35), y tu pipeline se queda pillado. Alguien sugiere “es la red.” Otro culpa a “OpenSSL siendo raro.” Mientras tanto, solo necesitas que el comando funcione porque producción no se va a desplegar sola.

Esta es la guía de campo para ese momento: comprobaciones rápidas primero y diagnóstico más profundo cuando lo obvio (hora, confianza, SNI) ya no es tan obvio. Es opinada porque tu canal de incidentes merece menos teorías y más decisiones.

Playbook de diagnóstico rápido (qué comprobar primero/segundo/tercero)

Si solo tienes cinco minutos y un pager ya enfadado, haz esto en orden. Esta secuencia está optimizada para las causas “más comunes” y “más destructivas” en Ubuntu 24.04 en redes corporativas.

Primero: Hora + DNS + accesibilidad básica (comprobaciones baratas, alto beneficio)

  1. Comprueba la hora del sistema y la sincronización NTP. Si la hora está mal, TLS falla. No negocies con la criptografía.
  2. Confirma que resuelves el nombre de host que crees. IP equivocada significa certificado equivocado, lo que parece un “fallo TLS”.
  3. Confirma que estás alcanzando el puerto correcto y que es realmente TLS. Un proxy, balanceador o service mesh puede poner HTTP plano en 443 y llamarlo “innovador”.

Segundo: Cadena de confianza y store de CA (el fallo más común de “ayer funcionaba”)

  1. Verifica que el paquete de CA existe y está al día. En Ubuntu, normalmente es el paquete ca-certificates más los archivos de bundle generados.
  2. Comprueba si tu empresa inserta un proxy de inspección TLS. Si es así, el emisor “real” es la raíz corporativa, no Let’s Encrypt / DigiCert / etc.
  3. Busca intermediarios caducados. Aparecen como “unable to get local issuer certificate” y arruinan tu mañana.

Tercero: SNI/ALPN/versión TLS (donde “funciona en el navegador” te engaña)

  1. Prueba SNI explícitamente. Sin SNI, muchos servidores envían un certificado por defecto que no coincide con tu hostname.
  2. Prueba la negociación ALPN (HTTP/2 vs HTTP/1.1). Algunos middleboxes fallan con HTTP/2 y el handshake muere a mitad de camino.
  3. Forza TLS 1.2 o TLS 1.3 para aislar problemas de compatibilidad. Esto es diagnóstico, no una “solución” permanente.

Una cita que vale la pena tener en tu monitor:

Werner Vogels (idea parafraseada): “Todo falla todo el tiempo; diseña y opera asumiendo que así será.”

Hechos interesantes y breve historia (por qué esto sigue pasando)

  • SNI existe porque las IPs se hicieron caras. A medida que el hosting se consolidó, varios dominios necesitaban compartir una IP, pero TLS necesitaba saber qué certificado presentar. SNI resolvió eso poniendo el hostname en el ClientHello.
  • TLS 1.3 cambió la forma del handshake. Eliminó cifrados heredados y alteró detalles de negociación; algunos proxies antiguos “soportan TLS” pero no la realidad del tráfico TLS 1.3.
  • ALPN es la razón por la que HTTP/2 suele “funcionar sin problemas”. Cliente y servidor acuerdan h2 vs http/1.1 durante la negociación TLS; cuando ALPN falla, puedes ver rarezas en el handshake, no solo transferencias más lentas.
  • “Store de confianza de CA” no es una sola cosa. Ubuntu mantiene la confianza del sistema vía /etc/ssl/certs y bundles generados; las aplicaciones a veces incluyen su propio bundle, causando inconsistencias encantadoras.
  • Las cadenas de certificados a menudo se “sirven” incorrectamente. Los servidores deben enviar intermediarios; muchas malas configuraciones dependen de que los clientes tengan intermediarios en caché por visitas previas, por eso “funciona en el navegador” puede engañar.
  • El desfase de reloj ha sido un problema de TLS desde el primer día. Las ventanas de validez son simples y brutales: demasiado temprano o demasiado tarde y la verificación falla, sin importar tus sentimientos.
  • Let’s Encrypt impulsó la automatización a la corriente principal. Genial para la velocidad de ops. También genial para descubrir qué appliances no pueden manejar cadenas modernas a las 03:00.
  • Los números de error de cURL son folclore estable. “(35) SSL connect error” es un cajón de sastre que oculta docenas de fallos de handshake distintos. Necesitas salida verbose para dejar de adivinar.

Broma #1: TLS es como una lista VIP—si tu reloj está mal, o no has nacido aún o ya estás muerto. Ninguno de los dos te entra al club.

Un modelo mental práctico del handshake TLS de curl

Cuando curl https://api.example.com falla en Ubuntu 24.04, normalmente estás fallando en una de cuatro puertas:

Puerta 1: Alcanzaste al peer correcto

La conexión TCP tuvo éxito y estás hablando con el sistema que pretendías. Fallos aquí aparecen como timeouts, conexiones rechazadas, o un handshake TLS que parece un galimatías porque en realidad no alcanzaste un endpoint TLS.

Puerta 2: Negociaste un protocolo común

Cliente y servidor deben acordar versión TLS y suite de cifrado. Con TLS 1.3, la semántica de la “lista de suites” difiere de TLS 1.2. Los middleboxes que inspeccionan tráfico a veces manejan mal esta negociación, y verás alertas como protocol_version o handshake_failure.

Puerta 3: Verificaste identidad (SNI + certificado + SAN + cadena)

El servidor presenta una cadena de certificados. El hostname que solicitaste debe coincidir con una entrada de Subject Alternative Name. La cadena debe llevar a una raíz de confianza en tu store local. Si falta SNI o es incorrecta, puedes recibir el certificado equivocado y todo lo demás es que el cliente hace su trabajo al negarse a continuar.

Puerta 4: Verificaste validez temporal

El certificado debe ser válido ahora. Aquí es donde un NTP ligeramente roto se convierte en una caída completa. Los certificados no son indulgentes; no les importa que la VM se reanudara desde una pausa y el reloj despertara en el martes pasado.

Regla operativa: No “arregles” fallos de handshake TLS deshabilitando la verificación (-k) salvo que estés diagnosticando en un sandbox seguro. En producción, es el equivalente de seguridad a quitar la batería del detector de humo porque suena mucho.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son tareas reales que puedes ejecutar en Ubuntu 24.04. Cada una incluye: un comando, una salida de ejemplo, qué significa y qué decisión tomar a continuación.

Task 1: Captura el error real de curl con verbosidad TLS completa

cr0x@server:~$ curl -v https://api.example.com/health
*   Trying 203.0.113.10:443...
* Connected to api.example.com (203.0.113.10) port 443 (#0)
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS alert, handshake failure (552):
* OpenSSL SSL_connect: SSL_ERROR_SSL in connection to api.example.com:443
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SSL in connection to api.example.com:443

Significado: La conexión TCP funcionó; la negociación TLS falló. La alerta vino del peer (o de un middlebox actuando como peer).

Decisión: Pasa a comprobaciones de protocolo/SNI/CA/tiempo. No pierdas tiempo en “si el host es alcanzable.” Lo es.

Task 2: Confirma la hora del sistema y el estado NTP

cr0x@server:~$ timedatectl
               Local time: Mon 2025-12-29 09:18:44 UTC
           Universal time: Mon 2025-12-29 09:18:44 UTC
                 RTC time: Mon 2025-12-29 09:18:43
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Significado: El reloj está sincronizado. La hora probablemente no es la culpable.

Decisión: Si System clock synchronized: no o la hora está mal, arregla NTP antes de cualquier otra cosa. Si está correcta, continúa.

Task 3: Comprueba las fuentes NTP (busca “sincronizado a algo raro”)

cr0x@server:~$ chronyc sources -v
  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.
 / .- Source state '*' = current best, '+' = combined, '-' = not combined.
| /             .- RFC 5905 reachability register.
||               .- Last sample was +/- offset between local clock and source.
|| |   .- Polling interval.  .- Leap status.
|| |   |    |     |         |      |  
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* ntp1.corp.local               2   6   377    22   -153us[-401us] +/-   12ms
^+ ntp2.corp.local               2   6   377    19   +211us[ +19us] +/-   15ms

Significado: Estás sincronizado a fuentes sensatas con pequeños offsets.

Decisión: Si ves una fuente de reloj local (#) o offsets grandes, arregla la hora primero. Depurar TLS con hora mala es autolesión.

Task 4: Valida que la resolución DNS coincide con lo esperado

cr0x@server:~$ getent ahosts api.example.com
203.0.113.10     STREAM api.example.com
203.0.113.10     DGRAM  api.example.com
203.0.113.10     RAW    api.example.com

Significado: El nombre resuelve a una IP. Bien—salvo que esa IP sea incorrecta para tu entorno.

Decisión: Si esta IP es inesperada (región equivocada/VIP), investiga DNS dividido, resolv.conf o el push de DNS del VPN.

Task 5: Verifica que la ruta no está haciendo algo sorprendente

cr0x@server:~$ ip route get 203.0.113.10
203.0.113.10 via 192.0.2.1 dev eth0 src 192.0.2.55 uid 1000
    cache

Significado: El tráfico va por el gateway esperado.

Decisión: Si enruta por un túnel o interfaz inesperada, sospecha proxies/VPNs y prueba desde una ruta de red limpia.

Task 6: Confirma que realmente hablas TLS en ese puerto

cr0x@server:~$ timeout 5 openssl s_client -connect api.example.com:443 -brief
40F7E0A5D37F0000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../ssl/record/ssl3_record.c:358:

Significado: “Wrong version number” suele significar que alcanzaste un servicio no-TLS (HTTP plano en 443) o un proxy que espera CONNECT.

Decisión: Revisa variables de proxy, listeners del balanceador y si deberías usar https_proxy con CONNECT.

Task 7: Prueba SNI explícitamente (la prueba del “cert correcto”)

cr0x@server:~$ openssl s_client -connect 203.0.113.10:443 -servername api.example.com -showcerts </dev/null | head -n 20
CONNECTED(00000003)
---
Certificate chain
 0 s:CN = api.example.com
   i:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
-----BEGIN CERTIFICATE-----
MIIF...
-----END CERTIFICATE-----
---
Server certificate
subject=CN = api.example.com
issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1

Significado: Con SNI obtuviste un certificado para api.example.com. Buena señal.

Decisión: Si el subject es algún certificado por defecto (o un hostname no relacionado), arregla el uso de SNI o verifica que tu DNS/VIP apunte al backend correcto.

Task 8: Ve por qué falla la verificación (cadena, hostname, expiración)

cr0x@server:~$ openssl s_client -connect api.example.com:443 -servername api.example.com -verify_return_error </dev/null
...
depth=0 CN = api.example.com
verify error:num=20:unable to get local issuer certificate
Verification error: unable to get local issuer certificate
Verify return code: 20 (unable to get local issuer certificate)

Significado: No se puede construir la cadena hasta una raíz de confianza localmente. O falta el emisor en tu store de CA, o un proxy está intercambiando certificados por una raíz corporativa no confiada.

Decisión: Inspecciona el emisor y compáralo con tu store de confianza. Si estás en una red corporativa, confirma la instalación de la CA raíz corporativa.

Task 9: Identifica rápidamente el emisor y los SAN

cr0x@server:~$ echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null | openssl x509 -noout -issuer -subject -dates -ext subjectAltName
issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
subject=CN = api.example.com
notBefore=Dec  1 00:00:00 2025 GMT
notAfter=Mar  1 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
    DNS:api.example.com, DNS:api-internal.example.com

Significado: El hostname está presente en SAN; la ventana de validez parece correcta.

Decisión: Si SAN no incluye tu hostname, es un problema de certificado en el servidor (o estás usando el hostname equivocado). Arregla el endpoint, no curl.

Task 10: Verifica el bundle de CA y actualízalo (confianza del sistema)

cr0x@server:~$ dpkg -l | grep -E '^ii\s+ca-certificates\s'
ii  ca-certificates  20240203  all  Common CA certificates

Significado: El paquete del bundle de CA está instalado.

Decisión: Si falta, instálalo. Si está instalado pero obsoleto, actualiza y regenera.

cr0x@server:~$ sudo apt-get update
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Get:2 http://security.ubuntu.com/ubuntu noble-security InRelease [110 kB]
Fetched 110 kB in 1s (168 kB/s)
Reading package lists... Done
cr0x@server:~$ sudo apt-get install --only-upgrade ca-certificates
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
ca-certificates is already the newest version (20240203).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
cr0x@server:~$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

Significado: Tu store de CA del sistema es consistente.

Decisión: Si se añadieron/eliminaron certificados, vuelve a probar curl. Si sigue fallando, sospecha que falta la CA MITM corporativa o que la aplicación usa un bundle específico.

Task 11: Detecta variables de proxy que silencian el handshake

cr0x@server:~$ env | grep -iE 'https?_proxy|no_proxy'
https_proxy=http://proxy.corp.local:3128
http_proxy=http://proxy.corp.local:3128
no_proxy=localhost,127.0.0.1,.corp.local

Significado: curl probablemente hará túnel a través de un proxy, que puede fallar en CONNECT, interceptar TLS o requerir auth.

Decisión: Si el destino debería alcanzarse directamente, deshaz las vars de proxy para la prueba o añade el hostname a no_proxy.

cr0x@server:~$ curl -v --noproxy api.example.com https://api.example.com/health
*   Trying 203.0.113.10:443...
* Connected to api.example.com (203.0.113.10) port 443 (#0)
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
> GET /health HTTP/1.1
< HTTP/1.1 200 OK
ok

Significado: La conexión directa funciona; la ruta por proxy era el problema.

Decisión: Arregla la configuración del proxy (allowlist de CONNECT, auth, o CA corporativa). No “resuelvas” deshabilitando la verificación TLS.

Task 12: Forza HTTP/1.1 para descartar rarezas ALPN/HTTP2

cr0x@server:~$ curl -v --http1.1 https://api.example.com/health
* ALPN: curl offers http/1.1
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
> GET /health HTTP/1.1
< HTTP/1.1 200 OK
ok

Significado: HTTP/1.1 funciona. Si --http2 falla, tienes intolerancia HTTP/2 o ALPN en la ruta (a menudo un proxy o configuración antigua del balanceador).

Decisión: A corto plazo: fija a HTTP/1.1 para ese entorno. A largo plazo: arregla el middlebox o la configuración ALPN del servidor.

Task 13: Forza la versión TLS para aislar problemas de compatibilidad

cr0x@server:~$ curl -v --tlsv1.2 https://api.example.com/health
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
< HTTP/1.1 200 OK
ok

Significado: TLS 1.2 funciona. Si TLS 1.3 falla, algo en la ruta es alérgico a TLS 1.3.

Decisión: Si controlas el servidor, habilita/actualiza TLS 1.3 correctamente. Si no, considera fijar TLS 1.2 como mitigación temporal y crea el ticket adecuado para arreglar la ruta.

Task 14: Ve exactamente qué archivo CA usa curl

cr0x@server:~$ curl -v https://api.example.com/health 2>&1 | grep -iE 'CAfile|CApath'
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs

Significado: curl está usando el bundle de CA del sistema como se espera.

Decisión: Si apunta a otro sitio (bundle personalizado), alínea ese bundle con la confianza del sistema o actualiza ese bundle específicamente.

Task 15: Valida que la CA raíz corporativa esté instalada (cuando hay inspección TLS)

cr0x@server:~$ ls -l /usr/local/share/ca-certificates
total 4
-rw-r--r-- 1 root root 1874 Dec 28 12:10 corp-root-ca.crt
cr0x@server:~$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

Significado: Tu CA raíz corporativa ahora forma parte de la confianza del sistema.

Decisión: Vuelve a probar el comando curl que fallaba. Si ahora funciona, la causa raíz era “MITM no confiado”, no “Internet roto”.

Task 16: Confirma que el servidor devuelve la cadena completa de certificados

cr0x@server:~$ openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null | awk '/BEGIN CERTIFICATE/{c++} END{print c}'
1

Significado: Solo se envió un certificado. Eso suele ser incorrecto a menos que la hoja esté firmada directamente por una raíz (raro para sitios públicos).

Decisión: Arregla la configuración del servidor para enviar intermediarios. No “arregles” clientes distribuyendo intermediarios como contrabando.

Broma #2: Si alguna vez “arreglas” TLS copiando archivos de certificado aleatorios del portátil de un compañero, felicidades—has reinventado la distribución de malware con mejor documentación.

Listas de verificación / plan paso a paso (soluciones rápidas que perduran)

Lista A: Cuando curl falla con (35) o “handshake failure”

  1. Ejecuta curl -v y captura la primera línea de error TLS y cualquier línea CAfile/CApath.
  2. Verifica la hora con timedatectl. Si no está sincronizada, arregla NTP y prueba de nuevo.
  3. Comprueba DNS con getent ahosts. Si resuelve inesperadamente, arregla resolver/VPN/DNS dividido.
  4. Revisa proxies imprimiendo https_proxy/no_proxy. Vuelve a probar usando --noproxy donde corresponda.
  5. Prueba SNI con openssl s_client -servername. Confirma que el certificado leaf y SAN contienen tu hostname.
  6. Comprueba la verificación usando -verify_return_error. Si el emisor no es de confianza, actualiza el store de CA o instala la raíz corporativa.
  7. Aísla ALPN/versión TLS con --http1.1 y --tlsv1.2. Si forzar funciona, has encontrado una falla de compatibilidad.

Lista B: Soluciones permanentes que deberías preferir (en lugar de “curl -k”)

  • Hora: mantén NTP activo y monitoriza deriva en VMs que suspenden/reanudan. Arregla la plataforma, no el síntoma.
  • Confianza: gestiona raíces de CA vía gestión de configuración. Si necesitas una raíz corporativa, instálala una vez y rota deliberadamente.
  • SNI: siempre usa hostnames correctos; no hagas curl por IP salvo que controles --resolve/--connect-to y entiendas la validación de certificados.
  • Proxies: define explícitamente no_proxy para nombres internos. No dejes que variables de entorno te sorprendan en servicios systemd y jobs CI.
  • Higiene del servidor: sirve cadenas completas, usa cifrados modernos y prueba con OpenSSL desde el mismo OS cliente que usa tu automatización.

Lista C: Cuando sospechas de un proxy de inspección TLS empresarial

  1. Confirma que las variables de proxy están establecidas (o aplicadas por política de red).
  2. Obtén e inspecciona el emisor del certificado en la ruta que falla. Si el emisor parece “corporativo”, necesitas la CA raíz corporativa en el host.
  3. Instala la CA raíz corporativa en /usr/local/share/ca-certificates y ejecuta update-ca-certificates.
  4. Vuelve a probar curl. Si funciona, actualiza tus golden images y runners CI para no redescubrir esto el próximo trimestre.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: “certificate has expired” pero el sitio está bien en el navegador

Causa raíz: Hora del sistema incorrecta (a menudo la VM se reanudó) o el cliente está viendo una cadena de certificados diferente (intercepción proxy) que el navegador.

Solución: timedatectl + arregla NTP; luego inspecciona el emisor con openssl s_client. Si es emisor corporativo, instala la CA raíz corporativa.

2) Síntoma: “unable to get local issuer certificate” / “self-signed certificate in certificate chain”

Causa raíz: Falta la raíz/intermedio CA localmente, o un proxy de inspección TLS presenta un leaf emitido por la corporación sin instalar la raíz corporativa.

Solución: Actualiza ca-certificates; instala la CA raíz requerida en /usr/local/share/ca-certificates; ejecuta update-ca-certificates; vuelve a probar.

3) Síntoma: “wrong version number” de OpenSSL / curl

Causa raíz: No estás hablando TLS con un puerto TLS. Casos comunes: alcanzando un endpoint HTTP en 443, proxy que espera CONNECT, o mismatch en listener del balanceador.

Solución: Revisa variables de proxy; usa curl -v y busca CONNECT del proxy; verifica la configuración del listener del servidor; prueba la ruta directa con --noproxy.

4) Síntoma: Funciona con --tlsv1.2 pero falla con el valor por defecto

Causa raíz: Intolerancia a TLS 1.3 en servidor/middlebox, o dispositivo de inspección/IDS con bug.

Solución: Escala a redes/seguridad para parchear o evitar el dispositivo. Mitigación temporal: fijar TLS 1.2 para ese entorno (documentarlo y poner fecha de caducidad).

5) Síntoma: Funciona con --http1.1 pero falla normalmente

Causa raíz: Manejo incorrecto de ALPN/HTTP2 en proxy o balanceador.

Solución: Fuerza HTTP/1.1 en el cliente como parche; arregla soporte ALPN en la ruta. No desactives HTTP/2 globalmente a menos que disfrutes regresiones lentas que nadie correlaciona.

6) Síntoma: Handshake falla solo para un hostname en la misma IP

Causa raíz: Mismatch de SNI o enrutamiento virtual host equivocado. Sin SNI correcto, el servidor presenta el certificado por defecto.

Solución: Asegura que los clientes usen el hostname, no la IP cruda. Confirma con openssl s_client -servername. Arregla la configuración vhost del servidor si hace falta.

7) Síntoma: Funciona en un host Ubuntu pero no en otro

Causa raíz: Diferentes raíces de CA instaladas, diferente entorno de proxy, distintas características de build de OpenSSL/curl, o un host tiene store de CA obsoleto.

Solución: Compara curl -V, dpkg -l ca-certificates, env de proxy, y salida de update-ca-certificates. Haz consistente tu baseline.

Tres mini-historias corporativas (anonimizadas, técnicamente precisas)

Incidente causado por una suposición errónea: “Es solo una renovación de certificado, inofensiva”

Un equipo rotó un certificado público en un API gateway tarde un viernes. El ticket decía “solo renovación” y el on-call lo aprobó porque el CN siguió igual y la CA era reputada. Todo parecía rutinario y las pruebas en navegador pasaron.

En minutos, la flota de automatización Linux empezó a fallar: health checks basados en curl, descargas de paquetes desde un repo privado y una herramienta de despliegue interna que usaba libcurl. El error clásico fue: unable to get local issuer certificate. Los ingenieros supusieron que el bundle de CA en clientes estaba desactualizado. Empezaron a desplegar apt-get install --reinstall ca-certificates por máquinas como si fuera una vacuna.

La verdadera causa: el gateway estaba mal configurado y solo servía el certificado leaf, no el intermedio. Los navegadores a menudo tenían en caché los intermediarios, así que “funcionaba en mi laptop.” Los clientes sin cabeza no tenían ese intermedio en caché y rechazaron la cadena.

La solución fue aburrida y del lado servidor: actualizar la configuración TLS del gateway para presentar la cadena completa. La lección fue más punzante: “renovación de certificado” no es una etiqueta semántica; es un cambio de configuración que puede romper la presentación de la cadena. Después añadieron una comprobación en CI basada en OpenSSL que cuenta cadenas antes de promover la configuración TLS.

Optimización que salió mal: “Forcemos HTTP/2 en todas partes”

Un grupo de plataforma decidió estandarizar HTTP/2 para llamadas internas. La idea no era mala: multiplexación reduce conexiones, mejora latencia bajo carga y funciona bien con stacks TLS modernos. Hicieron un cambio en un wrapper curl compartido usado en jobs y agentes: por defecto --http2.

En una semana, una región empezó a ver fallos intermitentes de handshake TLS. No timeouts—handshakes reales muriendo con alertas. Los reintentos a veces funcionaban, a veces no. Todos culparon “pérdida de paquetes”, porque eso es lo que decimos cuando no queremos aprender cómo funciona ALPN.

El culpable fue un clúster de proxies que hacía inspección TLS y enforcement de políticas. Nominalmente soportaba HTTP/2 pero tenía una rareza: ciertos valores SNI de backend activaban un path legacy que no podía manejar la selección ALPN h2 de forma fiable. Cuando el cliente ofrecía h2, el proxy ocasionalmente lo negociaba y luego rompía el establecimiento del stream, reportando un fallo genérico de handshake corriente arriba.

La mitigación práctica fue forzar HTTP/1.1 para los dominios afectados vía configuración por host, no global. La corrección duradera requirió que el equipo de red parcheara y reconfigurara el proxy. El postmortem fue claro: “Las actualizaciones de protocolo son cambios de producción. Despliegalos como cualquier otro cambio, con canarios, métricas y rollback.”

Práctica aburrida pero correcta que salvó el día: “Imágenes golden con confianza gestionada”

Otra organización corría un entorno mixto: nube pública, on-prem y redes reguladas con inspección TLS obligatoria. Ya les habían quemado antes los fallos “funciona en dev, falla en prod”. Así que hicieron algo poco sexy: una baseline única para confianza CA y configuración de proxy aplicada a cada imagen Ubuntu y runner CI.

Cuando llegó Ubuntu 24.04, ya tenían un paso en pipeline que validaba conectividad TLS a endpoints críticos usando openssl s_client -servername y curl -v con expectativas conocidas: emisor correcto, SAN correcto y sincronización de hora. También controlaban dónde se podían establecer variables de proxy (drop-ins de systemd para servicios, configuración explícita para CI) en lugar de dejar que perfiles de shell dispersaran la configuración.

Una mañana, el equipo de seguridad rotó la raíz corporativa usada para inspección. Esa rotación puede ser un baño de sangre si se descubre host por host. Ellos no lo descubrieron. La nueva CA ya estaba staged en las imágenes y desplegada vía gestión de configuración antes de que la rotación completara, con monitorización de errores de verificación de certificados para detectar rezagados.

El salvamento no fue magia; fue hábito. La práctica “aburrida”—store de confianza gestionado, imágenes reproducibles y cheques TLS preflight—convirtió lo que pudo haber sido un incidente multi-equipo en un no-evento más un registro de cambio ordenado.

Preguntas frecuentes

1) ¿Por qué curl falla pero mi navegador funciona?

Los navegadores cachean intermediarios, traen su propia lógica de confianza y pueden usar configuraciones de proxy diferentes. Curl suele depender del store de CA del sistema y de variables de entorno. Prueba la cadena con openssl s_client -showcerts y cuenta certificados; verifica qué emisor estás viendo realmente.

2) ¿Es aceptable curl -k alguna vez?

Como diagnóstico temporal en un host no productivo para confirmar “es un problema de confianza”, sí. Como solución, no. Si necesitas saltar la verificación, no estás usando TLS por seguridad—lo estás usando por apariencia.

3) ¿Cómo sé si SNI es el problema?

Si conectando por IP funciona solo con -servername (OpenSSL) o si ves un certificado por defecto/no relacionado sin SNI, esa es la respuesta. Ejecuta openssl s_client -connect IP:443 -servername hostname y compara con el resultado sin -servername.

4) ¿Cuál es la forma más rápida de confirmar un proxy de inspección TLS corporativo?

Revisa variables de entorno de proxy y luego inspecciona el emisor del certificado presentado. Si el emisor es tu empresa (o la CA de un appliance de seguridad) en lugar de una CA pública, estás siendo interceptado. Instala la CA raíz corporativa en la confianza del sistema.

5) ¿Por qué forzar TLS 1.2 “lo arregla”?

No lo arregla; lo evita. Algunos middleboxes manejan mal TLS 1.3. Forzar TLS 1.2 es diagnóstico y parche mientras actualizas la ruta.

6) ¿Qué archivos usa Ubuntu 24.04 para CAs de confianza?

Típicamente /etc/ssl/certs/ca-certificates.crt (bundle) y el directorio hashed /etc/ssl/certs. Añades CAs locales en /usr/local/share/ca-certificates y ejecutas update-ca-certificates.

7) ¿Los problemas de DNS pueden parecer fallos de handshake TLS?

Absolutamente. Si resuelves un hostname a la IP equivocada (VIP equivocada, región equivocada, portal cautivo, DNS dividido mal enrutado), el servidor presentará un certificado que no coincide con tu hostname o terminará TLS de forma diferente. Verifica resolución y enrutamiento temprano.

8) ¿Cómo depuro cuando un proxy requiere autenticación?

Con curl -v, busca respuestas CONNECT del proxy (407 Proxy Authentication Required). Configura credenciales de proxy de forma segura (no en el historial de shell), o asegúrate de que no_proxy excluya el destino interno.

9) ¿Y si el servidor no envía intermediarios?

Arregla el servidor. Servir una cadena completa es responsabilidad del servidor. Hacks del lado cliente (llevar intermediarios) crean comportamiento inconsistente y automatización frágil.

10) ¿Por qué los contenedores fallan cuando el host funciona?

Los contenedores pueden tener un bundle de CA distinto, no tener bundle de CA, o comportamientos de sincronización de hora diferentes. Asegura que la imagen de contenedor incluya ca-certificates y que use la hora correcta del host (la mayoría sí, salvo configuraciones exóticas).

Conclusión: próximos pasos que puedes hacer hoy

Si los handshakes TLS de curl fallan en Ubuntu 24.04, deja de tratarlos como un misterio. Ejecuta el playbook rápido:

  1. Confirma sincronización de hora (timedatectl, chronyc sources).
  2. Confirma DNS y ruta (getent ahosts, ip route get).
  3. Revisa implicación de proxies (env | grep -i proxy, luego curl --noproxy).
  4. Verifica SNI y cadena (openssl s_client -servername, inspecciona SAN/emisor/fechas).
  5. Sólo entonces aísla rarezas de protocolo (forzar HTTP/1.1, TLS 1.2) y escala al equipo correcto con evidencia.

Luego haz el seguimiento adulto: estandariza la confianza CA en imágenes, mantén NTP sano y trata las “optimizaciónes” de protocolo como cambios de producción. Tu yo del futuro seguirá de guardia. Hazle la noche más tranquila.

← Anterior
ZFS zpool iostat -w: Comprender patrones de carga en tiempo real
Siguiente →
Pedidos lentos en el admin de WooCommerce: encuentra consultas pesadas y acelera

Deja un comentario