DEV Community

Cover image for Reverse Proxy en Docker con Nginx y SSL automático
Pablo Bagliere
Pablo Bagliere

Posted on

Reverse Proxy en Docker con Nginx y SSL automático

Código Rápido

Si solo necesitas el código para configurar tu reverse proxy con Nginx y certificados SSL automáticos, aquí está:

services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - certs:/etc/nginx/certs
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
    networks:
      - proxy
    restart: always

  letsencrypt:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-letsencrypt
    volumes_from:
      - nginx-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - acme:/etc/acme.sh
    environment:
      - DEFAULT_EMAIL=tu-email@ejemplo.com
    depends_on:
      - nginx-proxy
    networks:
      - proxy
    restart: always

volumes:
  certs:
  vhost:
  html:
  acme:

networks:
  proxy:
    external: true

Para usar con tus aplicaciones, agrega estas variables de entorno:

services:
  tu-aplicacion:
    image: tu-imagen
    environment:
      - VIRTUAL_HOST=tudominio.com
      - LETSENCRYPT_HOST=tudominio.com
    networks:
      - proxy

Nota: Asegúrate de crear la red proxy antes de iniciar los contenedores:

docker network create proxy

Introducción

Un reverse proxy es una herramienta esencial cuando manejas múltiples aplicaciones en Docker. En lugar de recordar diferentes puertos para cada servicio (como :8080, :3000, etc.), un reverse proxy te permite acceder a tus aplicaciones usando dominios o subdominios normales.

En este tutorial, vamos a usar nginx-proxy, una solución automatizada que:

  • Detecta automáticamente tus contenedores Docker
  • Configura rutas basadas en nombres de dominio
  • Genera certificados SSL automáticamente con Let's Encrypt
  • Renueva los certificados antes de que expiren

¿Qué es nginx-proxy?

nginx-proxy es una imagen de Docker que ejecuta Nginx y genera automáticamente configuraciones de reverse proxy para otros contenedores Docker utilizando docker-gen. La magia ocurre porque monitorea el socket de Docker y detecta cuándo inicias o detienes contenedores.

Cuando un contenedor tiene la variable de entorno VIRTUAL_HOST, nginx-proxy automáticamente:

  1. Crea una configuración de Nginx para ese host
  2. Redirige el tráfico del dominio hacia ese contenedor
  3. Actualiza la configuración si reinicias el contenedor

Contenedores necesarios

1. nginx-proxy (nginxproxy/nginx-proxy)

Este es el contenedor principal que maneja todo el tráfico HTTP/HTTPS entrante.

Puertos expuestos:

  • 80:80 — Tráfico HTTP estándar
  • 443:443 — Tráfico HTTPS cifrado

Volúmenes importantes:

  • /var/run/docker.sock:/tmp/docker.sock:ro — Acceso de solo lectura al socket de Docker para detectar contenedores
  • certs:/etc/nginx/certs — Almacena los certificados SSL
  • vhost:/etc/nginx/vhost.d — Configuraciones personalizadas por virtual host
  • html:/usr/share/nginx/html — Archivos estáticos para validación de Let's Encrypt

2. acme-companion (nginxproxy/acme-companion)

Este contenedor complementario se encarga de obtener y renovar certificados SSL de Let's Encrypt automáticamente.

Nota: Si usas Cloudflare con proxy activado (nube naranja), Cloudflare ya te proporciona certificados SSL. En ese caso, puedes omitir este contenedor y todo sigue funcionando igual.

¿Cómo funciona?

  • Detecta contenedores con LETSENCRYPT_HOST
  • Solicita certificados SSL para esos dominios
  • Configura Nginx para usar HTTPS
  • Renueva certificados automáticamente cada 60 días

Volúmenes importantes:

  • volumes_from: nginx-proxy — Comparte los volúmenes del proxy para acceder a certs, vhost y html
  • acme:/etc/acme.sh — Persiste el estado de ACME entre reinicios

Nota: Let's Encrypt dio de baja el servicio de notificaciones por email en enero de 2025. La variable DEFAULT_EMAIL sigue siendo requerida para el registro, pero ya no recibirás notificaciones de renovación.

Configuración paso a paso

Paso 1: Crear la red de Docker

Primero, necesitas crear una red externa que conectará el proxy con tus aplicaciones:

docker network create proxy

Esta red permite que los contenedores se comuniquen entre sí de forma segura.

Paso 2: Crear la carpeta para el docker-compose

