Puntos clave
- La baja sobrecarga de Fastify lo hace ideal para mantener un gran número de conexiones WebSocket abiertas sin tener problemas de memoria.
- PM2 mantiene activo un proceso de Node.js en producción con recuperación instantánea tras fallos, reinicios sin tiempo de inactividad y una gestión limpia de los registros.
- Un pequeño panel de telemetría creado con Fastify y una ruta WebSocket muestra cómo se transmiten datos en tiempo real a un navegador sin necesidad de sondeos.
- Cloudways Velocity se encarga automáticamente de la daemonización de PM2 y del enrutamiento mediante proxy inverso, por lo que una aplicación WebSocket de Node.js puede pasar de GitHub a producción sin necesidad de configurar manualmente el servidor.
Crear una aplicación de Node.js en tiempo real en tu ordenador local es bastante sencillo. Solo tienes que instalar un paquete, abrir un puerto y empezar a enviar datos a un cliente. Sin embargo, cuando se trata de implementar esa misma aplicación en producción, ahí es donde las cosas se complican.
A diferencia de las peticiones HTTP normales, que se completan en milisegundos, los WebSockets mantienen las conexiones abiertas mientras el cliente y el servidor necesiten comunicarse. Si tu aplicación se cuelga o se detiene el proceso de Node.js, todos los clientes conectados pierden la conexión al instante.
Para que una aplicación de Fastify funcione de forma fiable en producción, no basta con un marco de trabajo ligero. También necesitas un gestor de procesos que mantenga tu aplicación en marcha en segundo plano y la reinicie automáticamente si surge algún problema.
En esta guía, te explicaré cómo gestiona Fastify los WebSockets, por qué PM2 para Node.js es una parte importante a la hora de ejecutar aplicaciones en producción, crearé un proyecto sencillo de WebSockets con Node.js y, después, implementaré toda la pila en Cloudways Velocity, donde PM2 ya viene integrado.
Cómo afectan los WebSockets a tu infraestructura de servidores
Las arquitecturas HTTP REST estándar funcionan siguiendo un ciclo de vida estricto de solicitud-respuesta. Un cliente inicia un protocolo de enlace TCP, envía los encabezados y los datos del cuerpo, espera la respuesta del servidor y cierra la conexión.
Si un cliente necesita datos actualizados cada pocos segundos, tiene que repetir todo ese proceso de establecimiento de conexión una y otra vez.
Los WebSockets dan un giro radical al HTTP tradicional. En lugar de abrir y cerrar una nueva solicitud cada vez que necesitas datos, empiezan con un rápido establecimiento de conexión HTTP, actualizan el protocolo y dejan ese canal totalmente abierto.
| Conexión HTTP | Conexión WebSocket |
| Cliente → Solicitud → Servidor | Cliente → Solicitud de actualización → Servidor |
| Cliente ← Respuesta ← Servidor | Cliente ← 101 Protocolos de conmutación ← Servidor |
| Se cierra la conexión | La conexión se mantiene abierta |
| Se crea una nueva conexión para cada solicitud | Se reutiliza la misma conexión |
| La comunicación es solo de tipo «solicitud → respuesta» | El cliente y el servidor pueden intercambiar datos en ambas direcciones en cualquier momento |
Bueno, mantener una conexión abierta para siempre suena genial hasta que te fijas en lo que realmente le hace a tu servidor entre bastidores:
La memoria se va agotando rápidamente. Cada socket que mantengas abierto ocupa RAM. Si tu framework está sobrecargado, tu servidor se quedará sin margen de maniobra mucho antes de que alcances una escala importante.
Un solo fallo te deja a todos fuera de combate. Con las API REST estándar, si se produce un error no gestionado en una solicitud, un usuario ve un error 500. Pero, ¿y si se cuelga un servidor WebSocket sin supervisar? ¡Pum! Todo el proceso se va al traste y miles de usuarios activos se quedan sin conexión exactamente en el mismo milisegundo.
A los proxies les encanta cortar las conexiones inactivas. Los equilibradores de carga, los cortafuegos y los proxies en la nube cortan silenciosamente las conexiones TCP si no hay tráfico durante un par de minutos. Tienes que enviar señales periódicas de «ping/pong» solo para mantener el canal activo.
Ahí es precisamente donde Fastify destaca. Como su árbol de enrutamiento central y el análisis de JSON prácticamente no tienen sobrecarga, usa mucha menos memoria por conexión que los frameworks más antiguos, lo que lo hace ideal para mantener abiertas cantidades enormes de sockets.
Por qué PM2 es imprescindible para Node.js en producción
Como Node se ejecuta en un único hilo de bucle de eventos, un rechazo sin gestionar hará que se cuelgue toda tu aplicación. Y punto.
Y, obviamente, no puedes simplemente conectarte por SSH a un servidor en producción, ejecutar «node server.js» en una ventana de terminal y cerrar el portátil. En cuanto se cierre la sesión de la terminal, tu app se caería con ella.
Ahí es donde entra en juego PM2. Imagínatelo como un supervisor que está justo encima de tu aplicación Node y se encarga de que siga funcionando pase lo que pase:
Ejecución en segundo plano: convierte tu proceso en un servicio para que se ejecute de forma silenciosa en segundo plano las 24 horas del día, los 7 días de la semana.
Recuperación inmediata tras un fallo: si se cuela un error inesperado y bloquea el hilo, PM2 reactiva inmediatamente la instancia en milisegundos.
Actualizaciones sin interrupciones: puedes implementar código nuevo y reiniciar las instancias una tras otra, para que ningún usuario pierda la conexión en pleno funcionamiento.
Registro limpio: divide tus registros estándar y flujos de error en archivos limpios y persistentes sin que tengas que configurar flujos personalizados.
Normalmente, configurar todo esto en un servidor Linux sin nada instalado implica lidiar con los archivos de servicios de systemd, crear scripts personalizados de ecosystem.config.js, configurar la rotación de logs y configurar manualmente los proxies inversos de Nginx. Sin embargo, con Cloudways Velocity, no tienes que ocuparte de nada de eso: la demonización de PM2 y el enrutamiento del proxy se gestionan automáticamente en segundo plano cuando realizas el despliegue.
Olvídate del trabajo de DevOps y lanza tu aplicación directamente
Cloudways Velocity se encarga automáticamente de la gestión de procesos de PM2 y del proxy inverso, para que tu aplicación esté siempre en línea sin necesidad de configuraciones manuales.
Mini proyecto: aplicación WebSocket en tiempo real con Node.js y Fastify
Para mostrarte cómo funciona la implementación de WebSocket con Fastify, voy a crear un mini proyecto a modo de ejemplo. Lo haré de forma sencilla: crearé un panel de telemetría en tiempo real que recoja el uso de memoria y el tiempo de actividad del servidor y lo muestre en una página web.
Para esta configuración, primero voy a compilarlo y probarlo todo localmente con VS Code y mi entorno local de Node. Después lo subiré a GitHub y lo pondré en producción en Cloudways Velocity.
Paso 1: Configurar el entorno de Fastify
Es hora de preparar la carpeta. Como tengo restricciones informáticas en mi portátil del trabajo, estoy usando un binario independiente de Node.js. Esto significa que, por defecto, mi Símbolo del sistema no sabe dónde están mis herramientas de Node.
Para solucionarlo, voy a abrir el CMD y le diré exactamente dónde he descomprimido mi carpeta de Node. En mi caso, está aquí:
set PATH=%PATH%;C:\Users\abdulrehman\Downloads\node-v24.18.0-win-x64\node-v24.18.0-win-x64

