Saltar al contenido
Rack de servidores con cables de red y luces de estado junto a una laptop con una terminal abstracta
Aprender IA

Cómo desplegar una app de Node.js en un VPS paso a paso

Con systemd, Nginx y Certbot podés llevar una aplicación de Node.js a producción de forma entendible: el proceso se mantiene vivo, el proxy maneja HTTPS y el certificado se renueva solo. La verificación posterior al despliegue es tan importante como el despliegue mismo.

Por · Equipo de contenido y revisiónPublicado: 8 min de lectura

Puntos clave

Los puntos que más importan

  • Un servicio systemd mantiene la aplicación viva, la reinicia y centraliza los registros.
  • Nginx recibe el tráfico público y deriva al puerto local: HTTPS, compresión y estáticos sin cargar el proceso de Node.
  • Certbot emite y renueva el certificado, pero la renovación hay que probarla con --dry-run.
  • Un endpoint de salud permite verificar el despliegue antes y después de cada cambio.
  • Un VPS es un punto único de falla y requiere mantenimiento real: conviene saber cuándo usar una plataforma administrada.

Desplegar una aplicación de Node.js en un VPS significa que tu código deja de correr solo en tu computadora y pasa a estar disponible en internet, con un dominio, HTTPS y un proceso que se reinicia si se cae. No hace falta Docker ni Kubernetes para lograrlo: con systemd, Nginx y Certbot tenés un despliegue sólido y entendible en una tarde.

Esta guía recorre los nueve pasos, desde la compra del servidor hasta la actualización sin cortar el servicio. Incluye un ejemplo trabajado de una API con Express y PostgreSQL, los errores que suelen costar una noche entera y las limitaciones para saber cuándo conviene otra estrategia.

Qué necesitas antes de empezar

  • Un VPS con Ubuntu o Debian, 1 o 2 GB de RAM para empezar y una IP pública.
  • Un dominio o subdominio apuntando a esa IP con un registro DNS tipo A.
  • Acceso SSH con clave, no con contraseña.
  • Node.js LTS instalado en el servidor.
  • Tu aplicación en un repositorio de Git accesible desde el servidor.

Si te falta el dominio, igual podés practicar con la IP y agregar HTTPS después; certificar requiere un nombre de dominio real.

Paso 1: preparar el servidor y el usuario

Trabajar como root todo el tiempo es una mala práctica. Creá un usuario para la aplicación y dale permisos limitados:

sudo adduser --disabled-password --gecos "" apps
sudo usermod -aG sudo apps
sudo mkdir -p /srv/apps
sudo chown apps:apps /srv/apps

Después configurá el firewall permitiendo solo lo necesario:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Este es el momento de verificar que podés entrar con la clave SSH del usuario nuevo antes de cerrar la sesión de root.

Paso 2: instalar Node.js LTS y Git

Instalá Node desde el repositorio oficial de NodeSource o con el gestor de versiones que prefieras, pero fijá una versión LTS y anotala. Mezclar versiones entre tu máquina y el servidor es una fuente clásica de errores difíciles de ver.

node -v
npm -v
git --version

Paso 3: subir el código y las variables de entorno

Cloná el repositorio en una carpeta de releases y dejá la configuración fuera del código:

sudo -u apps git clone https://github.com/tu-usuario/tu-api.git /srv/apps/tu-api
cd /srv/apps/tu-api
sudo -u apps npm ci --omit=dev

Las variables de entorno van en un archivo con permisos restringidos, nunca dentro del repositorio:

sudo -u apps tee /srv/apps/tu-api/.env > /dev/null <<'ENV'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://usuario:clave@127.0.0.1:5432/miapp
ENV
sudo chmod 600 /srv/apps/tu-api/.env

Nunca subas claves a Git. Si ya lo hiciste, rotalas: borrar el commit no alcanza.

Paso 4: ejecutar la aplicación de forma persistente

Si lanzás el proceso con node index.js y cerrás la terminal, la aplicación muere. systemd se encarga de mantenerla viva, reiniciarla y capturar sus registros:

# /etc/systemd/system/tu-api.service
[Unit]
Description=API Node.js de ejemplo
After=network.target postgresql.service

[Service]
Type=simple
User=apps
Group=apps
WorkingDirectory=/srv/apps/tu-api
EnvironmentFile=/srv/apps/tu-api/.env
ExecStart=/usr/bin/node /srv/apps/tu-api/src/index.js
Restart=on-failure
RestartSec=3
TimeoutStopSec=20
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Activá y arrancá el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now tu-api
sudo systemctl status tu-api
curl -s http://127.0.0.1:3000/api/health

Ese endpoint de salud es tu mejor inversión: devuelve el estado de la aplicación y de sus dependencias (base de datos, caché) y te permite automatizar verificaciones.

Paso 5: Nginx como proxy inverso

La aplicación escucha en un puerto local; Nginx recibe el tráfico público y lo deriva. Ventajas: gestiona HTTPS, comprime, limita tamaños y sirve archivos estáticos sin ocupar el proceso de Node.

server {
    listen 80;
    server_name api.tudominio.com;

    client_max_body_size 10m;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "";
        proxy_read_timeout 120s;
    }
}

Verificá la configuración antes de recargar:

sudo nginx -t
sudo systemctl reload nginx

Si la configuración tiene un error y recargás sin probar, podés dejar el sitio caído. nginx -t es obligatorio, no opcional.

Paso 6: HTTPS con Certbot

Con el dominio apuntando al servidor, emitir el certificado es un comando:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.tudominio.com

Certbot edita la configuración, instala el certificado y programa la renovación automática. Verificala con sudo certbot renew --dry-run: una renovación que falla en silencio te deja sin sitio a los 90 días.

Paso 7: actualizar sin cortar el servicio

El ciclo de despliegue más simple y confiable para un solo servidor:

cd /srv/apps/tu-api
sudo -u apps git pull --ff-only
sudo -u apps npm ci --omit=dev
sudo systemctl restart tu-api
curl -s http://127.0.0.1:3000/api/health

Si querés evitar incluso esos segundos de corte, el patrón es publicar en un directorio nuevo y cambiar un enlace simbólico, con dos servicios en puertos distintos: el proxy apunta al que está sano y la verificación se hace antes de cambiar. Empieza simple y adoptá ese esquema cuando el tráfico lo justifique.

Paso 8: registros, respaldo y monitoreo mínimo

  • Logs de la aplicación: journalctl -u tu-api -f para ver en vivo y --since "1 hour ago" para revisar.
  • Rotación: systemd ya rota el journal; limitá el tamaño con JournalSizeMax si el disco es chico.
  • Respaldo de la base: una copia diaria con pg_dump comprimido, guardada fuera del servidor.
  • Monitoreo externo: un chequeo cada minuto a /api/health que te avise por correo o mensaje. Sin esto, te enterás de las caídas por un usuario enojado.
  • Alertas de disco: el disco lleno es la causa silenciosa de la mitad de las caídas.

Ejemplo trabajado: una API Express con PostgreSQL

Resumen del recorrido completo, con las decisiones explícitas:

DecisiónElecciónPor qué
Gestor de procesossystemdYa viene en el sistema, reinicia solo y centraliza logs
Proxy inversoNginxHTTPS, compresión y estáticos sin tocar Node
VariablesArchivo con permisos 600Evita credenciales en el repositorio
SaludEndpoint /api/healthPermite verificación automática antes y después de cada despliegue
Actualizacióngit pull más restartSimple y suficiente al inicio
HTTPSCertbot con renovación probadaUn certificado vencido es una caída evitable

Con la aplicación arriba, la verificación de cierre es esta secuencia:

curl -s -o /dev/null -w "%{http_code}\n" https://api.tudominio.com/api/health
sudo systemctl is-active tu-api
sudo journalctl -u tu-api --since "10 min ago" | tail -20

Si los tres comandos devuelven lo esperado, el despliegue está completo. Si algo falla, tenés los registros y el estado del servicio para diagnosticar sin adivinar.