mkdir -p nginx-proxy
cd nginx-proxy

Paso 3: Crear el archivo docker-compose.yml

Crea un archivo docker-compose.yml con el código mostrado al principio del tutorial.

Importante: Reemplaza tu-email@ejemplo.com con tu email real.

Paso 4: Iniciar el reverse proxy

docker compose up -d

Verifica que los contenedores estén corriendo:

docker ps

Cómo conectar tus aplicaciones

Para que una aplicación use el reverse proxy, solo necesitas agregar las variables de entorno correspondientes y conectarla a la red proxy.

Ejemplo con una aplicación Node.js

services:
  mi-api:
    image: node:20-alpine
    container_name: mi-api
    working_dir: /app
    volumes:
      - ./:/app
    command: npm start
    environment:
      - VIRTUAL_HOST=api.ejemplo.com
      - VIRTUAL_PORT=3000
      - LETSENCRYPT_HOST=api.ejemplo.com
    networks:
      - proxy
    restart: always

networks:
  proxy:
    external: true

Explicación de las variables

Variable Descripción Requerida
VIRTUAL_HOST El dominio que apuntará a este contenedor
VIRTUAL_PORT Puerto interno de la aplicación (por defecto 80) Solo si no es 80
LETSENCRYPT_HOST Dominio para obtener certificado SSL Solo con acme-companion

Múltiples dominios

Si quieres que un contenedor responda a múltiples dominios:

environment:
  - VIRTUAL_HOST=app.ejemplo.com,www.app.ejemplo.com
  - LETSENCRYPT_HOST=app.ejemplo.com,www.app.ejemplo.com

Esto genera un único certificado válido para ambos dominios.

Ejemplo: Múltiples aplicaciones

Un caso común es tener varias aplicaciones corriendo en el mismo servidor. Aquí un ejemplo con una API, un frontend y una base de datos:

services:
  # Backend API
  api:
    image: node:20-alpine
    container_name: mi-api
    working_dir: /app
    volumes:
      - ./api:/app
    command: npm start
    environment:
      - VIRTUAL_HOST=api.ejemplo.com
      - VIRTUAL_PORT=3000
      - LETSENCRYPT_HOST=api.ejemplo.com
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    networks:
      - proxy
      - internal
    restart: always

  # Frontend
  frontend:
    image: node:20-alpine
    container_name: mi-frontend
    working_dir: /app
    volumes:
      - ./frontend:/app
    command: npm start
    environment:
      - VIRTUAL_HOST=app.ejemplo.com
      - VIRTUAL_PORT=3000
      - LETSENCRYPT_HOST=app.ejemplo.com
    networks:
      - proxy
    restart: always

  # Base de datos (sin exponer al proxy)
  db:
    image: postgres:16-alpine
    container_name: mi-db
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal
    restart: always

volumes:
  postgres_data:

networks:
  proxy:
    external: true
  internal:
    driver: bridge

En este ejemplo:

  • La API y el frontend están expuestos a través del proxy con sus respectivos dominios
  • La base de datos solo está en la red internal, no accesible desde internet
  • La API puede comunicarse con la base de datos a través de la red internal

Conectar una aplicación con su propio docker-compose

Si ya tenés una aplicación con su propio docker-compose.yml y su red interna, podés conectarla al proxy agregando la red proxy como externa:

# docker-compose.yml de tu aplicación existente
services:
  app:
    image: tu-imagen
    environment:
      - VIRTUAL_HOST=app.ejemplo.com
      - VIRTUAL_PORT=8080
      - LETSENCRYPT_HOST=app.ejemplo.com
    networks:
      - default      # red interna de este compose
      - proxy        # red del reverse proxy
    restart: always

  redis:
    image: redis:alpine
    networks:
      - default      # solo red interna
    restart: always

networks:
  default:
    driver: bridge
  proxy:
    external: true

Configuración DNS

Para que todo funcione, necesitas apuntar tus dominios a tu servidor. Ejemplo de registro DNS:

Tipo Nombre Valor TTL
A api 203.0.113.50 3600
A app 203.0.113.50 3600

Verificación

Verificar logs del proxy

docker logs nginx-proxy
docker logs nginx-proxy-letsencrypt

Verificar que el certificado se generó

docker exec nginx-proxy ls /etc/nginx/certs/

Ver la configuración generada de Nginx

Útil para debuggear problemas:

docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf

Problemas comunes

