Node.js es perfecto para APIs, aplicaciones en tiempo real y backends modernos. Pero desplegarlo en producción requiere más que node app.js.
Esta guía te enseña a configurar Node.js en tu VPS de forma profesional.
Instalación de Node.js
Opción 1: NVM (Recomendado)
NVM permite instalar y cambiar entre versiones de Node fácilmente.
# Instalar NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Cargar NVM
source ~/.bashrc
# Verificar
nvm --version
# Instalar Node.js LTS
nvm install --lts
# O versión específica
nvm install 20
# Ver versiones instaladas
nvm ls
# Usar versión específica
nvm use 20
# Establecer por defecto
nvm alias default 20
Opción 2: NodeSource (sistema)
# Node.js 20.x LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verificar
node -v
npm -v
Instalar Yarn (opcional)
# Con npm
npm install -g yarn
# Verificar
yarn -v
Estructura de proyecto
Aplicación básica
/var/www/mi-app/
├── src/
│ └── index.js
├── package.json
├── package-lock.json
├── .env
├── .env.example
└── ecosystem.config.js
package.json ejemplo
{
"name": "mi-app",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"build": "npm run build:css",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"engines": {
"node": ">=20.0.0"
}
}
App Express básica
// src/index.js
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'API funcionando', env: process.env.NODE_ENV });
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`Servidor corriendo en puerto ${PORT}`);
});
PM2: Process Manager para Producción
PM2 mantiene tu app corriendo, la reinicia si falla, y gestiona logs.
Instalar PM2
npm install -g pm2
Comandos básicos
# Iniciar app
pm2 start src/index.js --name mi-app
# Con variables de entorno
pm2 start src/index.js --name mi-app --env production
# Ver apps corriendo
pm2 list
pm2 ls
# Ver logs
pm2 logs
pm2 logs mi-app
# Monitorizar
pm2 monit
# Reiniciar
pm2 restart mi-app
pm2 restart all
# Parar
pm2 stop mi-app
pm2 stop all
# Eliminar
pm2 delete mi-app
Archivo ecosystem.config.js
// ecosystem.config.js
module.exports = {
apps: [{
name: 'mi-app',
script: 'src/index.js',
instances: 'max', // O número específico
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// Logs
log_file: '/var/log/pm2/mi-app.log',
error_file: '/var/log/pm2/mi-app-error.log',
out_file: '/var/log/pm2/mi-app-out.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss',
// Restart
max_memory_restart: '500M',
restart_delay: 3000,
max_restarts: 10,
// Watch (solo desarrollo)
watch: false,
ignore_watch: ['node_modules', 'logs']
}]
};
# Iniciar con ecosystem
pm2 start ecosystem.config.js --env production
# Recargar sin downtime
pm2 reload ecosystem.config.js --env production
PM2 en arranque del sistema
# Generar script de startup
pm2 startup
# Ejecutar el comando que muestra (como root)
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u tu_usuario --hp /home/tu_usuario
# Guardar estado actual
pm2 save
Nginx como Proxy Reverso
Configuración básica
# /etc/nginx/sites-available/mi-app
upstream nodejs {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name api.tudominio.com;
location / {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# Habilitar
sudo ln -s /etc/nginx/sites-available/mi-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Con SSL (Let’s Encrypt)
sudo certbot --nginx -d api.tudominio.com
WebSockets
location /socket.io/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400; # 24 horas para WS
}
Caché de archivos estáticos
server {
# ... proxy config ...
# Servir estáticos directamente
location /public/ {
alias /var/www/mi-app/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Variables de Entorno
Archivo .env
# .env
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/db
REDIS_URL=redis://localhost:6379
JWT_SECRET=tu-secreto-muy-largo
API_KEY=clave-externa
Cargar con dotenv
// Al inicio de la app
require('dotenv').config();
// Usar
const dbUrl = process.env.DATABASE_URL;
Con PM2
// ecosystem.config.js
module.exports = {
apps: [{
name: 'mi-app',
script: 'src/index.js',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'postgresql://...'
}
}]
};
Archivo .env separado
# .env.production
NODE_ENV=production
PORT=3000
# ...
# Cargar env específico
pm2 start ecosystem.config.js --env production
Cluster Mode
¿Por qué cluster?
Node.js es single-threaded. Cluster permite usar todos los cores del CPU.
// ecosystem.config.js
module.exports = {
apps: [{
name: 'mi-app',
script: 'src/index.js',
instances: 'max', // Usa todos los cores
exec_mode: 'cluster', // Modo cluster
// O número específico
// instances: 4
}]
};
Ver instancias
pm2 list
# Verás múltiples instancias: mi-app-0, mi-app-1, etc.
# Escalar
pm2 scale mi-app 4
Logs y Monitorización
PM2 Logs
# Ver logs en tiempo real
pm2 logs
# Solo una app
pm2 logs mi-app
# Últimas N líneas
pm2 logs mi-app --lines 100
# Limpiar logs
pm2 flush
Rotación de logs
# Instalar módulo
pm2 install pm2-logrotate
# Configurar
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
PM2 Monit
# Dashboard en terminal
pm2 monit
Métricas básicas
# Info de app
pm2 show mi-app
# Métricas
pm2 info mi-app
Deploy automatizado
Script de deploy
#!/bin/bash
# /root/scripts/deploy-node.sh
APP_DIR="/var/www/mi-app"
BRANCH="main"
cd $APP_DIR
# Pull cambios
git pull origin $BRANCH
# Instalar dependencias
npm ci --production
# Build si es necesario
npm run build 2>/dev/null || true
# Reload sin downtime
pm2 reload ecosystem.config.js --env production
echo "Deploy completado: $(date)"
Con Git hook
#!/bin/bash
# /var/repo/mi-app.git/hooks/post-receive
TARGET="/var/www/mi-app"
GIT_DIR="/var/repo/mi-app.git"
while read oldrev newrev ref
do
if [ "$ref" = "refs/heads/main" ]; then
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f main
cd $TARGET
npm ci --production
npm run build 2>/dev/null || true
pm2 reload ecosystem.config.js --env production
echo "Deploy completado"
fi
done
Seguridad
Helmet (headers de seguridad)
const helmet = require('helmet');
app.use(helmet());
Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100 // límite por IP
});
app.use('/api/', limiter);
CORS
const cors = require('cors');
app.use(cors({
origin: ['https://tudominio.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));
No exponer stack traces
// Solo en producción
if (process.env.NODE_ENV === 'production') {
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Error interno' });
});
}
Problemas comunes
Puerto en uso
# Ver qué usa el puerto
sudo lsof -i :3000
sudo netstat -tlnp | grep 3000
# Matar proceso
sudo kill -9 PID
App se reinicia constantemente
# Ver logs de error
pm2 logs mi-app --err
# Ver últimos reinicios
pm2 show mi-app
Memoria alta
// En ecosystem.config.js
max_memory_restart: '500M'
# Ver uso de memoria
pm2 monit
Permisos de archivos
# Si usas NVM como usuario no-root
sudo chown -R $USER:$USER /var/www/mi-app
Preguntas frecuentes
¿NVM o instalación de sistema?
NVM para desarrollo y cuando necesitas múltiples versiones. Instalación de sistema (NodeSource) para producción con una sola versión. NVM es más flexible pero requiere configuración adicional para PM2.
¿Cuántas instancias de cluster debo usar?
Generalmente el número de cores del CPU. PM2 con 'instances: max' lo detecta automáticamente. Para VPS pequeños (1-2 cores), 2-4 instancias es razonable.
¿Por qué usar Nginx delante de Node?
Nginx maneja mejor archivos estáticos, SSL, y múltiples dominios. También actúa como buffer entre internet y tu app, mejorando seguridad y rendimiento.
¿PM2 o systemd para Node.js?
PM2 es más fácil y tiene más features (cluster, logs, monit). Systemd es más básico pero está integrado en el sistema. Para la mayoría, PM2 es la mejor opción.
¿Cómo actualizo Node.js sin downtime?
Con NVM: instala la nueva versión, cambia el default, y haz pm2 reload. PM2 mantendrá las instancias corriendo mientras recarga.
Nuestra recomendación
Setup básico:
- Instalar Node.js con NVM
- PM2 para gestión de procesos
- Nginx como proxy reverso
- SSL con Let’s Encrypt
Para producción:
- Cluster mode según cores
- Variables en .env
- Logs con rotación
- Health endpoint para monitorización
¿Necesitas hosting Node.js gestionado? La administración gestionada de Avantys incluye configuración y mantenimiento de aplicaciones Node.js.
Conclusión
Node.js en producción requiere más que ejecutar node app.js. Con PM2, Nginx y buenas prácticas de seguridad, tu aplicación estará lista para manejar tráfico real.
Empieza con PM2 básico y ve añadiendo cluster, logs y monitorización según crezca tu app.
¿Necesitas un VPS para Node.js? Explora los VPS de Avantys con Node.js preconfigurado.
¿Quieres que lo hagamos por ti?
En Avantys gestionamos tu web, hosting y crecimiento digital de punta a punta. Tú a lo importante.