Errores que te van a costar una noche

  • Olvidar abrir los puertos 80 y 443 y creer que Nginx está roto.
  • Configurar el DNS sin esperar la propagación y culpar a Certbot.
  • Dejar el proceso con node en primer plano y perderlo al cerrar SSH.
  • Usar npm install en vez de npm ci y desplegar dependencias distintas a las probadas.
  • Guardar el .env dentro del repositorio.
  • No fijar la versión de Node entre desarrollo y producción.
  • Recargar Nginx sin nginx -t.
  • No probar la renovación del certificado.
  • Exponer la base de datos a internet en lugar de dejarla en 127.0.0.1.
  • No limitar el tamaño de subida y recibir un archivo que consume toda la memoria.
  • No respaldar antes de actualizar y descubrir el problema con la base ya migrada.

Limitaciones: cuándo un VPS no es la mejor opción

  • Sin experiencia en sistemas, el mantenimiento es real. Parches de seguridad, actualizaciones del sistema operativo y monitoreo son tareas continuas.
  • Un solo servidor es un punto único de falla. Si la máquina cae, el sitio cae. Para alta disponibilidad necesitás varias instancias y balanceo.
  • Escalar requiere trabajo. Mientras una plataforma administrada escala con un botón, un VPS pide réplicas, sesiones compartidas y almacenamiento externo.
  • Costos ocultos de tiempo. El VPS puede ser más barato en dinero y más caro en horas de mantenimiento.
  • Alternativas válidas. Para prototipos y proyectos sin tráfico constante, una plataforma con despliegue desde Git suele ser mejor decisión: pagás por uso y no administrás nada.
  • Seguridad. Un servidor expuesto recibe intentos de acceso constante. Claves SSH, actualizaciones al día y firewall mínimo no son opcionales.

Preguntas frecuentes

¿Necesito Docker para desplegar en un VPS? No. Docker ayuda a reproducir entornos, pero agrega una capa que hay que aprender. Con systemd y Nginx llegás lejos y entiendes cada pieza.

¿Cuánta RAM necesito? Para una API pequeña con Node y PostgreSQL, 1 o 2 GB alcanzan para empezar. Monitoreá el consumo antes de escalar.

¿Cómo despliego varios proyectos en el mismo servidor? Un servicio por aplicación en puertos distintos y un bloque de Nginx por dominio. Aislá usuarios y carpetas por proyecto.

¿Qué hago si el proceso se reinicia en bucle? Mirá los logs con journalctl -u tu-api -n 100. Casi siempre es una variable de entorno faltante, un puerto ocupado o un error de sintaxis en el arranque.

¿Sirve un subdominio gratuito para practicar? Sirve para probar, pero para HTTPS y correo profesional necesitás un dominio propio.

¿Cómo sé si me están atacando? Revisá intentos de acceso SSH fallidos, picos de tráfico y errores 4xx inusuales en los logs de Nginx. Un firewall y autenticación por clave cubren la mayoría de los casos comunes.

Siguiente paso

Si querés construir la aplicación antes de desplegarla, la guía de Express desde cero te da la base del backend y desarrollo web: curso completo cubre el frontend. Para incorporar pruebas antes de cada despliegue, revisá testing con pytest —los conceptos de verificación aplican igual en JavaScript— y el flujo de trabajo con ramas en Git y GitHub en equipo. Si preferís una ruta guiada de proyecto a producción, mirá los cursos de Cursalo y la categoría Proyectos y casos reales.

Fuentes

Referencias externas

  1. Nginx: documentación oficialNginx
  2. systemd.service: manual oficialfreedesktop.org
  3. Certbot: instrucciones de instalaciónElectronic Frontier Foundation

Siguiente paso

Domina la IA con Cursalo

Crea tu cuenta y avanza con rutas estructuradas, proyectos reales, libros y biblioteca de prompts.

02 / LLEVAR A LA PRÁCTICA

Después de leer

Convierte una idea útil en una habilidad repetible.

Elige una ruta breve, aplícala a una tarea real y termina con algo que puedas revisar, usar o compartir.