Nginx Proxy Manager: SSL y dominios para todos los contenedores en un solo VPS
TL;DR
Nginx Proxy Manager permite alojar decenas de contenedores Docker en un solo VPS, asignar a cada uno un dominio o subdominio independiente y emitir automáticamente certificados SSL de Let’s Encrypt sin configurar Nginx manualmente.
- Una única dirección IP pública atiende varios sitios, API, paneles de administración y servicios self-hosted.
- Nginx Proxy Manager recibe tráfico HTTP/HTTPS en los puertos 80 y 443 y lo dirige al contenedor correspondiente dentro de la red interna de Docker.
- Los certificados SSL de Let’s Encrypt se emiten y renuevan automáticamente mediante la interfaz web.
- El panel de administración de Nginx Proxy Manager no debe dejarse abierto a todo Internet sin protección adicional.
- Para un conjunto pequeño de servicios, normalmente basta un VPS con 2 vCPU, 2–4 GB de RAM y un disco SSD de 30 GB o más.
- Los datos críticos —archivos Docker Compose, volúmenes de Nginx Proxy Manager, base de datos SQLite o MariaDB y copias de seguridad de aplicaciones— deben exportarse regularmente fuera del VPS.
Qué configuramos y por qué
Un VPS típico se convierte rápidamente en un conjunto de servicios: GitLab o Gitea, Mattermost, Vaultwarden, Nextcloud, Grafana, una página de inicio, API de aplicaciones, entornos de prueba y paneles de monitorización. Cada contenedor suele escuchar en su propio puerto interno: 3000, 8080, 9000, 5000 u otro. Abrir todos estos puertos a Internet resulta incómodo e inseguro.
Nginx Proxy Manager, en adelante NPM, resuelve esta tarea como reverse proxy con interfaz web. Recibe solicitudes en los puertos estándar HTTP 80 y HTTPS 443, determina el dominio a partir de la cabecera Host y redirige el tráfico al contenedor adecuado. Por ejemplo, una solicitud a git.example.com se envía a Gitea, chat.example.com a Mattermost y status.example.com a Uptime Kuma.
Como resultado, para el usuario externo no importa en qué puerto funciona realmente la aplicación. Todos los servicios están disponibles mediante direcciones HTTPS claras, y en el VPS basta con abrir únicamente SSH, HTTP y HTTPS. Los puertos internos de las aplicaciones permanecen cerrados dentro de la red Docker.
Arquitectura final
En esta guía se utilizará la siguiente arquitectura:
- Un VPS con Ubuntu Server 24.04 LTS.
- Docker Engine y Docker Compose Plugin.
- Nginx Proxy Manager en un proyecto Docker Compose independiente.
- Una red Docker externa compartida
proxy. - Varias aplicaciones conectadas a esta red.
- Registros DNS de dominios como
app.example.com, que apuntan a la IP del VPS. - Let’s Encrypt para certificados TLS gratuitos.
El flujo de la solicitud será el siguiente: el navegador abre https://vault.example.com → DNS devuelve la IP del VPS → NPM recibe la solicitud en el puerto 443 → NPM termina TLS → NPM transmite HTTP al contenedor interno de Vaultwarden a través de la red Docker.
Qué obtendrá después de la configuración
- Un único punto de entrada para todos los contenedores del servidor.
- HTTPS para cada dominio sin crear manualmente configuraciones de Nginx.
- Renovación automática de certificados Let’s Encrypt.
- Posibilidad de habilitar WebSocket, access list, autenticación básica y redirecciones mediante la UI.
- Aislamiento de servicios: las aplicaciones no tienen que publicar puertos en la IP del VPS.
- Una migración de infraestructura más sencilla: la configuración se almacena en archivos Compose y Docker volumes.
Qué alternativas existen
| Enfoque | Ventajas | Limitaciones |
|---|---|---|
| Cloud-managed ingress o load balancer | Alta disponibilidad, certificados gestionados, menos administración | Coste mensual, dependencia de la nube, a menudo excesivo para un solo VPS |
| Nginx convencional configurado manualmente | Máxima flexibilidad, configuraciones conocidas, alto rendimiento | Es necesario escribir por cuenta propia configuraciones de virtual host, configurar Certbot y las actualizaciones |
| Traefik | Automatización práctica mediante Docker labels, adecuado para un gran número de servicios | La configuración mediante labels y middleware puede ser más compleja para principiantes |
| Caddy | Configuración sencilla, HTTPS automático, Caddyfile compacto | Menos gestión visual, algunas tareas requieren trabajo manual con la configuración |
| Nginx Proxy Manager | UI clara, Let’s Encrypt, access lists, proxy hosts sin Nginx manual | Panel de administración adicional, es necesario proteger el acceso administrativo |
Para un desarrollador individual, un equipo pequeño o el propietario de un VPS, NPM resulta práctico porque no requiere recordar la sintaxis de Nginx al añadir otro servicio. No elimina la necesidad de comprender las redes, DNS y la seguridad, pero reduce considerablemente el número de operaciones manuales.
Nginx Proxy Manager no sustituye a un firewall. Incluso si el servicio solo está disponible a través del proxy, los puertos Docker abiertos pueden eludir las reglas de UFW. Publique los puertos de las aplicaciones solo en
127.0.0.1o no los publique en absoluto.
Qué configuración de VPS se necesita para esta tarea
El propio Nginx Proxy Manager consume pocos recursos. En un servidor limpio, su contenedor suele requerir unos cientos de megabytes de RAM, y las operaciones TLS cargan notablemente la CPU solo con una gran cantidad de conexiones nuevas. Los recursos deben elegirse principalmente en función de las aplicaciones detrás del proxy: GitLab, Nextcloud, bases de datos, servidores de juegos y CI son mucho más exigentes que NPM.
| Escenario | CPU | RAM | Disco | Red |
|---|---|---|---|---|
| NPM + 2–5 servicios ligeros | 2 vCPU | 2 GB | 30 GB SSD | 100 Mbit/s |
| NPM + Nextcloud, Gitea, Mattermost, monitorización | 2–4 vCPU | 4–8 GB | 80–160 GB NVMe | 100 Mbit/s o 1 Gbit/s |
| Muchos usuarios, archivos, CI, bases de datos | 4–8 vCPU | 8–16 GB | 160 GB+ NVMe | 1 Gbit/s |
Una opción inicial práctica para NPM, Vaultwarden, Uptime Kuma, Gitea y una API pequeña es 2 vCPU, 4 GB de RAM, 60–80 GB NVMe y 1 Gbit/s. Si es necesario, puede elegir un VPS con las características indicadas o un plan similar de otro proveedor. Más importante que el nombre del plan es disponer de IPv4 público dedicado, una red estable, acceso por SSH y la posibilidad de crear snapshots.
Por qué se necesita una IP pública
Para la verificación HTTP-01 estándar de Let’s Encrypt, el servidor debe ser accesible desde el exterior por el puerto 80. Si el VPS se encuentra detrás de CGNAT o dispone solo de una IP privada, la emisión automática de certificados mediante la verificación HTTP habitual no funcionará. En ese caso se necesita un IPv4 público, IPv6 público con DNS configurado correctamente o DNS-01 challenge mediante el proveedor de DNS.
Cuándo se necesita un dedicated en lugar de un VPS
Para un único reverse proxy no se necesita un servidor dedicated. Se justifica cuando detrás del proxy funcionan servicios exigentes: un GitLab grande con CI-runner, una nube de archivos con terabytes de datos, transcodificación de vídeo, una base de datos de alta carga, decenas de miles de solicitudes por segundo o requisitos de rendimiento predecible del disco.
También conviene considerar un dedicated con alto tráfico de red, una gran cantidad de conexiones TLS simultáneas, requisitos de varios discos físicos en RAID y cuando la virtualización con otros clientes en un VPS no es aceptable según la política de seguridad. Para 5–30 servicios internos de un equipo pequeño, normalmente basta un VPS de calidad.
Cómo elegir la ubicación
La ubicación del VPS afecta a la latencia, los requisitos legales y la velocidad de carga inicial. Si la audiencia principal se encuentra en Europa, elija un centro de datos europeo; si los usuarios están en Kazajistán, Rusia, Turquía o Oriente Medio, mida previamente el ping hacia varias ubicaciones. Para los paneles de administración, la diferencia entre 20 y 60 ms apenas se nota, pero para juegos, servicios de voz, API y almacenamiento de archivos es más importante.
No ubique el servidor solo según la cercanía a usted si el servicio lo utiliza un equipo de otro país. Compruebe también si los puertos necesarios están permitidos, si hay reverse DNS para servicios de correo cuando sea necesario y si hay almacenamientos de copia de seguridad disponibles en otra ubicación.
Preparación del servidor
A continuación se asume un servidor nuevo con Ubuntu Server 24.04 LTS e inicio de sesión como usuario root. Si utiliza Debian 12 o Debian 13, la lógica sigue siendo la misma, pero algunos comandos de instalación de paquetes pueden diferir. Antes de configurar los dominios, cree registros A: por ejemplo, npm.example.com, vault.example.com y status.example.com deben apuntar al IPv4 público del servidor.
Actualice el sistema y cree un administrador
Realice la configuración inicial mediante SSH. En lugar de admin, indique su propio nombre de usuario y, en lugar del ejemplo de clave pública, inserte la clave del archivo ~/.ssh/id_ed25519.pub en el equipo local.
apt update && apt upgrade -y
apt install -y sudo curl ca-certificates gnupg fail2ban ufw vim git
adduser admin
usermod -aG sudo admin
El comando actualiza el sistema, instala utilidades básicas, Fail2ban y UFW, y luego crea un usuario con privilegios sudo.
install -d -m 700 -o admin -g admin /home/admin/.ssh
nano /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
chown admin:admin /home/admin/.ssh/authorized_keys
Este bloque crea el directorio SSH y añade la clave pública. Pegue la clave en una sola línea, guarde el archivo y no cierre la sesión actual de root hasta comprobar el nuevo acceso.
ssh -i ~/.ssh/id_ed25519 admin@SERVER_IP
El comando comprueba que el nuevo usuario realmente puede conectarse con la clave. Abra una ventana de terminal independiente y asegúrese de que el inicio de sesión se realiza sin contraseña.
Desactive el acceso SSH mediante contraseña
Después de una comprobación correcta, edite un archivo de configuración SSH independiente. Es más seguro que modificar el archivo principal /etc/ssh/sshd_config, que puede actualizarse mediante el gestor de paquetes.
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Añada los siguientes parámetros:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
sudo sshd -t && sudo systemctl restart ssh
El comando comprueba la sintaxis de la configuración y solo después reinicia SSH. Si la comprobación devuelve un error, no reinicie el servicio hasta corregir el archivo.
Configure el firewall y Fail2ban
NPM debe recibir conexiones externas en los puertos 80 y 443. Es recomendable limitar SSH a la dirección IP de la oficina o de la VPN doméstica, pero para la primera puesta en marcha puede dejar temporalmente el acceso desde cualquier IP. Docker tiene particularidades al interactuar con UFW, por lo que la principal protección de los contenedores es no publicar sus puertos hacia el exterior.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Los comandos bloquean el tráfico entrante de forma predeterminada y permiten únicamente SSH, HTTP y HTTPS. Más adelante puede añadir una restricción de SSH por IP de origen o trasladar el acceso al panel de NPM detrás de una VPN.
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Este bloque inicia Fail2ban y muestra el estado de la jail de SSH. Fail2ban reduce el efecto de los intentos de descifrado de contraseñas, pero no sustituye las claves SSH, las actualizaciones ni la restricción de acceso.
Compruebe el DNS antes de emitir el certificado
El certificado Let’s Encrypt no se emitirá hasta que el registro DNS apunte al servidor actual. Compruebe el registro desde el VPS y, preferiblemente, mediante resolutores DNS externos.
getent hosts npm.example.com
curl -4 ifconfig.me
dig +short npm.example.com A @1.1.1.1
La IP devuelta por DNS debe coincidir con el IPv4 público del VPS. Si el DNS aún se está propagando, espere al TTL del registro. No cree decenas de solicitudes fallidas de certificado: Let’s Encrypt aplica rate limits.
Instalación del software — paso a paso
Para Nginx Proxy Manager, utilice Docker Engine del repositorio oficial de Docker, no el paquete obsoleto docker.io del repositorio estándar de Ubuntu. En 2026, la rama actual Docker Engine 29 es compatible con Compose Plugin; compruebe el número de versión menor específico en la documentación oficial de Docker antes de instalarlo.
Instale Docker Engine y Compose Plugin
sudo apt remove -y docker.io docker-compose docker-compose-v2 podman-docker containerd runc || true
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
Los comandos eliminan paquetes antiguos en conflicto y añaden la clave GPG del repositorio oficial 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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Este bloque añade el repositorio e instala Docker Engine, containerd, Buildx y el moderno Compose Plugin.
sudo usermod -aG docker admin
newgrp docker
docker version
docker compose version
El comando añade el usuario al grupo Docker y muestra las versiones de Docker y Compose. Tenga en cuenta que la pertenencia al grupo docker concede de facto privilegios de nivel root. No añada usuarios normales de aplicaciones a este grupo.
Cree una red Docker compartida
Los contenedores a los que NPM debe acceder mediante el DNS interno de Docker se conectarán a una misma red externa. Esto es mejor que reenviar los puertos de cada aplicación a la interfaz pública del servidor.
docker network create proxy
docker network inspect proxy
Los comandos crean la red bridge proxy y muestran sus parámetros. En el futuro, NPM podrá acceder al contenedor por el nombre del servicio, por ejemplo, vaultwarden o uptime-kuma.
Cree el directorio de Nginx Proxy Manager
mkdir -p ~/stacks/nginx-proxy-manager
cd ~/stacks/nginx-proxy-manager
mkdir -p data letsencrypt
chmod 700 data letsencrypt
Este bloque crea el directorio de trabajo y los directorios persistentes. En data, NPM almacena la configuración y la base de datos SQLite; en letsencrypt, los certificados y los datos ACME.
Prepare el archivo Compose
Para un VPS pequeño puede utilizar la base de datos SQLite integrada. Esto minimiza el número de contenedores y es suficiente para una única instancia de NPM. En instalaciones grandes y cuando se requieren redundancias, normalmente se utiliza MariaDB, pero para un único reverse proxy SQLite es más sencillo de mantener.
nano compose.yml
Inserte la configuración con la imagen fijada de NPM de la rama 2.12.3. Antes de un nuevo despliegue, revise GitHub Releases del proyecto y el changelog: no sustituya la etiqueta estable por latest sin realizar pruebas.
services:
npm:
image: jc21/nginx-proxy-manager:2.12.3
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81"
environment:
TZ: "Europe/Berlin"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
Los puertos 80 y 443 se publican para todo Internet, mientras que el puerto administrativo 81 está vinculado únicamente a 127.0.0.1. Esta es una opción de seguridad importante: el panel no estará disponible directamente mediante http://SERVER_IP:81.
docker compose pull
docker compose up -d
docker compose ps
docker logs --tail=100 nginx-proxy-manager
Los comandos descargan la imagen, inician el stack en segundo plano, muestran el estado del contenedor y los últimos registros. El estado debe ser Up. Si el contenedor se reinicia, revise los registros completos.
Abra el panel administrativo mediante un túnel SSH
Como el puerto 81 solo está disponible localmente en el VPS, cree un túnel desde el equipo de trabajo. El comando se ejecuta en su equipo local, no en el servidor.
ssh -L 8181:127.0.0.1:81 admin@SERVER_IP
Mientras la sesión SSH esté abierta, acceda en el navegador a http://127.0.0.1:8181. Para el primer inicio de sesión, utilice los datos estándar de NPM: email [email protected], contraseña changeme. Inmediatamente después de iniciar sesión, la interfaz le pedirá cambiar el email y la contraseña.
No publique el puerto 81 mediante
81:81salvo que exista una necesidad estricta. Incluso con una contraseña robusta, el panel de administración no debe ser una superficie adicional de ataque público.
Configuración
Ahora configuraremos un ejemplo con dos aplicaciones: Vaultwarden y Uptime Kuma. Funcionarán sin puertos externos publicados. NPM las verá a través de la red compartida proxy, y los usuarios accederán a ellas mediante los dominios vault.example.com y status.example.com.
Despliegue los contenedores de prueba
Cree un proyecto Compose independiente. No guarde secretos en el código fuente ni los publique en Git. Para Vaultwarden, utilice un archivo .env con permisos solo para el propietario.
mkdir -p ~/stacks/apps
cd ~/stacks/apps
nano .env
chmod 600 .env
Agregue una contraseña aleatoria larga al archivo .env. Puede generarla con el comando openssl rand -base64 48.
VAULTWARDEN_ADMIN_TOKEN=replace_with_a_long_random_secret
TZ=Europe/Berlin
nano compose.yml
services:
vaultwarden:
image: vaultwarden/server:1.34.3
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com"
WEBSOCKET_ENABLED: "true"
ADMIN_TOKEN: "${VAULTWARDEN_ADMIN_TOKEN}"
TZ: "${TZ}"
SIGNUPS_ALLOWED: "false"
volumes:
- ./vaultwarden-data:/data
networks:
- proxy
uptime-kuma:
image: louislam/uptime-kuma:1.23.16
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./uptime-kuma-data:/app/data
networks:
- proxy
networks:
proxy:
external: true
En este ejemplo no hay una sección ports. Los contenedores solo son accesibles para sus vecinos en la red proxy. Si una aplicación necesita acceso a una base de datos, conecte también la base a una red interna independiente y no publique su puerto 3306, 5432 o 6379 hacia el exterior.
docker compose up -d
docker compose ps
docker network inspect proxy
Los comandos inician las aplicaciones y verifican que los contenedores estén conectados a la red. En la salida de docker network inspect proxy deben aparecer nginx-proxy-manager, vaultwarden y uptime-kuma.
Compruebe la conectividad interna
Antes de crear un proxy host, asegúrese de que NPM vea los contenedores de destino. Dentro del contenedor NPM puede realizar una solicitud HTTP mediante el nombre DNS de Docker. Vaultwarden utiliza el puerto 80 de forma predeterminada y Uptime Kuma el 3001.
docker exec nginx-proxy-manager curl -I http://vaultwarden:80
docker exec nginx-proxy-manager curl -I http://uptime-kuma:3001
Para Vaultwarden se espera una respuesta HTTP como 200, 301 o 302. Para Uptime Kuma también se admite una respuesta de redirección. El error Could not resolve host significa que el contenedor no está conectado a la red proxy.
Cree un Proxy Host para Vaultwarden
Abra un túnel SSH hacia el panel, vaya a Hosts → Proxy Hosts → Add Proxy Host y complete los campos:
- Domain Names:
vault.example.com - Scheme:
http - Forward Hostname / IP:
vaultwarden - Forward Port:
80 - Cache Assets: desactivado para el primer inicio
- Block Common Exploits: activado
- Websockets Support: activado
En la pestaña SSL, seleccione Request a new SSL Certificate, indique un email para Let’s Encrypt, active Force SSL, HTTP/2 Support y acepte los términos de Let’s Encrypt. Luego guarde la entrada.
Para el desafío HTTP-01, el dominio debe apuntar al VPS y el puerto entrante 80 debe estar disponible. NPM utiliza por sí mismo una lógica ACME integrada, similar a Certbot: no es necesario ni posible instalar manualmente Certbot o Caddy sobre NPM, porque competirán por los puertos 80 y 443.
Cree un Proxy Host para Uptime Kuma
Agregue un segundo host de forma similar, pero con otros valores:
- Domain Names:
status.example.com - Scheme:
http - Forward Hostname / IP:
uptime-kuma - Forward Port:
3001 - Websockets Support: activado
- Block Common Exploits: activado
En la pestaña SSL, solicite de nuevo un certificado independiente. Puede emitir un certificado para varios dominios, pero los certificados independientes son más fáciles de mantener y más seguros desde el punto de vista del aislamiento. Un certificado wildcard requerirá un desafío DNS-01 y la API del proveedor DNS.
Configure una access list para los paneles internos
No todos los servicios deben estar disponibles para todos los usuarios. Por ejemplo, es mejor proteger con Basic Auth y, si es posible, con una lista de IP permitidas un panel de administración, Grafana, Portainer o una API de prueba. En NPM, vaya a Access Lists → Add Access List, cree la lista admins-only, agregue un usuario con una contraseña larga e indique las redes CIDR permitidas en la pestaña Access.
Después, seleccione la lista creada en la configuración del Proxy Host correspondiente. Basic Auth es una capa adicional, no un sustituto de la autenticación de la propia aplicación. Para paneles críticos, es mejor utilizar WireGuard, Tailscale o un túnel SSH.
Parámetros adicionales del proxy host
Algunas aplicaciones requieren transmitir la IP real del cliente, un límite de carga mayor o tiempos de espera prolongados. NPM ya agrega los proxy headers estándar, pero en el campo Advanced puede añadir directivas seguras para un host específico:
client_max_body_size 2g;
proxy_connect_timeout 60s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
send_timeout 3600s;
Este ejemplo es necesario para cargar archivos grandes en Nextcloud o un servicio similar. No inserte directivas aleatorias de foros en la configuración global: un error de sintaxis puede romper la generación de configuraciones de Nginx para todos los dominios.
Compruebe HTTPS y el certificado
curl -I https://vault.example.com
curl -I https://status.example.com
curl -Iv https://vault.example.com 2>&1 | grep -E "SSL certificate verify|subject:|issuer:"
La respuesta debe contener un código HTTP correcto y no debe mostrar un error de verificación TLS. Si utiliza Cloudflare en modo proxy, para el diagnóstico inicial desactive temporalmente el modo proxy del registro o asegúrese de que su modo SSL no entre en conflicto con el certificado de origen.
docker logs --tail=100 nginx-proxy-manager
docker exec nginx-proxy-manager nginx -t
Estos comandos muestran los logs de NPM y comprueban la sintaxis de la configuración de Nginx generada. Ejecútelos después de modificar configuraciones avanzadas complejas o cuando haya errores 502/503.
Copias de seguridad y mantenimiento
Nginx Proxy Manager puede recrearse rápidamente, pero sin una copia de seguridad perderá los proxy hosts, las access lists, los usuarios, los certificados y la configuración. Además de NPM, debe realizar copias de seguridad por separado de los datos de las aplicaciones: directorios de Nextcloud, Vaultwarden, repositorios Git, bases de datos PostgreSQL y MariaDB, archivos de Mattermost y configuraciones Compose.
Qué incluir en la copia de seguridad
| Objeto | Ubicación | Por qué es necesario |
|---|---|---|
| Datos de NPM | ~/stacks/nginx-proxy-manager/data |
Base de datos SQLite, proxy hosts, usuarios, configuración |
| Let’s Encrypt | ~/stacks/nginx-proxy-manager/letsencrypt |
Certificados, cuenta ACME, claves |
| Archivos Compose y .env | ~/stacks |
Despliegue reproducible y secretos |
| Datos de aplicaciones | bind mounts o named volumes | Archivos de usuarios, bases de datos, configuración |
| Volcados de bases de datos | directorio de backup independiente | Restauración coherente de PostgreSQL/MariaDB |
No considere un snapshot del VPS como una copia de seguridad completa. Un snapshot es útil antes de una actualización, pero si se almacena en la misma cuenta o centro de datos, no protege contra la eliminación de la cuenta, el compromiso de las credenciales o una incidencia del proveedor. Utilice la regla 3-2-1: al menos tres copias, en dos tipos de almacenamiento y una fuera del servidor principal.
Copia de seguridad sencilla con Restic en S3
Restic admite cifrado en el lado del cliente y almacenamientos compatibles con S3. Cree un bucket independiente, un usuario independiente con acceso solo a ese bucket y guarde las claves en un archivo protegido.
sudo apt install -y restic
sudo install -d -m 700 /root/.config/restic
sudo nano /root/.config/restic/env
sudo chmod 600 /root/.config/restic/env
Agregue las variables de acceso al archivo. Sustituya los valores por los datos de su almacenamiento S3.
export RESTIC_REPOSITORY="s3:https://s3.example-storage.net/server-backups/npm-vps"
export RESTIC_PASSWORD="use_a_long_unique_backup_password"
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
sudo bash -c 'source /root/.config/restic/env && restic init'
El comando crea un nuevo repositorio cifrado. Guarde la contraseña RESTIC_PASSWORD por separado del VPS: en un gestor de contraseñas o almacenamiento offline. La pérdida de esta contraseña hace que las copias de seguridad sean irrecuperables.
sudo nano /usr/local/sbin/backup-stacks.sh
sudo chmod 700 /usr/local/sbin/backup-stacks.sh
Cree el script:
#!/usr/bin/env bash
set -euo pipefail
source /root/.config/restic/env
STAMP=$(date +%F)
BACKUP_DIR="/root/backup-work/$STAMP"
mkdir -p "$BACKUP_DIR"
docker exec vaultwarden /bin/sh -c 'sqlite3 /data/db.sqlite3 ".backup /data/db-backup.sqlite3"' || true
restic backup /home/admin/stacks \
--exclude='/node_modules' \
--exclude='/cache' \
--tag docker-stacks
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
El script guarda los directorios de los stacks, elimina snapshots antiguos según la política de retención y comprueba el repositorio. Para aplicaciones con PostgreSQL o MariaDB, agregue pg_dump o mariadb-dump antes de la copia de seguridad; copiar los archivos de una base de datos activa sin un volcado puede producir una copia dañada o lógicamente incoherente.
sudo crontab -e
Agregue una ejecución diaria por la noche:
15 3 * /usr/local/sbin/backup-stacks.sh >> /var/log/backup-stacks.log 2>&1
Después de configurarlo, realice obligatoriamente una restauración en un directorio de prueba. Una copia de seguridad solo se considera funcional tras comprobar correctamente la restauración.
sudo bash -c 'source /root/.config/restic/env && restic snapshots'
sudo mkdir -p /root/restore-test
sudo bash -c 'source /root/.config/restic/env && restic restore latest --target /root/restore-test'
Actualizaciones de NPM y contenedores
Para un único VPS, el modelo óptimo es una ventana de mantenimiento: elija un período, por ejemplo una vez cada dos semanas, haga una copia de seguridad, lea las release notes y actualice un stack a la vez. Una rolling update es útil en un clúster con varias réplicas, pero en un servidor único normalmente no proporciona alta disponibilidad: reiniciar NPM durante 10–30 segundos seguirá interrumpiendo brevemente las conexiones.
cd ~/stacks/nginx-proxy-manager
docker compose pull
docker compose up -d
docker image prune -f
docker compose ps
Los comandos descargan la imagen, recrean el contenedor si cambia la versión, eliminan imágenes dangling no utilizadas y muestran el estado final. Primero fije la versión de la imagen en compose.yml y después actualícela de forma consciente. No ejecute sin pensar docker system prune -a --volumes: el comando puede eliminar datos que Docker considera no utilizados.
Una vez al mes, compruebe las actualizaciones del sistema operativo, la validez de los certificados, el tamaño de los logs y el espacio libre:
sudo apt update && sudo apt upgrade -y
df -h
docker system df
openssl s_client -connect vault.example.com:443 -servername vault.example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates
Solución de problemas + FAQ
¿Por qué aparece Internal Error o timeout al emitir un certificado Let’s Encrypt?
Primero compruebe el DNS: el dominio debe devolver la IP de este VPS. Después, asegúrese de que el puerto 80 esté abierto en UFW, en el panel del proveedor y que no esté ocupado por otro contenedor o Nginx en el host. Ejecute sudo ss -lntp | grep -E ":80|:443" y compruebe que Docker está escuchando los puertos. Si se utiliza un proxy CDN, desactívelo temporalmente para el diagnóstico. Compruebe también docker logs nginx-proxy-manager y no supere los límites de Let’s Encrypt con intentos repetidos.
¿Por qué Nginx Proxy Manager devuelve 502 Bad Gateway?
El error 502 casi siempre significa que NPM no puede conectarse al contenedor upstream. Compruebe el nombre del contenedor, el puerto interno y la red Docker. Ejecute docker exec nginx-proxy-manager curl -I http://SERVICE:PORT. Si el nombre no se resuelve, ambos contenedores no están en la red proxy. Si la conexión es rechazada, la aplicación escucha en otro puerto o no se ha iniciado. No indique el dominio público del propio VPS en el campo Forward Hostname: esto puede crear un bucle de proxy.
¿Por qué el dominio se abre por HTTP, pero no redirige a HTTPS?
Abra la configuración del Proxy Host correspondiente y asegúrese de que el certificado esté seleccionado y el interruptor Force SSL activado. Si el certificado no se ha emitido, NPM no podrá redirigir de forma segura a HTTPS. Compruebe si se utiliza un registro DNS IPv6 AAAA independiente que apunte a otro servidor: el navegador puede conectarse mediante IPv6 mientras usted comprueba IPv4. Los comandos dig A domain y dig AAAA domain ayudan a ver rápidamente las discrepancias.
¿Qué configuración de VPS es la mínima adecuada?
Para un solo Nginx Proxy Manager y varios contenedores ligeros, la opción mínima razonable es 2 vCPU, 2 GB de RAM, 30 GB SSD e IPv4 público. Una vCPU y 1 GB de RAM pueden funcionar para pruebas, pero las actualizaciones, las operaciones TLS y los servicios adicionales agotarán rápidamente la memoria. Para Vaultwarden, Uptime Kuma, una Gitea pequeña y un reverse proxy, es mejor elegir directamente 2 vCPU, 4 GB de RAM y 60 GB NVMe.
¿Qué elegir: VPS o dedicated para esta tarea?
Para Nginx Proxy Manager, servicios personales y un equipo pequeño, elija VPS: es más económico, se despliega más rápido y normalmente se escala fácilmente en CPU, RAM y disco. Dedicated se justifica no por el proxy, sino por la carga de las aplicaciones detrás de él: un GitLab grande, una base de datos pesada, almacenamiento de archivos de terabytes, CI, procesamiento de vídeo o tráfico muy alto. Si el servidor atiende a menos de varias decenas de usuarios activos, un VPS suele ser suficiente.
¿Por qué la aplicación funciona por IP y puerto, pero no mediante el dominio?
Compruebe la configuración de Proxy Host: el scheme correcto, el nombre del contenedor y el puerto. Muchas aplicaciones generan enlaces, cookies y URL de redirección basándose en variables como DOMAIN, URL, ROOT_URL o PUBLIC_URL. Indique allí el dominio HTTPS externo, no http://localhost:PORT. Para servicios con WebSocket, active Websockets Support. Después de cambiar las variables de entorno, vuelva a crear el contenedor mediante docker compose up -d.
¿Es necesario abrir los puertos de las aplicaciones, por ejemplo 3000, 8080 o 3001, en UFW?
No, si NPM y la aplicación están conectados a la misma red Docker. No añada reglas de UFW para puertos internos ni los publique mediante ports. NPM accede al servicio mediante el nombre del contenedor dentro de la red Docker. La excepción es la depuración, pero incluso entonces es más seguro vincular temporalmente el puerto a 127.0.0.1, por ejemplo 127.0.0.1:3001:3001, y conectarse mediante un túnel SSH.
¿Cómo abrir de forma segura el panel de administración de NPM?
La mejor opción sencilla es mantener el puerto 81 vinculado a 127.0.0.1 y utilizar un túnel SSH. Para acceso permanente, puede crear un proxy host independiente con HTTPS, access list y autenticación adicional, pero esto aumenta la superficie de ataque externa. Un enfoque más fiable es acceder al panel solo mediante WireGuard o Tailscale. Cambie siempre las credenciales predeterminadas, use una contraseña única y actualice el contenedor periódicamente.
Conclusiones y próximos pasos
Ahora un VPS puede atender varias aplicaciones Docker mediante dominios independientes con HTTPS automático. Nginx Proxy Manager oculta los puertos internos de los contenedores, centraliza los certificados TLS y simplifica la incorporación de nuevos servicios.
- Añada monitorización de disponibilidad de dominios mediante Uptime Kuma y configure notificaciones en Telegram, email o Mattermost.
- Mueva las interfaces administrativas detrás de WireGuard o Tailscale y deje públicos únicamente los servicios destinados a los usuarios.
- Configure comprobaciones periódicas de restauración de copias de seguridad y documente dominios, contenedores, puertos y secretos en un repositorio privado o password manager.
Cuando aumente el número de servicios, separe las aplicaciones por proyectos Compose, limite los recursos de los contenedores y considere una base de datos independiente o un segundo VPS para copias de seguridad y monitorización.