Cómo solucionar el error Certbot Challenge Failed for Domain en NGINX Docker

Intentar emitir o renovar un certificado SSL gratuito con Let’s Encrypt y encontrarse con el mensaje Certbot failed to authenticate some domains (HTTP-01): Challenge failed for domain es el problema más recurrente al desplegar contenedores web en producción.

Para verificar que tú eres el propietario legítimo del dominio, los servidores de validación de Let’s Encrypt intentan descargar un archivo de token temporal alojado en la ruta pública http://tudominio.com/.well-known/acme-challenge/<TOKEN>. En arquitecturas contenerizadas, este desafío HTTP-01 suele fracasar debido al problema del huevo o la gallina: NGINX no puede arrancar porque no encuentra los archivos SSL, pero Certbot no puede generar los certificados porque NGINX no está levantado para responder el desafío en el puerto 80. Si estás estructurando este entorno sobre nuestra Guía Maestra de Reverse Proxy NGINX con Docker y SSL, aquí tienes el protocolo exacto para resolverlo.

// EJECUCIÓN RÁPIDA (TL;DR) – LA SOLUCIÓN EN 3 COMPROBACIONES

# 1. El bloque ACME en NGINX DEBE ir ANTES de cualquier redirección HTTPS:

location /.well-known/acme-challenge/ { root /var/www/certbot; }

# 2. Prueba manual de escritura y visibilidad del token desde fuera:

docker exec -it certbot-ssl touch /var/www/certbot/test.txt && curl -I http://tudominio.com/.well-known/acme-challenge/test.txt

Si curl devuelve “HTTP/1.1 200 OK”, Certbot emitirá el certificado en segundos. Si devuelve 404 o 301, el volumen está mal mapeado o hay una redirección prematura.

1. Análisis del Mensaje de Error de Certbot

Cuando ejecutas el cliente ACME, la consola suele reportar un código de estado HTTP emitido por los servidores de Let’s Encrypt. Identificar este código ahorra horas de depuración:

  • Invalid response from domain (404 Not Found): Let’s Encrypt alcanzó tu servidor NGINX en el puerto 80, pero el archivo del token no estaba en la ruta física esperada. Esto indica una discrepancia en el montaje de volúmenes compartidos entre el contenedor de Certbot y el de NGINX.
  • Connection refused o Timeout: El tráfico hacia el puerto TCP 80 está bloqueado por el cortafuegos de tu proveedor cloud (AWS Security Groups, firewall de Hetzner o UFW en el host).
  • Invalid response (301 Moved Permanently): NGINX interceptó la petición en el puerto 80 y forzó una redirección hacia HTTPS antes de evaluar el directorio del desafío. Como el certificado aún no existe, la conexión HTTPS aborta.

Causa 1: La Trampa de la Redirección Forzada a HTTPS (301)

Muchos archivos de configuración incluyen una regla global de redirección para garantizar que todo el tráfico viaje seguro. Sin embargo, si colocas la directiva return 301 https://$host$request_uri; al inicio del bloque del servidor en el puerto 80, romperás el desafío de Let’s Encrypt.

Configuración correcta del bloque HTTP en NGINX:

server {
    listen 80;
    listen [::]:80;
    server_name app.tudominio.com;

    # REGLA EXCEPCIONAL PARA CERTBOT (Debe ir en primer lugar)
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
        allow all;
    }

    # Redirección para el resto del tráfico
    location / {
        return 301 https://$host$request_uri;
    }
}

Causa 2: Discrepancia en el Volumen Compartido Webroot

En Docker, Certbot genera físicamente el token dentro de su propio contenedor y NGINX lo lee desde el suyo. Si las rutas en el host no están perfectamente espejadas en el docker-compose.yml, NGINX buscará en un directorio vacío.

Mapeo simétrico en Docker Compose:

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./certbot/www:/var/www/certbot:ro   # NGINX solo necesita permisos de lectura

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/www:/var/www/certbot:rw   # Certbot requiere permisos de escritura

