Introducción
Con la creciente disponibilidad de servicios de soporte para el desarrollo de IA como ChatGPT, Claude y Google Gemini, incluso aquellos con experiencia limitada en programación ahora pueden desarrollar e implementar aplicaciones web a gran escala. Sin embargo, para operar estas aplicaciones de manera segura, son esenciales las medidas de seguridad adecuadas.
Aquí es donde la tecnología de “proxy inverso” entra en foco. En particular, un proxy inverso equipado con una función de renovación automática de certificados SSL/TLS es una herramienta increíblemente importante para desarrolladores individuales y equipos pequeños.
¿Qué es un Proxy Inverso?

En pocas palabras, un proxy inverso es como un “servidor gateway” que se sitúa frente a su aplicación web.
Por ejemplo:
- Cuando un usuario accede a un sitio web, el gateway (proxy inverso) es el primero en responder.
- El gateway puede detectar visitantes sospechosos y gestionar la congestión del tráfico.
- Cifra las interacciones con los usuarios (habilitando HTTPS) para evitar la interceptación de información.
- Enruta adecuadamente el acceso a múltiples aplicaciones.
¿Por qué es Necesario un Proxy Inverso?
1. Seguridad Mejorada
- Oculta las direcciones IP y los números de puerto reales de los servidores de aplicaciones.
- Bloquea el acceso no autorizado.
- Cifra la comunicación con HTTPS (compatible con TLS 1.2/1.3).
- Mitiga los ataques DDoS.
2. Gestión Operativa Simplificada
- Gestiona múltiples aplicaciones bajo un solo dominio.
- Centraliza la gestión de certificados SSL (con renovación automática).
- Centraliza la gestión de registros de acceso.
- Proporciona funcionalidad de verificación de estado (health check).
3. Rendimiento Mejorado
- Almacena en caché el contenido estático.
- Reduce el volumen de transferencia de datos con compresión gzip.
- Permite la comunicación de alta velocidad con HTTP/2.
- Distribuye la carga (balanceo de carga).
Lo que Puede Lograr con Esta Guía
Esta guía le ayudará a construir el siguiente entorno basado en las mejores prácticas actuales:
✅ Renovación automática de certificados SSL gratuitos (usando Let’s Encrypt)
✅ Configuración moderna usando Docker Compose V2
✅ Seguridad robusta con TLS 1.2/1.3
✅ Instrucciones detalladas paso a paso fáciles de entender para principiantes
✅ Soporte completo para la solución de problemas
Configuración del Sistema
Este proyecto utiliza contenedores Docker para ejecutar Nginx y Certbot, creando un entorno de servidor web seguro y automatizado.
Componentes Clave
1. Contenedor Nginx (Proxy Inverso)
- Recibe el acceso externo y lo reenvía a los servidores de aplicaciones internos.
- Actúa como el punto final HTTPS (terminación SSL/TLS).
- Utiliza una imagen ligera basada en Alpine Linux 3.20 (aprox. 10MB).
- Logra una comunicación de alta velocidad con soporte para HTTP/2.
2. Contenedor Certbot (Gestión de Certificados SSL)
- Obtiene certificados SSL gratuitos de Let’s Encrypt.
- Maneja la renovación automática de certificados (verifica cada 12 horas, renovable 30 días antes del vencimiento).
- Aplica automáticamente los certificados renovados a Nginx sin tiempo de inactividad.
- Soporta el protocolo ACME v2.
Rol de los Archivos de Configuración
| Nombre de archivo | Rol | Importancia |
|---|---|---|
nginx.conf | Configuraciones básicas de Nginx (procesos de trabajo, formato de registro, etc.) | ★★★ |
default.conf | Configuraciones específicas del sitio (configuración SSL, reglas de proxy, etc.) | ★★★ |
docker-compose.yml | Gestión de la configuración de contenedores (formato Docker Compose V2) | ★★★ |
init-letsencrypt.sh | Script de configuración inicial | ★★★ |
.env | Variables de entorno (nombre de dominio, dirección de correo electrónico, etc.) | ★★☆ |