Una vez que haya seleccionado la carpeta de Node, crearé la carpeta de mi proyecto e inicializaré la aplicación de Node.
mkdir fastify-pm2-demo cd fastify-pm2-demo npm init -y

Ahora voy a instalar el framework Fastify y el plugin de WebSocket con este comando:
npm install fastify @fastify/websocket

Una vez hecho esto, voy a añadir unos scripts al archivo package.json para poder iniciar la aplicación con «npm run dev» durante el desarrollo y con «npm start» en producción.
{
"name": "fastify-pm2-demo",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@fastify/websocket": "^11.3.0",
"fastify": "^5.11.0"
}
}
Y con esto, ya tengo listo el entorno básico para mi app.
Paso 2: Crear el servidor WebSocket
Ahora voy a abrir la carpeta del proyecto que acabo de crear en VS Code.

Dentro de la carpeta, voy a crear un archivo llamado server.js. Este será el archivo que contendrá toda la lógica del backend.
Este es el código que voy a añadir a mi archivo server.js:
const Fastify = require('fastify');
const fastifyWebsocket = require('@fastify/websocket');
const fs = require('fs');
const path = require('path');
const fastify = Fastify({ logger: true });
fastify.register(fastifyWebsocket, {
options: { maxPayload: 1048576 }
});
// Serve the HTML dashboard on the root URL
fastify.get('/', (req, reply) => {
reply.type('text/html').send(fs.createReadStream(path.join(__dirname, 'index.html')));
});
fastify.register(async function (app) {
app.get('/ws', { websocket: true }, (socket, req) => {
app.log.info('Client connected to WebSocket stream');
socket.isAlive = true;
socket.on('pong', () => {
socket.isAlive = true;
});
const interval = setInterval(() => {
if (socket.readyState === 1) {
const memoryUsage = process.memoryUsage();
socket.send(JSON.stringify({
timestamp: new Date().toISOString(),
uptime: Math.floor(process.uptime()),
heapUsedMb: (memoryUsage.heapUsed / 1024 / 1024).toFixed(2)
}));
}
}, 1000);
socket.on('close', () => {
app.log.info('Client disconnected');
clearInterval(interval);
});
socket.on('error', (err) => {
app.log.error(`Socket error: ${err.message}`);
clearInterval(interval);
});
});
});
const pingInterval = setInterval(() => {
if (!fastify.websocketServer) return;
fastify.websocketServer.clients.forEach((socket) => {
if (socket.isAlive === false) {
return socket.terminate();
}
socket.isAlive = false;
socket.ping();
});
}, 30000);
fastify.addHook('onClose', (instance, done) => {
clearInterval(pingInterval);
done();
});
const start = async () => {
try {
const port = process.env.PORT || 3000;
const host = process.env.HOST || '0.0.0.0';
await fastify.listen({ port: Number(port), host });
console.log(\`Server running on http://host:{host}: host:{port}\`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();

Lo que va a hacer este código es abrir una ruta en /ws. Y cada segundo, comprobará la memoria del servidor y difundirá esos datos a todos los que estén conectados.
Ah, y también he añadido un mecanismo de ping. Con esto me refiero a que comprueba cada 30 segundos si hay conexiones inactivas para cortarlas, así la memoria del servidor no se va agotando poco a poco hasta que se cuelga.
Paso 3: Crear el panel de control HTML
Ahora voy a crear la lógica para mostrar los datos que se van a recuperar del servidor.
Para ello, voy a crear un archivo index.html justo al lado de mi archivo del servidor. Voy a escribir un poco de JavaScript básico que se conecte con el backend. Básicamente, cuando reciba un mensaje, colocará los valores en unos elementos HTML con estilo definido mediante CSS.
Así que… en lugar de dejar que se cuelgue si el servidor se reinicia, he añadido una lógica de reconexión. Si se corta la conexión, intentará volver a conectarse cada tres segundos.
Este es el código que voy a añadir a mi archivo index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fastify WebSocket Dashboard</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f4f4f5; padding: 2rem; color: #333; }
.card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); max-width: 400px; margin: 0 auto; }
h1 { font-size: 1.25rem; margin-top: 0; margin-bottom: 1.5rem; text-align: center; }
.status-container { text-align: center; margin-bottom: 1.5rem; }
.status { font-weight: bold; padding: 6px 12px; border-radius: 999px; display: inline-block; font-size: 0.875rem; }
.status.connected { background: #dcfce7; color: #166534; }
.status.disconnected { background: #fee2e2; color: #991b1b; }
.metric { display: flex; justify-content: space-between; margin-bottom: 0.75rem; border-bottom: 1px solid #e4e4e7; padding-bottom: 0.75rem; }
.metric:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
</style>
</head>
<body>
<div class="card">
<h1>Server Dashboard</h1>
<div class="status-container">
<div id="status" class="status disconnected">Disconnected</div>
</div>
<div class="metric">
<span>Server Uptime</span>
<strong id="uptime">0s</strong>
</div>
<div class="metric">
<span>Heap Memory Usage</span>
<strong id="heap">0 MB</strong>
</div>
<div class="metric">
<span>Last Ping</span>
<strong id="ping">--:--:--</strong>
</div>
</div>
<script>
// Dynamically grab the current host/port so it works on localhost, 127.0.0.1, or a live server
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws`;
let ws;
function connect() {
console.log(`Attempting to connect to ${wsUrl}...`);
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connected!');
const statusEl = document.getElementById('status');
statusEl.textContent = 'Connected';
statusEl.className = 'status connected';
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Update the DOM with the live data
document.getElementById('uptime').textContent = data.uptime + 's';
document.getElementById('heap').textContent = data.heapUsedMb + ' MB';
const date = new Date(data.timestamp);
document.getElementById('ping').textContent = date.toLocaleTimeString();
} catch (err) {
console.error("Error parsing WebSocket message:", err);
}
};
ws.onclose = () => {
console.log('WebSocket disconnected. Retrying in 3 seconds...');
const statusEl = document.getElementById('status');
statusEl.textContent = 'Disconnected';
statusEl.className = 'status disconnected';
// Auto-reconnect if the connection drops
setTimeout(connect, 3000);
};
ws.onerror = (error) => {
console.error('WebSocket Error:', error);
};
}
// Initialize connection
connect();
</script>
</body>
</html>

Paso 4: Ejecutar y probar localmente
Es hora de probarlo. Vuelvo al CMD y voy a iniciar el servidor de desarrollo:
npm run dev
Después abriré http://localhost:3000 en mi navegador.
Si todo ha funcionado, el panel de control se vuelve verde en cuanto se carga la página y los números empiezan a subir cada segundo.

Lo que pasa es que el JavaScript ha abierto una conexión persistente con el servidor Fastify, y ahora el servidor simplemente envía datos actualizados por esa conexión cada segundo. El navegador no está pidiendo actualizaciones, sino que se le envían directamente.
¿Quieres ver cómo se activa la lógica de desconexión? Voy a parar el servidor en la terminal. El panel de control se pone rojo por sí solo, sin necesidad de actualizar la página.

Vuelve a ponerlo en marcha y se vuelve a conectar solo. Sigue sin hacer falta actualizar.

Paso 5: Subir el código a GitHub
El código funciona localmente, así que ahora lo voy a subir a GitHub.
Pero antes de nada, voy a añadir un archivo .gitignore con los módulos de Node. Si no, acabaría metiendo miles de archivos de dependencias en el repositorio, y eso no me interesa.
node_modules/

Después, solo tienes que usar los comandos habituales de Git para subir todo:
git init git add . git commit -m "Initial commit for Fastify WebSocket application" git branch -M main git remote add origin https://github.com/your-username/fastify-pm2-demo git push -u origin main
Actualizo la página del repositorio y ahí están todos mis archivos.

Paso 6: Implementación en Cloudways Velocity
Ahora que el proyecto está en GitHub, puedo implementarlo en el alojamiento de Node.js de Cloudways (Velocity).
Voy a iniciar sesión en Cloudways, buscaré «Velocity» en el menú de la izquierda y haré clic en «Empezar».

El plan «Starter» es más que suficiente para esto, así que lo elegiré y haré clic en «Continuar».

Ahora quiere saber dónde está mi código. Cloudways te ofrece GitHub, GitLab o Bitbucket, y el mío está en GitHub, así que ese es el que voy a conectar.

En el menú desplegable, voy a seleccionar mi repositorio «fastify-pm2-demo» y haré clic en «Continuar».

Cloudways detecta el proyecto y rellena los ajustes automáticamente. Les echaré un vistazo rápido y haré clic en «Deploy Now».

A partir de ahí, descarga el código, inicia la aplicación con PM2 y configura el proxy inverso para reenviar el tráfico de WebSocket a la aplicación Node. Sin configuración manual de PM2, sin tocar Nginx, que es precisamente la ventaja de usar Velocity para esto.

Paso 7: Prueba final en producción
Una vez finalizada la implementación, Cloudways te proporciona una URL temporal para la aplicación.

Voy a copiar eso y lo abriré en mi navegador. Como el JavaScript del frontend comprueba el protocolo por sí solo, el cambio a una conexión segura wss:// se hace automáticamente, sin que yo tenga que configurar nada.

La página se carga, el icono se pone verde y los datos de telemetría empiezan a transmitirse, exactamente igual que lo hacían en mi ordenador.

Y ahí tienes una aplicación Fastify WebSocket lista para producción, que se ejecuta con PM2 para garantizar su estabilidad, en directo en el alojamiento Node.js de Cloudways (Velocity).
Pon tu aplicación Node.js en producción hoy mismo
Disfruta de la recuperación tras fallos de PM2 y del proxy automático para WebSocket desde el primer momento con Cloudways Velocity.
Conclusión
Así es como se lleva una aplicación en tiempo real desde tu portátil a un servidor de producción. Expliqué cómo Fastify te proporciona la velocidad necesaria para mantener muchas conexiones WebSocket abiertas sin apenas sobrecarga, y cómo PM2 es el componente que evita que esas conexiones se interrumpan cada vez que la aplicación encuentra un error o el proceso falla.
El miniproyecto lo integró todo: primero se ejecutó una transmisión de telemetría en vivo de forma local y luego se implementó en Cloudways Velocity, donde la parte de PM2 ya está gestionada, por lo que no hay que preocuparse por la configuración manual.
El código fuente completo está en mi GitHub por si quieres clonarlo. Y si te atascas en algún momento, ya sea con la configuración de Fastify, las partes relacionadas con WebSocket o la propia implementación, deja un comentario aquí abajo y te echaré una mano.
P. ¿Para qué sirve Fastify?
Es un framework ligero de Node.js para crear APIs y páginas web dinámicas. Es rápido, tiene un consumo de recursos reducido y está diseñado en torno a plugins, así que solo incorporas los componentes que tu aplicación realmente necesita.
P. ¿Para qué sirve PM2?
Mantiene las aplicaciones de Node.js en ejecución en segundo plano y las reinicia si se cuelgan. Además, te ofrece un equilibrador de carga integrado, recargas sin tiempo de inactividad y gestión automática de registros; básicamente, todo aquello que prefieres no tener que gestionar a mano en un servidor en producción.
P. ¿De verdad es Fastify más rápido que Express?
En la mayoría de las pruebas de rendimiento, sí. Express suele rondar entre 10.000 y 20.000 solicitudes por segundo, mientras que Fastify se acerca más a las 45.000 o 50.000. Gran parte de esa diferencia se debe a su serialización JSON y al enrutamiento mediante árboles radix. Que lo notes o no depende de tu tráfico, pero la diferencia es real.
P. ¿Cómo se activa la supervisión con PM2?
Añade el parámetro –watch al iniciar la aplicación: pm2 start app.js –watch. O configúralo de forma permanente añadiendo watch: true en el archivo de configuración de tu ecosistema.
P. ¿Fastify es bueno para producción?
Sí que lo es. La validación de esquemas ayuda mucho, ya que descarta las peticiones mal formadas antes de que lleguen a tu lógica, así que ganas un poco en seguridad y velocidad sin ningún esfuerzo. Entre eso, los plugins y el HTTP/2 nativo, aguanta muy bien bajo carga real.
P. ¿Qué empresas usan Fastify?
Hay unas cuantas grandes, como Capital One, Walmart, American Express, Voodoo y Handsontable, entre otras.
Start Growing with Cloudways Today.
Our Clients Love us because we never compromise on these
[email protected]
Abdul es un experto en tecnología, aficionado al café y al marketing creativo al que le encanta estar al día de las últimas actualizaciones de software y aparatos tecnológicos. También es un hábil escritor técnico capaz de explicar conceptos complejos de forma sencilla para un público amplio. Abdul disfruta compartiendo sus conocimientos sobre el sector de la Nube a través de manuales de usuario, documentación y entradas de blog.