Asegúrate de que la carpeta local exista en el host ejecutando: mkdir -p ./certbot/www/.well-known/acme-challenge.

Causa 3: La Trampa del Registro IPv6 (AAAA)

Este es uno de los fallos más difíciles de detectar. Let’s Encrypt tiene una política técnica estricta: si tu dominio tiene configurado un registro DNS de tipo AAAA (IPv6), intentará validar el desafío prioritariamente a través de IPv6.

Si tu proveedor de dominio configuró un registro IPv6 por defecto pero tu servidor VPS en Hostinger o Hetzner solo tiene direccionamiento IPv4 (registro A), la verificación fallará con un error de timeout. Ejecuta en tu máquina local:

dig AAAA app.tudominio.com +short

Si devuelve una IP pero tu servidor no tiene interfaz IPv6 configurada en el kernel de Linux, elimina el registro AAAA de tu panel DNS y espera a que propague.

Causa 4: El Proxy Nube de Cloudflare (Cloud Naranja)

Si utilizas Cloudflare para gestionar tus DNS y el modo proxy está activo (nube naranja), Cloudflare intercepta el tráfico entrante. Si tu configuración de SSL en Cloudflare está en modo Flexible o Off, puede alterar los encabezados del desafío ACME y devolver respuestas inesperadas a Let’s Encrypt.

Solución rápida para la emisión:

  1. En el panel de Cloudflare, cambia temporalmente el registro DNS a modo Solo DNS (nube gris).
  2. Ejecuta la emisión del certificado con Certbot.
  3. Una vez emitido el archivo fullchain.pem, reactiva el proxy de Cloudflare y ajusta la encriptación a Full (Strict).

Comando de Emisión Manual en Modo Standalone (Plan de Contingencia)

Si la configuración de NGINX está demasiado dañada y necesitas emitir el certificado de emergencia sin intermediarios, puedes detener NGINX temporalmente y permitir que Certbot levante su propio servidor web integrado:

# 1. Detener el contenedor de NGINX para liberar el puerto 80
docker stop nginx-proxy

# 2. Ejecutar Certbot en modo standalone
docker run -it --rm --name certbot-standalone \
  -p 80:80 \
  -v "$(pwd)/certbot/conf:/etc/letsencrypt" \
  certbot/certbot certonly --standalone \
  -d app.tudominio.com --email contacto@gustavodulcey.com --agree-tos --no-eff-email

# 3. Volver a iniciar NGINX con los certificados ya creados
docker start nginx-proxy

Estabilidad Post-Emisión

Una vez que el certificado esté activo, NGINX podrá iniciar la terminación TLS sin interrupciones. Recuerda que si el contenedor de tu aplicación后端 colapsa por consumo desmedido de memoria durante peticiones pesadas, la pasarela devolverá errores al cliente; mantén monitorizados tus servicios revisando nuestra guía para corregir el error OOMKilled en contenedores y los diagnósticos de conexión hacia el upstream en solucionar el error NGINX 502 Bad Gateway con Docker.

Preguntas Frecuentes

¿Por qué Let’s Encrypt exige abrir el puerto 80 si mi sitio solo usa HTTPS?

El estándar RFC 8555 que define el protocolo ACME exige que el desafío HTTP-01 comience obligatoriamente en el puerto TCP 80. Aunque tu web esté blindada para servir contenido exclusivamente por el puerto 443, el puerto 80 debe permanecer abierto en el firewall para responder a las solicitudes de renovación periódica.

¿Existe un límite de intentos si Certbot sigue fallando?

Sí. Let’s Encrypt aplica un límite estricto de 5 fallos de validación por cuenta, por nombre de host y por hora. Si alcanzas ese umbral, tu dominio quedará bloqueado temporalmente. Durante las pruebas, añade siempre el flag --dry-run al comando de Certbot para verificar la configuración contra el entorno de pruebas (Staging) sin consumir tu cuota oficial.


Manual técnico de seguridad perimetral y certificados SSL elaborado por Gustavo Dulcey, Ingeniero de Sistemas especializado en Operaciones DevOps e Infraestructura Web.

Deja un comentario