Medidas de Seguridad
Este sistema incorpora las siguientes características de seguridad:
1. Cifrado de la Comunicación
# Usar solo TLS 1.2 y 1.3 (los protocolos más antiguos están deshabilitados)
ssl_protocols TLSv1.2 TLSv1.3;
# Suites de cifrado
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# Asegurar Perfect Forward Secrecy
ssl_prefer_server_ciphers off;
2. Cabeceras de Seguridad HTTP
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Otras cabeceras de seguridad
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
3. Gestión Automatizada de Certificados
- Monitorea automáticamente el vencimiento de los certificados SSL.
- Inicia la renovación automática 30 días antes del vencimiento.
- Función de reintento automático en caso de fallo de renovación.
- Gestiona los permisos de los certificados (protegidos con permisos 600).
4. Control de Acceso
- Redirección automática de HTTP a HTTPS (Redirección Permanente 301).
- Función de limitación de velocidad (protección contra DDoS).
- Restricción de acceso basada en la dirección IP (opcional).
Requisitos Previos y Configuración del Entorno
Entorno Requerido
Antes de comenzar la configuración, asegúrese de tener el siguiente entorno:
1. Requisitos del Servidor
- SO: Ubuntu 22.04 LTS / Debian 12 / CentOS 9 / Rocky Linux 9, etc.
- Memoria: Mínimo 512MB (se recomienda 1GB o más).
- Almacenamiento: Mínimo 10GB (incluido el espacio para los archivos de registro).
- CPU: 1 núcleo o más.
2. Software Requerido
- Docker Engine: v24.0 o superior (se recomienda la última versión estable).
- Docker Compose: v2.20 o superior (versión del plugin).
3. Requisitos de Red
- Dirección IP Pública (es deseable una IP fija).
- Los puertos 80 y 443 deben estar abiertos.
- La configuración del firewall debe estar configurada adecuadamente.
4. Requisitos del Dominio
- Un dominio personalizado (por ejemplo, example.com).
- El registro A del dominio debe apuntar a la dirección IP del servidor.
Instalación de Docker y Docker Compose
Para Ubuntu/Debian:
# 1. Instalar los paquetes necesarios
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# 2. Agregar la clave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 3. Configurar el repositorio de Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 4. Instalar Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 5. Verificar la instalación
docker --version
docker compose version # ¡Tenga en cuenta el espacio en "docker compose"!
Agregar el usuario actual al grupo de docker (para ejecutar sin sudo):
sudo usermod -aG docker $USER
# Cierre la sesión y vuelva a iniciarla para aplicar los cambios
newgrp docker
Verificación de la Configuración de DNS
Verifique si su dominio apunta correctamente a su servidor:
# Verificar el registro A
dig +short su-dominio.com
# O
nslookup su-dominio.com
Si se muestra la dirección IP de su servidor, la configuración es correcta.
Explicación Detallada de los Archivos Clave
docker-compose.yml (formato Docker Compose V2)
# Cumple con la especificación de Docker Compose V2
services: # 'version' ya no es necesario (desde v2.20)
nginx-proxy:
image: nginx:alpine
container_name: nginx-proxy
restart: unless-stopped # Configuración de reinicio automático
ports:
- "80:80"
- "443:443"
volumes:
# Archivos de configuración de Nginx
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./default.conf:/etc/nginx/conf.d/default.conf:ro
# Certificados SSL (compartidos con Certbot)
- ./data/certbot/conf:/etc/letsencrypt:ro
- ./data/certbot/www:/var/www/certbot:ro
networks:
- webproxy
depends_on:
- certbot
# Recarga automática de Nginx en la renovación del certificado
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot:latest
container_name: certbot
restart: unless-stopped
volumes:
- ./data/certbot/conf:/etc/letsencrypt:rw
- ./data/certbot/www:/var/www/certbot:rw
networks:
- webproxy
# Configuración de renovación automática (cada 89 días = intervalo recomendado por Let's Encrypt)
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 89d & wait $${!}; done;'"
networks:
webproxy:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
default.conf (Archivo de Configuración de Nginx)
# Configuraciones del servidor HTTP (puerto 80)
server {
listen 80;
listen [::]:80; # Soporte para IPv6
server_name example.com www.example.com;
# Para los archivos de desafío de Let's Encrypt
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirigir todas las demás solicitudes a HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# Configuraciones del servidor HTTPS (puerto 443)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2; # Soporte para IPv6
server_name example.com www.example.com;
# Configuraciones del certificado SSL
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Configuraciones de SSL
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Versiones de TLS (solo 1.2 y 1.3)
ssl_protocols TLSv1.2 TLSv1.3;
# Suites de cifrado (configuraciones recomendadas)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Grapado OCSP (acelera las comprobaciones de validez de los certificados)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# Cabeceras de seguridad
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Configuraciones de proxy para el servidor backend
location / {
proxy_pass http://10.0.0.37:8080; # Dirección del servidor backend
# Configuraciones de la cabecera del proxy
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;
# Soporte para WebSocket (si es necesario)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Configuraciones de tiempo de espera
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
nginx.conf (Configuración Básica de Nginx)
user nginx;
worker_processes auto; # Ajuste automático basado en los núcleos de la CPU
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024; # Número de conexiones simultáneas
use epoll; # Modelo de eventos de alto rendimiento para Linux
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Formato de registro
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Configuraciones de rendimiento
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M; # Límite de tamaño de carga
# Compresión Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml text/x-js text/x-cross-domain-policy application/x-font-ttf application/x-font-opentype application/vnd.ms-fontobject image/x-icon;
# Configuraciones de seguridad
server_tokens off; # Ocultar la versión de Nginx
# Incluir configuraciones específicas del sitio
include /etc/nginx/conf.d/*.conf;
}
init-letsencrypt.sh (Script de Configuración Inicial)
#!/bin/bash
# Variables para la salida en color
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # Sin color
# Variables de configuración (cambiar según su entorno)
domains=(example.com www.example.com) # Nombres de dominio (se permiten múltiples)
rsa_key_size=4096 # Tamaño de la clave RSA
data_path="./data/certbot"
email="admin@example.com" # Correo electrónico para las notificaciones de Let's Encrypt
staging=1 # 1=modo de prueba, 0=modo de producción
# Comprobar si Docker Compose está instalado
if ! [ -x "$(command -v docker)" ]; then
echo -e "${RED}Error: Docker no está instalado.${NC}" >&2
exit 1
fi
if ! docker compose version >/dev/null 2>&1; then
echo -e "${RED}Error: Docker Compose V2 no está instalado.${NC}" >&2
exit 1
fi
# Comprobar si existen datos
if [ -d "$data_path" ]; then
read -p "Se encontraron datos existentes. ¿Continuar? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
# Crear un certificado ficticio (para iniciar Nginx)
echo -e "${GREEN}### Creando certificado ficticio...${NC}"
path="/etc/letsencrypt/live/${domains[0]}"
mkdir -p "$data_path/conf/live/${domains[0]}"
docker compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
# Iniciar Nginx
echo -e "${GREEN}### Iniciando Nginx...${NC}"
docker compose up --force-recreate -d nginx-proxy
echo
# Eliminar el certificado ficticio
echo -e "${GREEN}### Eliminando certificado ficticio...${NC}"
docker compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/${domains[0]} && \
rm -Rf /etc/letsencrypt/archive/${domains[0]} && \
rm -Rf /etc/letsencrypt/renewal/${domains[0]}.conf" certbot
echo
# Obtener el certificado de Let's Encrypt
echo -e "${GREEN}### Obteniendo el certificado de Let's Encrypt...${NC}"
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Establecer la dirección de correo electrónico
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Establecer el entorno de prueba
if [ $staging != "0" ]; then
staging_arg="--staging"
echo -e "${YELLOW}Nota: Ejecutando en modo de prueba.${NC}"
fi
# Obtener el certificado
docker compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
# Reiniciar Nginx
echo -e "${GREEN}### Reiniciando Nginx...${NC}"
docker compose restart nginx-proxy
echo -e "${GREEN}### ¡Configuración completa!${NC}"
if [ $staging != "0" ]; then
echo -e "${YELLOW}Para cambiar a producción, cambie staging=0 y vuelva a ejecutar el script.${NC}"
fi
Pasos de Configuración (Versión Detallada para Principiantes)
Paso 1: Descargar el Proyecto
# Clonar el proyecto desde GitHub
git clone https://github.com/superdoccimo/rev.git
cd rev
# O, si crea los archivos individualmente
mkdir nginx-letsencrypt
cd nginx-letsencrypt
Paso 2: Crear el Archivo de Variables de Entorno
Cree un archivo .env para describir la configuración específica de su entorno:
# Crear el archivo .env
cat > .env << EOF
# Configuración del dominio
DOMAIN=example.com
WWW_DOMAIN=www.example.com
# Configuración de Let's Encrypt
LETSENCRYPT_EMAIL=admin@example.com
# Configuración del servidor backend
BACKEND_HOST=10.0.0.37
BACKEND_PORT=8080
# Configuración del entorno (prueba/producción)
ENVIRONMENT=staging
EOF
Paso 3: Personalizar los Archivos de Configuración
3.1 Cambiar el Nombre de Dominio
Edite el archivo default.conf:
# Reemplazo masivo con sed (ejemplo)
sed -i 's/example.com/su-dominio.com/g' default.conf
# O, edite con un editor de texto
nano default.conf
3.2 Configurar el Servidor Backend
Edite la línea proxy_pass en default.conf:
# Ejemplo 1: Para un contenedor Docker local
proxy_pass http://app-container:3000;
# Ejemplo 2: Para un servidor diferente
proxy_pass http://192.168.1.100:8080;
# Ejemplo 3: Para un socket Unix
proxy_pass http://unix:/var/run/app.sock;
Paso 4: Ejecutar en Modo de Prueba
Antes de ejecutar en producción, siempre verifique la configuración en modo de prueba:
# Otorgar permiso de ejecución
chmod +x init-letsencrypt.sh
# Ejecutar en modo de prueba (staging=1)
sudo ./init-letsencrypt.sh
Puntos de Verificación:
- ✅ Compruebe si hay mensajes de error.
- ✅ Asegúrese de que el contenedor Nginx se esté ejecutando correctamente.
- ✅ Compruebe si puede acceder al sitio a través del puerto 80.
# Comprobar el estado del contenedor
docker compose ps
# Comprobar los registros
docker compose logs nginx-proxy
docker compose logs certbot
Paso 5: Verificar el Funcionamiento
Compruebe el acceso en su navegador:
# Comprobar el acceso HTTP
curl -I http://su-dominio.com
# Comprobar la redirección HTTPS (una respuesta 301 está bien)
# Es normal obtener un error de certificado en modo de prueba.
Paso 6: Cambiar al Modo de Producción
Una vez que la prueba sea exitosa, obtenga el certificado de producción:
# Editar init-letsencrypt.sh
nano init-letsencrypt.sh
# Cambiar staging=1 a staging=0
staging=0
# Eliminar los datos del certificado existente (¡importante!)
sudo rm -rf ./data/certbot/conf/*
# Ejecutar en modo de producción
sudo ./init-letsencrypt.sh
Paso 7: Verificar el Certificado SSL
# Comprobar los detalles del certificado SSL
openssl s_client -connect su-dominio.com:443 -servername su-dominio.com < /dev/null
# Comprobar la fecha de vencimiento del certificado
docker compose exec nginx-proxy openssl x509 -in /etc/letsencrypt/live/su-dominio.com/cert.pem -text -noout | grep "Not After"
Solución de Problemas (Problemas Comunes y Soluciones)
Problema 1: Falla la Adquisición del Certificado
Síntoma:
Challenge failed for domain example.com
Causa y Solución:
1. Error de Configuración de DNS
# Comprobar DNS
dig +short su-dominio.com
# Verifique que se muestre la dirección IP del servidor.
# Espere la propagación de DNS (puede tardar hasta 48 horas).
2. Configuración del Firewall
# Compruebe si los puertos 80 y 443 están abiertos
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
3. Otro Proceso ya está Usando los Puertos
# Comprobar el uso de los puertos
sudo lsof -i :80
sudo lsof -i :443
# Detener los servicios existentes si es necesario
sudo systemctl stop apache2 # Para Apache
sudo systemctl stop nginx # Para el Nginx del sistema
Problema 2: Nginx no se Inicia
Síntoma:
nginx-proxy exited with code 1
Solución:
Comprobar la Sintaxis del Archivo de Configuración
# Probar la configuración de Nginx
docker compose exec nginx-proxy nginx -t
# Si se produce un error, corrija la línea correspondiente.
Problema 3: Falla la Renovación Automática del Certificado
Síntoma:
El certificado no se renueva aunque se acerque la fecha de vencimiento.
Solución:
Probar la Renovación Manualmente
# Realizar una ejecución de prueba de la renovación
docker compose exec certbot certbot renew --dry-run
# Renovar realmente
docker compose exec certbot certbot renew --force-renewal
# Reiniciar Nginx para aplicar el certificado
docker compose restart nginx-proxy
Problema 4: No se Puede Acceder a través de HTTPS
Síntoma:
ERR_SSL_PROTOCOL_ERROR
Solución:
Verificar la Ruta del Certificado
# Compruebe si existe el archivo del certificado
ls -la ./data/certbot/conf/live/su-dominio/
# Compruebe y corrija los permisos
sudo chmod -R 755 ./data/certbot/conf/
Problema 5: Límites de Tasa de Let’s Encrypt
Síntoma:
Error creating new order :: too many certificates already issued
Solución:
- La emisión de certificados para el mismo dominio está limitada a 5 veces por semana.
- Utilice el entorno de prueba para las pruebas.
- Espere una semana antes de volver a intentarlo.
Métodos de Personalización
Operación de Múltiples Sitios
Una de las principales ventajas de un proxy inverso es la capacidad de gestionar múltiples sitios web de forma centralizada.


Ejemplo de Configuración 1: Enrutamiento Basado en Subdominios
# Configuraciones para blog.example.com
server {
listen 443 ssl http2;
server_name blog.example.com;
ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080; # Contenedor de WordPress
}
}
# Configuraciones para portfolio.example.com
server {
listen 443 ssl http2;
server_name portfolio.example.com;
ssl_certificate /etc/letsencrypt/live/portfolio.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/portfolio.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8888; # Sitio del portafolio
}
}
Ejemplo de Configuración 2: Enrutamiento Basado en Rutas
server {
listen 443 ssl http2;
server_name example.com;
# Para el blog
location /blog {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Para la API
location /api {
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Para archivos estáticos
location /static {
alias /var/www/static;
expires 30d;
add_header Cache-Control "public, immutable";
}
}


Ajuste de Rendimiento
1. Configuraciones de Caché
# Configuraciones de caché del proxy
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache my_cache;
proxy_cache_valid 200 60m;
proxy_cache_valid 404 1m;
proxy_cache_bypass $http_pragma $http_authorization;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
2. Optimización de la Compresión
# Agregar compresión Brotli (mayor tasa de compresión)
load_module modules/ngx_http_brotli_module.so;
http {
# Configuraciones de Brotli
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml;
}Mejora de la Seguridad
1. Implementación de la Limitación de Velocidad
# Limitación de velocidad para la protección contra DDoS
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /api {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://backend;
}
}
2. Restricción de Dirección IP
# Restricción de acceso para el panel de administración
location /admin {
allow 192.168.1.0/24; # Red interna
allow 203.0.113.5; # IP fija del administrador
deny all;
proxy_pass http://backend;
}
Configuraciones Avanzadas de Docker Compose
1. Adición de Verificaciones de Estado
services:
nginx-proxy:
image: nginx:alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
2. Límites de Recursos
services:
nginx-proxy:
image: nginx:alpine
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Automatización y Mantenimiento
Configuraciones de Renovación Automática de Certificados
Los certificados de Let’s Encrypt vencen en 90 días, por lo que la renovación automática es crucial.
Método Usando Temporizadores de systemd (Recomendado)
# 1. Crear un archivo de servicio de systemd
sudo cat > /etc/systemd/system/certbot-renewal.service << EOF
[Unit]
Description=Renovación de Certbot
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
WorkingDirectory=/ruta/a/su/proyecto
ExecStart=/usr/bin/docker compose exec -T certbot certbot renew --quiet
ExecStartPost=/usr/bin/docker compose exec -T nginx-proxy nginx -s reload
[Install]
WantedBy=multi-user.target
EOF
# 2. Crear un archivo de temporizador
sudo cat > /etc/systemd/system/certbot-renewal.timer << EOF
[Unit]
Description=Ejecutar la Renovación de Certbot dos veces al día
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
# 3. Habilitar e iniciar el servicio
sudo systemctl daemon-reload
sudo systemctl enable certbot-renewal.timer
sudo systemctl start certbot-renewal.timer
# 4. Comprobar el estado
sudo systemctl status certbot-renewal.timer
sudo systemctl list-timers
Gestión de Registros
Configuraciones de Rotación de Registros
# Crear /etc/logrotate.d/nginx-docker
sudo cat > /etc/logrotate.d/nginx-docker << EOF
/var/lib/docker/containers/*/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 root root
sharedscripts
postrotate
docker compose exec -T nginx-proxy nginx -s reopen
endscript
}
EOF
Configuraciones de Monitoreo
1. Script de Monitoreo de Vencimiento de Certificados
#!/bin/bash
# check-cert-expiry.sh
DOMAIN="example.com"
DAYS_WARNING=30
expiry_date=$(docker compose exec -T nginx-proxy \
openssl x509 -in /etc/letsencrypt/live/$DOMAIN/cert.pem -noout -enddate \
| cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( ($expiry_epoch - $current_epoch) / 86400 ))
if [ $days_left -lt $DAYS_WARNING ]; then
echo "Advertencia: ¡El certificado SSL vence en ${days_left} días!"
# Enviar correo electrónico o notificación de Slack
fi
2. Integración de la Verificación de Estado con Docker Compose
services:
monitoring:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
networks:
- webproxy
Mejores Prácticas y Recomendaciones de Seguridad
Recomendaciones de Seguridad
1. Optimización de la Configuración de TLS
# Configuraciones de TLS recomendadas
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
2. Cabeceras de Seguridad Completas
# Cabeceras de Seguridad
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:;" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Optimización del Rendimiento
1. Configuraciones de HTTP/2 y HTTP/3
# Habilitar HTTP/2 (ya incluido por defecto)
listen 443 ssl http2;
# Habilitar HTTP/3 (QUIC) (Nginx 1.25.0+)
listen 443 quic reuseport;
listen 443 ssl http2;
add_header Alt-Svc 'h3=":443"; ma=86400';
2. Optimización de la Conexión
# Optimización de la conexión Keep-alive
keepalive_timeout 65;
keepalive_requests 100;
# Optimización del tamaño del búfer
client_body_buffer_size 128k;
client_max_body_size 100m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;
Monitoreo y Alertas
Monitoreo con Prometheus + Grafana
# Agregar a docker-compose.yml
services:
nginx-exporter:
image: nginx/nginx-prometheus-exporter:latest
ports:
- "9113:9113"
command:
- -nginx.scrape-uri=http://nginx-proxy/nginx_status
networks:
- webproxy
depends_on:
- nginx-proxy
Preguntas Frecuentes (FAQ)
P1: ¿Es suficiente un certificado gratuito de Let’s Encrypt?
R: Sí, en la mayoría de los casos, es suficiente. Let’s Encrypt:
- Es de confianza para todos los principales navegadores.
- Es fácil de gestionar con la renovación automática.
- Funciona como un certificado DV (Validado por Dominio).
Sin embargo, considere un certificado de pago si:
- Necesita un certificado EV (Validación Extendida).
- Necesita gestionar muchos subdominios con un certificado comodín.
- Requiere indemnización legal.
P2: ¿Qué sucede si falla la renovación del certificado?
R: La renovación automática se intenta 30 días antes del vencimiento, por lo que hay múltiples oportunidades de reintento. Pasos manuales:
# Comprobar el estado de la renovación
docker compose exec certbot certbot certificates
# Renovación manual
docker compose exec certbot certbot renew --force-renewal
# Reiniciar Nginx
docker compose restart nginx-proxy
P3: ¿Puedo usar un certificado para múltiples dominios?
R: Sí, esto es posible con un certificado SAN (Nombres Alternativos del Sujeto):
# Especificar múltiples dominios en init-letsencrypt.sh
domains=(example.com www.example.com api.example.com)
P4: ¿Cómo debo hacer una copia de seguridad de mi configuración?
R: Debe hacer una copia de seguridad de los siguientes directorios:
# Ejemplo de script de copia de seguridad
#!/bin/bash
BACKUP_DIR="/backup/nginx-letsencrypt"
DATE=$(date +%Y%m%d_%H%M%S)
# Copia de seguridad de los certificados
tar -czf $BACKUP_DIR/certbot_$DATE.tar.gz ./data/certbot/
# Copia de seguridad de los archivos de configuración
tar -czf $BACKUP_DIR/config_$DATE.tar.gz *.conf docker-compose.yml
# Eliminar copias de seguridad antiguas (más de 30 días)
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
P5: ¿Qué debo hacer si el rendimiento es lento?
R: Compruebe los siguientes puntos:
Compruebe si hay escasez de recursos
docker stats
htopAjuste los procesos de trabajo de Nginx
worker_processes auto; # Se ajusta automáticamente al número de núcleos de la CPUHabilitar el almacenamiento en caché
Reducir el registro innecesario
Conclusión
Esta guía ha proporcionado una explicación detallada de cómo construir un proxy inverso SSL con renovación automática utilizando Docker, Nginx y Let’s Encrypt, basado en las mejores prácticas actuales.
Lo que Logramos
✅ Construcción de un Entorno Seguro
- Comunicación cifrada con TLS 1.2/1.3.
- Seguridad continua mediante la renovación automática de certificados.
- Implementación de cabeceras de seguridad modernas.
✅ Automatización Operativa
- Fácil gestión con Docker Compose V2.
- Renovación automática con Let’s Encrypt.
- Rotación automática de registros.
✅ Configuración Escalable
- Gestión centralizada de múltiples sitios.
- Potencial para implementar el balanceo de carga.
- Adaptable a la arquitectura de microservicios.
Próximos Pasos
Para desarrollar aún más este sistema:
- Agregar un sistema de monitoreo (Prometheus + Grafana).
- Construir una canalización de CI/CD (por ejemplo, GitHub Actions).
- Migrar a un entorno de Kubernetes (para operaciones a mayor escala).
- Introducir un WAF (Firewall de Aplicaciones Web) (por ejemplo, ModSecurity).
Recursos Relacionados
- Documentación Oficial de Nginx
- Sitio Web Oficial de Let’s Encrypt
- Documentación Oficial de Docker
- Repositorio de GitHub
Artículos Relacionados
Video tutorial:
Última Actualización: Septiembre de 2025 Versión: 2.0 Autor: mamu minokamo
Si este artículo le resultó útil, ¡considere destacarlo en GitHub! Si tiene preguntas o sugerencias de mejora, hágamelo saber a través de un Issue de GitHub.

