Despliegas un registro privado de Docker. Funciona para ti. Funciona para un runner de CI. Luego un nuevo nodo se une al clúster y de repente: x509: certificate signed by unknown authority. La gente empieza a “arreglarlo” alternando insecure-registries porque son las 2 a.m. y el pager suena fuerte.
No lo hagas. Los errores TLS alrededor de los registros son uno de esos problemas donde la solución correcta es aburrida, repetible y mucho más barata que el parche heroico. La solución equivocada es rápida, frágil y vuelve como una mala secuela—normalmente durante un congelamiento de despliegues.
Qué significan realmente los errores TLS del registro Docker
La mayoría de los “errores TLS del registro Docker” no son problemas de Docker. Son fallos estándar de validación TLS que se muestran a través de la pila cliente de Docker (la librería TLS de Go), el daemon y a veces containerd. Tu registro es simplemente un servidor HTTPS con opiniones.
Cuando ejecutas:
docker login registry.example.comdocker pull registry.example.com/team/app:tagkubectl applyy los nodos empiezan a descargar imágenes
…el cliente espera que el servidor presente un certificado que:
- Coincida con el nombre de host que usaste (Subject Alternative Name, no solo CN).
- Sea válido actualmente (no expirado, y dentro de NotBefore/NotAfter).
- Encadene hasta una CA raíz de confianza en la máquina que está realizando la descarga.
- Incluya los intermedios requeridos (o que pueda obtenerlos vía AIA, lo cual es poco fiable en redes con acceso restringido).
- Use tamaños de clave y algoritmos de firma aceptables.
Los mensajes de error de Docker suelen ser cortos y poco románticos, como:
x509: certificate signed by unknown authorityx509: certificate is valid for ..., not registry.example.comremote error: tls: bad certificatetls: handshake failure
Cada uno de esos mapea a un modo de fallo específico. Puedes diagnosticarlo con pasos deterministas. No necesitas “probar cosas” en producción.
Broma #1: TLS es como un portero—si tu cadena de confianza parece falsa, no entras al club, por más alto que grites que estás “en la lista”.
Hechos y contexto que puedes usar en un postmortem
Algo de contexto ayuda, porque los fallos TLS a menudo vienen de la historia—configuraciones antiguas, hábitos viejos, infraestructura heredada.
- Docker trasladó temprano las decisiones de confianza al daemon. El daemon hace las descargas, así que su almacén de confianza importa más que el de tu shell.
- Subject Alternative Name reemplazó la validación por CN hace años. Los clientes modernos ignoran el viejo Common Name para la validación de hostname; SAN es obligatorio.
- Los CAs intermedios se hicieron comunes porque las raíces están muy controladas. Muchas PKI públicas y privadas emiten certificados leaf desde intermedios, no directamente desde raíces.
- La obtención por AIA es real pero no garantizada. Algunas pilas TLS pueden descargar intermedios faltantes vía Authority Information Access, pero el comportamiento de Docker depende del entorno y del acceso de red.
- Let’s Encrypt cambió el comportamiento de cadenas con el tiempo. La selección de cadena y las rotaciones de intermedios han causado sorpresas de “funcionaba ayer” cuando los servidores sirven la cadena equivocada.
- Clientes antiguos fallan con algoritmos nuevos y viceversa. Un registro con solo certificados ECDSA puede romper builds con OpenSSL antiguo; RSA-only puede ser más lento pero ampliamente compatible.
- La inspección TLS corporativa rompe supuestos. Proxies transparentes que re-firman certificados implican que tu almacén de confianza interno ahora es una dependencia, te guste o no.
- Los runtimes de contenedores evolucionaron. En muchos sistemas Docker usa containerd internamente; Kubernetes puede usar containerd directamente. Las rutas de almacén de confianza y la semántica de recarga difieren.
- “Insecure registries” fue pensado para desarrollo, no producción. Desactiva la verificación crítica. No es una “solución temporal”; es una decisión de política con deuda técnica de seguridad.
Una cita operativa para mantenerte honesto: “La esperanza no es una estrategia.” — General Gordon R. Sullivan. Aplica dolorosamente bien a las cadenas de certificados.
Guía rápida de diagnóstico
Si estás de guardia, no quieres una lección. Quieres un camino rápido del síntoma a la causa raíz.
Primero: confirma qué endpoint está alcanzando realmente el cliente
- ¿Es
registry.example.como el nombre de un balanceador de carga? - ¿Estás usando un puerto como
:5000? - ¿Hay un proxy o inspección MITM TLS?
Si el nombre de host difiere de lo que cubre el certificado, asunto resuelto: arregla DNS o los SAN.
Segundo: inspecciona lo que el servidor presenta (cadena + SAN)
Usa openssl s_client contra el endpoint del registro desde la máquina que falla. No lo inspecciones desde tu portátil y asumas que es el mismo camino.
Si el servidor presenta solo el certificado leaf (faltan intermedios), corrige la configuración del servidor. No “enseñes a cada nodo” a compensar los intermedios faltantes si el registro es el que está fallando.
Tercero: determina dónde falta la confianza
- Si
curlfalla y Docker falla: el almacén de confianza de la máquina no tiene la CA o la cadena es incorrecta. - Si
curlfunciona pero Docker falla: la configuración de confianza del daemon Docker difiere, o estás usando containerd con su propia configuración de confianza. - Si solo fallan pulls en Kubernetes: la confianza del runtime del nodo difiere de la de tu shell interactivo.
Cuarto: comprueba la hora, porque la hora lo arruina todo
El desajuste de reloj hace que certificados válidos parezcan inválidos. Las interrupciones de NTP causan “errores TLS aleatorios” que parecen problemas de PKI.
Quinto: resiste usar “insecure-registries” a menos que aceptes explícitamente el riesgo
Te dará builds verdes. También normaliza la omisión de la verificación, que es como acabas enviando credenciales al endpoint equivocado más tarde.
Modelo mental de la cadena de certificados (para que dejes de adivinar)
Una cadena de certificados TLS es una historia que el servidor le cuenta al cliente: “Yo soy registry.example.com, y así es como puedes creerme.” La historia tiene personajes:
- Certificado leaf: emitido para los nombres de host de tu registro. Esto es lo que “es” tu servidor.
- Certificado(s) intermedio(s): la CA que emitió el leaf. Estos conectan el leaf a una raíz.
- Certificado raíz: el ancla de confianza. Esto es lo que el cliente ya confía.
Los clientes normalmente confían en las raíces, no en los intermedios. Los intermedios típicamente no están en el almacén de confianza del SO a menos que el proveedor de CA los incluya. Por eso los servidores deben a menudo presentar la cadena intermedia.
Qué significa realmente “arreglar la cadena”
Significa: tu endpoint del registro debe presentar el certificado leaf más todos los intermedios requeridos en el orden correcto, y el cliente debe tener un ancla de confianza (raíz) que valide esa cadena.
En servidores web típicos, esa es la diferencia entre servir:
- Incorrecto:
cert.pem(solo leaf) - Correcto:
fullchain.pem(leaf + intermedios)
Además: tu CA raíz privada debe instalarse en cada nodo que vaya a descargar imágenes, incluidos runners CI efímeros y nodos Kubernetes autoescalados. Si emites desde una raíz privada que no está en la confianza del sistema, nada más importa.
Matiz específico de Docker: ubicación de confianza y comportamiento de recarga
Docker no consume automáticamente todos los certificados que pongas en el SO. El daemon usa la confianza del sistema en muchas distribuciones, pero Docker también soporta bundles de confianza por registro en:
/etc/docker/certs.d/registry.example.com:5000/ca.crt
Y containerd tiene sus propios ajustes según la distribución y la distribución de Kubernetes. Traducción: necesitas saber qué runtime hace la descarga y dónde lee las CAs.
Tareas prácticas: comandos, salidas, decisiones (12+)
Éstas son las tareas que realmente ejecuto cuando fallan los pulls del registro. Cada una incluye el comando, qué significa una salida típica y la decisión que tomas. Ejecútalas desde la máquina que está fallando (nodo, runner, agente de build), no desde la máquina que desearías que estuviera fallando.
Task 1: Confirmar el host:puerto exacto del registro que usa Docker
cr0x@server:~$ docker image pull registry.example.com:5000/team/app:1.2.3
Error response from daemon: Get "https://registry.example.com:5000/v2/": x509: certificate signed by unknown authority
Qué significa: Docker está usando https://registry.example.com:5000 y falla la verificación de confianza.
Decisión: Todas las comprobaciones subsiguientes deben dirigirse a registry.example.com:5000, incluidos los SAN y la ruta del bundle de confianza bajo certs.d.
Task 2: Comprobar DNS y enrutamiento básicos (sí, en serio)
cr0x@server:~$ getent hosts registry.example.com
10.20.30.40 registry.example.com
Qué significa: El nombre resuelve a 10.20.30.40.
Decisión: Si esto difiere entre nodos (DNS de horizonte dividido), puedes estar alcanzando distintos balanceadores con certificados distintos.
Task 3: Inspeccionar la cadena presentada y los SAN
cr0x@server:~$ echo | openssl s_client -connect registry.example.com:5000 -servername registry.example.com -showcerts 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
subject=CN = registry.example.com
issuer=CN = Corp Issuing CA 01
notBefore=Dec 1 00:00:00 2025 GMT
notAfter=Dec 1 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
DNS:registry.example.com, DNS:registry
Qué significa: El certificado leaf cubre registry.example.com; bien. El emisor es un intermedio (“Corp Issuing CA 01”).
Decisión: Si los SAN no incluyen el nombre de host exacto que usan los clientes, reemite el certificado. No confíes en CN. Si las fechas están mal, corrige la rotación o el reloj.
Task 4: Ver si el servidor está enviando intermedios
cr0x@server:~$ echo | openssl s_client -connect registry.example.com:5000 -servername registry.example.com -showcerts 2>/dev/null | awk '/BEGIN CERTIFICATE/{i++} END{print i}'
1
Qué significa: Solo se envía un certificado (el leaf). Es una mala configuración clásica si los clientes no tienen ya el intermedio.
Decisión: Arregla la configuración TLS del registro para servir la cadena completa (leaf + intermedios). Normalmente éste es el lugar correcto para resolverlo.
Task 5: Verificar la cadena localmente con un bundle CA específico
cr0x@server:~$ openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /tmp/registry-leaf.pem
CN = registry.example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error /tmp/registry-leaf.pem: verification failed
Qué significa: El almacén de confianza del sistema no puede construir la cadena desde el leaf hasta una raíz de confianza (falta intermedio y/o raíz).
Decisión: O bien instala la CA raíz corporativa correcta en el almacén de confianza del sistema, o bien corrige el servidor para presentar intermedios (o ambos, dependiendo del diseño PKI).
Task 6: Comprobar que el almacén de confianza del SO conoce tu raíz corporativa
cr0x@server:~$ sudo grep -R --line-number "Corp Root CA" /etc/ssl/certs 2>/dev/null | head
/etc/ssl/certs/Corp_Root_CA.pem:1:-----BEGIN CERTIFICATE-----
Qué significa: La raíz corporativa está presente en el directorio de confianza del sistema (al menos en este host).
Decisión: Si está presente pero la verificación sigue fallando, te faltan intermedios en el servidor o no estás encadenando realmente a esa raíz.
Task 7: Probar con curl para aislar “Docker vs sistema”
cr0x@server:~$ curl -vkI https://registry.example.com:5000/v2/
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* SSL certificate problem: unable to get local issuer certificate
curl: (60) SSL certificate problem: unable to get local issuer certificate
Qué significa: Esto no es Docker siendo especial; el host mismo no puede validar la cadena del servidor.
Decisión: Arregla la cadena/confianza primero. No toques la configuración de Docker todavía.
Task 8: Confirmar que el daemon Docker ve algún bundle CA por registro
cr0x@server:~$ ls -la /etc/docker/certs.d/registry.example.com:5000/
total 12
drwxr-xr-x 2 root root 4096 Jan 3 09:12 .
drwxr-xr-x 3 root root 4096 Jan 3 09:12 ..
-rw-r--r-- 1 root root 1984 Jan 3 09:12 ca.crt
Qué significa: Docker tiene un archivo CA por registro disponible.
Decisión: Si este archivo falta en los nodos que fallan, instálalo (o mejor, arregla la cadena del servidor y distribuye sólo tu CA raíz vía la gestión estándar de confianza del SO).
Task 9: Reiniciar Docker después de cambiar la confianza (porque no va a leerte la mente)
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ sudo systemctl is-active docker
active
Qué significa: Docker se reinició correctamente.
Decisión: Si el reinicio falla, resuelve los problemas de configuración del daemon antes de volver a probar pulls. Los cambios de confianza que no se cargan no cuentan.
Task 10: Inspeccionar la configuración del daemon Docker en busca de atajos inseguros
cr0x@server:~$ cat /etc/docker/daemon.json
{
"log-driver": "json-file",
"insecure-registries": ["registry.example.com:5000"]
}
Qué significa: Alguien pasó por alto la verificación TLS para este registro.
Decisión: Elíminalo una vez que TLS esté arreglado correctamente. Si lo mantienes, aceptas riesgo MITM para todas las descargas de ese endpoint.
Task 11: Verificar qué archivo de certificado está sirviendo realmente tu endpoint de registro (ejemplo Nginx)
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -n "ssl_certificate"
57: ssl_certificate /etc/nginx/certs/registry.crt;
58: ssl_certificate_key /etc/nginx/certs/registry.key;
Qué significa: Nginx está sirviendo registry.crt. Eso podría ser solo el leaf.
Decisión: Asegura que ssl_certificate apunte a un PEM con la cadena completa (leaf + intermedios). Luego recarga Nginx.
Task 12: Confirmar que el archivo PEM contiene realmente la cadena completa
cr0x@server:~$ awk '/BEGIN CERTIFICATE/{i++} END{print i}' /etc/nginx/certs/registry.crt
2
Qué significa: Dos certificados en el PEM (probablemente leaf + intermedio). Eso suele ser lo deseado.
Decisión: Si imprime 1, probablemente estás sirviendo solo el leaf. Arregla el archivo y recarga el servidor.
Task 13: Recargar de forma segura el frontend del registro (Nginx)
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
cr0x@server:~$ sudo systemctl is-active nginx
active
Qué significa: La configuración es válida y la recarga tuvo éxito. Las conexiones existentes deberían sobrevivir; los nuevos handshakes usan los nuevos certificados.
Decisión: Prefiere recargar sobre reiniciar durante horas laborales.
Task 14: Volver a probar la presentación de la cadena tras el cambio en el servidor
cr0x@server:~$ echo | openssl s_client -connect registry.example.com:5000 -servername registry.example.com -showcerts 2>/dev/null | awk '/BEGIN CERTIFICATE/{i++} END{print i}'
2
Qué significa: El servidor ahora envía más que el certificado leaf.
Decisión: Vuelve a probar con curl y luego con docker pull. Si aún falla, puede faltar el ancla de confianza.
Task 15: Validar con curl de nuevo (ahora debería tener éxito)
cr0x@server:~$ curl -sSI https://registry.example.com:5000/v2/ | head -n 5
HTTP/1.1 401 Unauthorized
Server: nginx
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="https://registry.example.com:5000/token",service="registry.example.com:5000"
Date: Fri, 03 Jan 2026 09:27:31 GMT
Qué significa: TLS tuvo éxito; la autenticación HTTP es ahora el “problema”, lo cual es normal. El registro espera credenciales.
Decisión: Pasa a las pruebas de Docker login/pull. Tu capa TLS ya está en buen estado.
Task 16: Confirmar que Docker ahora puede hablar TLS a /v2/
cr0x@server:~$ docker login registry.example.com:5000
Username: cr0x
Password:
Login Succeeded
Qué significa: La validación TLS y la autenticación funcionaron.
Decisión: Propaga la misma corrección a todos los nodos/runners. Luego elimina cualquier configuración de registro inseguro.
Task 17: Comprobar la hora del nodo si la validez del certificado parece incorrecta
cr0x@server:~$ timedatectl status | sed -n '1,8p'
Local time: Fri 2026-01-03 09:28:02 UTC
Universal time: Fri 2026-01-03 09:28:02 UTC
RTC time: Fri 2026-01-03 09:28:03
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
Qué significa: El reloj está sincronizado.
Decisión: Si no está sincronizado, arregla NTP antes de culpar a la PKI. Errores de expirado/no válido a menudo vienen de deriva de tiempo.
Tres microhistorias corporativas desde las trincheras
1) El incidente causado por una suposición equivocada: “El balanceador de carga maneja la cadena”
Una empresa mediana ejecutaba un registro privado detrás de un balanceador de capa 7. El equipo de plataforma rotaba certificados trimestralmente. Asumieron que el balanceador servía la cadena completa porque los navegadores estaban contentos. Los navegadores, por supuesto, son indulgentes: cachean intermedios, los obtienen vía AIA, “simplemente funcionan” hasta que dejan de hacerlo.
Luego desplegaron nuevos nodos worker Linux en un pool autoescalado. Máquinas recién creadas, imagen base mínima, egress bloqueado. Esos nodos no podían obtener intermedios desde Internet público, y no tenían el intermedio cacheado de sesiones previas porque eran recién nacidos.
El modo de fallo fue maravillosamente repetitivo: cada nodo nuevo fallaba en los pulls de imágenes con x509: certificate signed by unknown authority. Los nodos antiguos seguían funcionando porque sus caches de confianza e historial enmascaraban el defecto. El incidente se interpretó erróneamente como “el autoescalado está roto” y “containerd es inestable”. No lo era. La cadena estaba incompleta.
La solución fue vergonzosamente simple: configurar el balanceador para presentar la cadena completa (leaf + intermedio) y validar usando openssl s_client desde un nodo nuevo sin estado cacheado. La lección real: los navegadores no son una prueba de cumplimiento para tus clientes de infraestructura.
2) La optimización que salió mal: “Usaremos solo ECDSA”
Otra organización decidió modernizar TLS. Generaron certificados ECDSA porque son más rápidos y pequeños, y endurecieron el ingress del registro para preferir cifrados modernos. Fue un cambio elegante en la teoría y una gran diapositiva en una revisión de seguridad.
Luego una flota de builds legada empezó a fallar. Algunos runners tenían una pila OpenSSL antigua compilada sin soporte para la curva adecuada; un par de appliances de proveedor hacían terminación TLS y re-encriptación con soporte limitado de algoritmos. El registro no estaba “caído”, pero era selectivamente inalcanzable.
La primera reacción del equipo fue revertir. La segunda reacción fue mejor: ofrecer estrategia dual—soportar RSA y ECDSA donde sea factible, o al menos confirmar que todos los clientes en alcance pueden negociar los algoritmos elegidos. También aprendieron a probar con la flota real, no con un único portátil moderno.
El problema no fue que ECDSA sea malo. El problema fue creer que “nuestra infraestructura” es homogénea. Rara vez lo es.
3) La práctica aburrida pero correcta que salvó el día: “Trata la distribución de CA como un artefacto de release”
Un equipo fintech ejecutaba varios servicios internos usando una PKI privada, incluido su registro de contenedores. Tenían una regla: la CA raíz corporativa y los intermedios requeridos se empaquetaban y distribuían como cualquier otra dependencia—versionados, con checksum y desplegados vía su tooling de gestión de configuración.
Cuando rotaron su CA intermedia (planificado, anunciado, ensayado), actualizaron primero el paquete “trust bundle”. Los nodos lo recibieron gradualmente. Solo después de que la flota informó cumplimiento empezaron a emitir nuevos certificados leaf para endpoints como el registro.
El día de la rotación fue anticlimático. Unos pocos rezagados fallaron pulls—predeciblemente los servidores “snowflake” sin gestión—y se corrigieron con las herramientas estándar. No hubo incidente a medianoche. No hubo ajustes de emergencia de insecure registries. No hubo “¿por qué solo un clúster está roto?” misterioso.
Fue aburrido. Aburrido es lo que deseas. Broma #2: Si la rotación de certificados es emocionante, estás haciendo teatro en vivo, no operaciones.
Errores comunes: síntomas → causa raíz → arreglo
1) Síntoma: x509: certificate signed by unknown authority
Causa raíz: El cliente no puede construir una cadena hasta una raíz de confianza. O bien la CA raíz no está instalada, o el servidor no está enviando intermedios, o ambas cosas.
Arreglo: Prefiere arreglar la presentación de la cadena en el servidor (servir fullchain). También asegura que la CA raíz correcta esté instalada en la confianza del SO y/o en /etc/docker/certs.d/ para ese registro.
2) Síntoma: x509: certificate is valid for foo, not registry.example.com
Causa raíz: SANs incorrectos. A menudo ocurre cuando se emiten certificados para un nombre interno pero los clientes usan un nombre externo, o cuando el nombre del balanceador difiere del del registro.
Arreglo: Reemite el certificado leaf con SANs para cada nombre que usen los clientes. No lo tapes con hacks DNS a menos que controles a todos los llamantes.
3) Síntoma: Funciona en el navegador, falla en Docker
Causa raíz: El navegador obtuvo intermedios vía AIA o tenía intermedios cacheados; el host de Docker no los tenía. O bien la confianza del daemon Docker difiere de la del usuario en el SO.
Arreglo: Valida con openssl s_client -showcerts desde el nodo que falla. Sirve la cadena completa en el servidor. Confirma el bundle de confianza de Docker.
4) Síntoma: Algunos nodos pueden descargar, nodos nuevos no pueden
Causa raíz: Intermedios cacheados, imágenes base inconsistentes o distribución de CA inconsistente. El autoescalado expone la deriva.
Arreglo: Estandariza la instalación de CA en la imagen o en el bootstrap. Prueba en un nodo limpio sin historial TLS previo.
5) Síntoma: tls: handshake failure sin más pistas
Causa raíz: Incompatibilidad de protocolo/cifrado, problemas SNI o un proxy en el medio. A veces el registro habla TLS solo en un puerto y estás golpeando otro.
Arreglo: Usa openssl s_client con -servername. Confirma versiones TLS y cifrados en servidor y clientes. Identifica cualquier proxy interceptando.
6) Síntoma: remote error: tls: bad certificate en el cliente
Causa raíz: El servidor rechazó la parte cliente del handshake. Esto puede ocurrir con mTLS mal configurado (se requieren certificados cliente), o con una cadena de servidor rota que confunde la pila del lado servidor.
Arreglo: Revisa logs del servidor (nginx, envoy, registry). Confirma si se requieren certificados cliente. Si es así, configura Docker/cliente para mTLS o deshabilita el requisito para endpoints de registro.
7) Síntoma: Pull falla solo en Kubernetes, no en un bastión
Causa raíz: El almacén de confianza del runtime del nodo falta la CA, o los nodos usan containerd directamente y no leen certs.d de Docker. O la descarga pasa por un proxy interno en los nodos.
Arreglo: Depura en el nodo. Instala la CA donde el runtime la espera. Para containerd, usa sus mecanismos de configuración de certificados (varía por distro). Evita asumir que “la configuración de Docker aplica”.
8) Síntoma: De pronto falla tras rotación de certificados
Causa raíz: Se introdujo un intermedio nuevo, orden de cadena incorrecto, intermedio expirado aún servido, o el leaf fue emitido por una CA nueva que todavía no confían los clientes.
Arreglo: Sirve la cadena completa correcta. Distribuye previamente nuevas raíces/intermedios antes de rotar leafs. Valida desde un cliente limpio.
Listas de verificación / plan paso a paso
Paso a paso: arreglar la cadena de certificados de un registro privado (la manera sensata)
- Inventario de llamantes. Enumera cada sistema que descarga: portátiles de desarrolladores, runners CI, nodos Kubernetes, builders air-gapped, cajas edge.
- Identifica el punto de terminación TLS. ¿Es el propio registro, Nginx, Envoy/Ingress o un balanceador?
- Confirma los nombres de host que usan los clientes. Incluye variantes con puerto. Incluye nombres DNS internos.
- Emite un certificado leaf con SANs correctos. No negocies con la realidad; si los clientes usan tres hostnames, SAN necesita esos tres hostnames.
- Construye un PEM fullchain. Concatena leaf y luego intermedios (sin la raíz a menos que tu plataforma la espere específicamente; muchas no lo hacen).
- Configura el endpoint TLS para servir fullchain. Nginx/Envoy/Ingress deben presentar intermedios.
- Valida la cadena desde un host limpio. Usa
openssl s_client -showcertsycurl. - Distribuye anclas de confianza correctamente. Instala la CA raíz corporativa en la confianza del SO de cada nodo capaz de descargar; opcionalmente añade bundles CA por registro.
- Recarga servicios. Recarga Nginx/Ingress. Reinicia Docker/containerd si es necesario para aplicar actualizaciones de confianza.
- Elimina atajos inseguros. Borra entradas de
insecure-registriesy cualquier excepción HTTP para registros. - Automatiza renovación y recarga. La rotación de certificados sin automatización es solo el plan de interrupción de tu yo futuro.
- Pon un canario. Un pull periódico desde un pool de nodos limpio detecta regresiones de cadena temprano.
Lista operativa: antes de culpar a la PKI
- ¿Resuelve DNS a la IP esperada desde el nodo que falla?
- ¿Está sincronizado el reloj del nodo que falla?
- ¿Estás detrás de un proxy corporativo de inspección TLS?
- ¿Estás usando SNI correctamente (hostname coincide con
-servername)? - ¿Se reconfiguró recientemente un balanceador de carga?
Lista operativa: antes de aceptar “insecure registries”
- ¿Has demostrado que el servidor falta intermedios?
- ¿Has demostrado que el cliente falta la CA raíz?
- ¿Has comprobado si los SAN del certificado coinciden con el hostname usado?
- ¿Has documentado el riesgo y un plan de reversión?
Si no puedes responder a eso, no estás tomando una decisión; estás creando un desastre.
Preguntas frecuentes
1) ¿Debo poner la CA raíz en el archivo fullchain del servidor?
Usualmente no. Los servidores típicamente envían leaf + intermedios; los clientes ya tienen (o deberían tener) la raíz. Enviar la raíz puede confundir a algunos clientes y desperdicia bytes. Si tu entorno lo exige, documenta la excepción y prueba ampliamente.
2) ¿Por qué mi navegador confía en el registro pero Docker no?
Los navegadores cachean intermedios y pueden obtener los faltantes automáticamente. Docker en un host mínimo a menudo no tiene esos intermedios cacheados y puede no obtenerlos. Trata el éxito en navegador como “agradable”, no como prueba definitiva.
3) ¿Dónde instalo una CA personalizada para Docker?
La mejor práctica es instalar tu CA raíz corporativa en el almacén de confianza del SO para que todo se beneficie. Docker también soporta CAs por registro en /etc/docker/certs.d/<host:port>/ca.crt. Tras los cambios, reinicia Docker.
4) Arreglé la cadena en el servidor, pero los nodos Kubernetes siguen fallando en pulls. ¿Por qué?
Kubernetes puede usar containerd directamente, y los nodos podrían no tener tu CA instalada en el almacén del SO o en la ubicación que espera el runtime. Depura en el nodo, no en tu estación de trabajo.
5) ¿Cuál es la diferencia entre “autoridad desconocida” y “válido para X, no para Y”?
“Autoridad desconocida” es un problema de cadena de confianza (CA/raíz/intermedio). “Válido para X, no para Y” es un problema de validación de hostname (mismatch de SAN). Tienen arreglos diferentes; no los confundas.
6) ¿Puedo usar temporalmente insecure-registries?
Puedes, en el mismo sentido en que puedes desactivar tus detectores de humo mientras cocinas. Puede reducir el ruido, pero también elimina un control de seguridad. Úsalo sólo con aprobación explícita y una fecha de caducidad planificada.
7) ¿Necesito reiniciar Nginx/Ingress después de actualizar archivos de certificados?
Sí, necesitas al menos una recarga para que el proceso cargue el nuevo certificado y la cadena. Valida con openssl s_client después de la recarga, no antes.
8) ¿Cómo evito esta interrupción durante la rotación de certificados?
Distribuye previamente nuevos anclajes de confianza (raíces/intermedios) antes de cambiar leafs, sirve cadenas completas y valida desde hosts limpios. Trata la distribución de CAs como un artefacto gestionado, no como conocimiento tribal.
9) ¿Por qué falla solo en nodos completamente nuevos?
Los nodos frescos exponen intermedios faltantes y CAs raíz ausentes porque no tienen certificados cacheados, y a menudo tienen almacenes de confianza mínimos más estrictos. Por eso el autoescalado es un gran auditor.
10) ¿Un registro es solo HTTPS, o Docker requiere algo especial?
A nivel de transporte es HTTPS con expectativas del cliente. A nivel de aplicación es la API Docker Registry. Si TLS está correcto, normalmente obtendrás 401 Unauthorized desde /v2/ cuando no te autentiques, lo cual es una señal saludable.
Conclusión: próximos pasos que no te perseguirán
Arreglar los errores TLS del registro Docker “correctamente” trata menos de Docker y más de disciplina con la PKI. Sirve la cadena completa. Usa SANs correctos. Instala los anclajes de confianza correctos en cada máquina que descargue imágenes. Luego borra los hacks inseguros que agregaste durante el pánico.
Pasos prácticos siguientes:
- Ejecuta la guía rápida de diagnóstico desde el nodo que falla y captura salidas para las notas del incidente.
- Actualiza la terminación TLS del registro para servir leaf + intermedios (verifica con
openssl s_client -showcerts). - Estandariza la distribución de CAs entre nodos/runners (confianza del SO primero;
/etc/docker/certs.dsolo si es necesario). - Añade un pull canario desde un entorno limpio para detectar regresiones de cadena antes que tus desarrolladores.
- Elimina
insecure-registriesy trata cualquier reintroducción como una excepción de seguridad con fecha de caducidad.
Si haces esas cinco cosas, el TLS del registro dejará de ser un drama recurrente y se convertirá en lo que debería haber sido siempre: ruido de fondo que nunca escuchas.