Cómo configurar un Reverse Proxy NGINX con Docker Compose y SSL automático

Exponer contenedores de aplicaciones web directamente a Internet publicando puertos arbitrarios en el host (como :3000, :8080 o :5000) es una de las prácticas más inseguras e ineficientes en la ingeniería de operaciones.

La arquitectura estándar en entornos DevOps exige concentrar todo el tráfico web entrante a través de un Reverse Proxy (Proxy Inverso) centralizado con NGINX. Este patrón permite la terminación TLS/SSL en un único punto, balanceo de carga, enrutamiento por nombres de dominio (FQDN) y aislamiento estricto de los contenedores en redes privadas internas de Docker. Si ejecutas este stack sobre máquinas virtuales en centros de datos, puedes integrarlo perfectamente con nuestra Guía Maestra de virtualización en Proxmox VE 8.

// EJECUCIÓN RÁPIDA (TL;DR) – ARQUITECTURA DE PRODUCCIÓN

# 1. Crear la red bridge externa compartida:

docker network create proxy-network

# 2. Estructura de carpetas recomendada:nginx-proxy/ ├── docker-compose.yml ├── nginx.conf └── certs/

# 3. Desplegar el stack en segundo plano:

docker compose up -d

1. Diseño de la Arquitectura de Red en Docker

Para evitar colisiones de puertos y garantizar que el proxy pueda resolver los contenedores por su nombre de servicio (DNS interno de Docker), utilizaremos una red de tipo bridge definida por el usuario. De esta forma, ningún contenedor backend expone puertos directamente al host físico; únicamente NGINX expone los puertos estándar 80 (HTTP) y 443 (HTTPS).

2. Manifiesto ‘docker-compose.yml’ con Certbot Integrado

El siguiente archivo orquesta el servidor web NGINX junto con el cliente oficial Certbot de Let’s Encrypt, programado para verificar la renovación del certificado cada 12 horas de forma transparente:

services:
  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    networks:
      - proxy-network

  certbot:
    image: certbot/certbot
    container_name: certbot-ssl
    restart: unless-stopped
    volumes:
      - ./certbot/conf:/etc/letsencrypt:rw
      - ./certbot/www:/var/www/certbot:rw
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

networks:
  proxy-network:
    external: true

3. Configuración Segura de NGINX (‘nginx.conf’)

Crea el archivo conf.d/app.conf para definir la redirección forzada de HTTP a HTTPS y el enrutamiento hacia tu contenedor de aplicación (por ejemplo, una API en Node.js o Python):

# Redirección global de HTTP a HTTPS y desafío ACME de Certbot
server {
    listen 80;
    listen [::]:80;
    server_name app.tudominio.com;

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

    location / {
        return 301 https://$host$request_uri;
    }
}

# Bloque HTTPS seguro
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name app.tudominio.com;

    ssl_certificate /etc/letsencrypt/live/app.tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.tudominio.com/privkey.pem;

    # Parámetros de seguridad TLS recomendados
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Encabezados de proxy para preservar la IP real del cliente
    location / {
        proxy_pass http://mi-contenedor-backend:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Nota técnica de diagnóstico: Si al iniciar tu contenedor backend NGINX devuelve un error de conexión hacia el upstream, consulta de inmediato nuestra guía para solucionar el error NGINX 502 Bad Gateway con Docker Proxy Pass.

4. Emisión del Certificado SSL Inicial con Certbot

Dado que NGINX no podrá arrancar si los archivos .pem especificados en la directiva SSL aún no existen, aplica el siguiente flujo de inicialización por única vez:

  1. Comenta temporalmente el bloque server { listen 443 ... } en tu archivo de configuración para permitir que NGINX levante solo en el puerto 80.
  2. Levanta el contenedor: docker compose up -d nginx.
  3. Solicita el certificado SSL ejecutando el contenedor Certbot de forma interactiva: docker run -it --rm --name certbot-init \ -v "$(pwd)/certbot/conf:/etc/letsencrypt" \ -v "$(pwd)/certbot/www:/var/www/certbot" \ certbot/certbot certonly --webroot -w /var/www/certbot \ -d app.tudominio.com --email tu-correo@dominio.com --agree-tos --no-eff-email Si el proceso falla en la validación del dominio, revisa los pasos de resolución para reparar el error Certbot Challenge Failed for Domain.
  4. Descomenta el bloque HTTPS en app.conf y recarga la configuración sin downtime: docker exec -it nginx-proxy nginx -s reload

5. Conexión de Contenedores de Aplicación a la Red

Para que cualquier otra aplicación (Node.js, WordPress, Python) sea accesible a través del proxy inverso, solo debes añadirla a la red proxy-network en su respectivo docker-compose.yml:

services:
  mi-aplicacion:
    image: mi-app:latest
    container_name: mi-contenedor-backend
    restart: always
    networks:
      - proxy-network

networks:
  proxy-network:
    external: true

Monitoreo y Buenas Prácticas en Producción

En entornos con alto volumen de tráfico, los contenedores backend pueden saturar la memoria RAM asignada por el kernel de Linux. Asegúrate de configurar límites de memoria adecuados en los servicios para evitar interrupciones por el temido error OOMKilled (Exit Code 137). Asimismo, si ejecutas orquestación avanzada en clusters, mantén monitorizados tus servicios para anticiparte a fallos de inicialización como el estado CrashLoopBackOff en Kubernetes.

Preguntas Frecuentes

¿Por qué no usar Traefik o NGINX Proxy Manager en lugar de NGINX puro?

Herramientas como NGINX Proxy Manager o Traefik ofrecen interfaces visuales cómodas, pero consumen más memoria RAM y añaden capas de abstracción innecesarias que dificultan el diagnóstico en fallos de red. NGINX en Alpine Linux consume menos de 15 MB de RAM, soporta decenas de miles de conexiones concurrentes y ofrece control total sobre las directivas de seguridad del servidor web.

¿Qué sucede cuando se renueva el certificado SSL?

Certbot renovará automáticamente los archivos en el volumen compartido ./certbot/conf. Para que NGINX cargue las nuevas claves en memoria sin reiniciar el contenedor, puedes programar un cron job en el host que ejecute periódicamente: docker exec nginx-proxy nginx -s reload.


Guía de ingeniería DevOps y arquitectura de microservicios validada por Gustavo Dulcey, especialista en Despliegue de Contenedores y Seguridad en Redes Cloud.

Deja un comentario