1. El certificado no se genera

  • Verifica que el dominio apunte correctamente a tu servidor con dig tudominio.com
  • Asegúrate de que los puertos 80 y 443 estén abiertos en tu firewall
  • Revisa los logs: docker logs nginx-proxy-letsencrypt
  • Let's Encrypt tiene límites de rate: máximo 5 certificados por dominio por semana

2. Error "502 Bad Gateway"

  • Verifica que el contenedor esté en la red proxy: docker network inspect proxy
  • Confirma que el contenedor esté corriendo: docker ps
  • Si tu app no usa puerto 80, agrega VIRTUAL_PORT

3. La aplicación usa un puerto diferente al 80

Frameworks como Next.js, NestJS, Express, etc., suelen usar puertos como 3000 u 8080. Especifícalo así:

environment:
  - VIRTUAL_HOST=app.ejemplo.com
  - VIRTUAL_PORT=3000

Configuraciones avanzadas

Forzar HTTPS (redireccionar HTTP a HTTPS)

Por defecto, nginx-proxy permite tanto HTTP como HTTPS. Para forzar HTTPS y redireccionar todo el tráfico HTTP, agrega esta variable a tu aplicación:

environment:
  - VIRTUAL_HOST=app.ejemplo.com
  - LETSENCRYPT_HOST=app.ejemplo.com
  - HTTPS_METHOD=redirect

Opciones disponibles para HTTPS_METHOD:

Valor Comportamiento
redirect Redirecciona HTTP a HTTPS (recomendado)
noredirect Permite HTTP y HTTPS sin redirección
nohttps Solo HTTP, deshabilita HTTPS

Soporte para WebSockets

Si tu aplicación usa WebSockets (Socket.io, notificaciones en tiempo real, etc.), nginx-proxy los soporta automáticamente. No necesitas configuración adicional.

Sin embargo, si tenés problemas de conexión, podés crear un archivo de configuración para ese host. Crea app.ejemplo.com_location con:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;

Y móntalo en el proxy:

volumes:
  - ./app.ejemplo.com_location:/etc/nginx/vhost.d/app.ejemplo.com_location:ro

Redirección www a non-www (o viceversa)

Para redirigir www.ejemplo.com a ejemplo.com, crea un archivo www.ejemplo.com en la carpeta vhost.d:

return 301 https://ejemplo.com$request_uri;

Y móntalo:

volumes:
  - ./www.ejemplo.com:/etc/nginx/vhost.d/www.ejemplo.com:ro

Importante: Asegúrate de que ambos dominios (www.ejemplo.com y ejemplo.com) tengan LETSENCRYPT_HOST configurado para que se generen los certificados.

Cambiar tamaño máximo de subida de archivos

Nginx por defecto limita las subidas a 1 MB. Puedes cambiarlo de forma global o por host.

Para todos los hosts

Crea un archivo my_proxy.conf junto al docker-compose:

client_max_body_size 100m;

Agrégalo como volumen:

services:
  nginx-proxy:
    # ... resto de la configuración
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro
      - certs:/etc/nginx/certs
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html

Para un host específico

Crea el archivo con el nombre exacto del dominio en /etc/nginx/vhost.d/:

volumes:
  - ./my_proxy.conf:/etc/nginx/vhost.d/app.ejemplo.com:ro

Después reinicia con docker compose up -d.

Headers personalizados y configuración de proxy

Para agregar headers o configuraciones específicas de proxy por host, crea un archivo app.ejemplo.com_location:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;

Y móntalo en /etc/nginx/vhost.d/app.ejemplo.com_location.

Consideraciones de seguridad

El socket de Docker (/var/run/docker.sock) da acceso completo al daemon de Docker. Al montarlo en un contenedor, ese contenedor podría potencialmente controlar otros contenedores. Por eso:

  • Se monta como solo lectura (:ro)
  • nginx-proxy es una imagen oficial y ampliamente auditada
  • En entornos de alta seguridad, considera usar docker-socket-proxy

Conclusión

Con nginx-proxy y acme-companion tienes una solución robusta y automática para manejar múltiples aplicaciones con HTTPS. Lo mejor es que:

  • ✅ Los certificados se renuevan automáticamente
  • ✅ Agregar nuevas aplicaciones es solo agregar variables de entorno
  • ✅ No necesitas configurar Nginx manualmente
  • ✅ Todo está contenedorizado y es fácil de mantener

Ahora puedes enfocarte en desarrollar tus aplicaciones sin preocuparte por la configuración del servidor web.

Top comments (0)