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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

docker network create proxy
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verifica que los contenedores estén corriendo:

docker ps
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verificar que el certificado se generó

docker exec nginx-proxy ls /etc/nginx/certs/
Enter fullscreen mode Exit fullscreen mode

Ver la configuración generada de Nginx

Útil para debuggear problemas:

docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Y móntalo en el proxy:

volumes:
  - ./app.ejemplo.com_location:/etc/nginx/vhost.d/app.ejemplo.com_location:ro
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Y móntalo:

volumes:
  - ./www.ejemplo.com:/etc/nginx/vhost.d/www.ejemplo.com:ro
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)