Un servidor WebSocket está diseñado para recibir datos en tiempo real, a diferencia de un servidor web clásico donde abres un enlace en tu navegador y obtienes datos (por ejemplo, html, xml, json, etc.). Con un servidor clásico, este establece una conexión -> transmite datos -> termina la conexión.
Un servidor WebSocket funciona de manera diferente: estableces una conexión con él y esta permanece continuamente activa. Tan pronto como los datos cambian, el servidor los transmite al cliente. Debido a que utiliza comunicación bidireccional, la conexión siempre está activa y no se termina.
1. Aplicación
Para recibir datos en tiempo real, por ejemplo, si deseas mostrar los visitantes de tu sitio web o el estado de los pedidos de tu tienda en línea en vivo en una página web separada o incluso en un dispositivo separado.
2. Cómo funciona
Un servidor WebSocket es un servicio separado que se ejecuta en un puerto dedicado que tú asignas para conexiones posteriores. El protocolo WS (servidor WebSocket) o WSS (servidor WebSocket + SSL) te permite configurar la salida de datos con actualizaciones en tiempo real, por ejemplo, a través de un navegador usando html + JS.
¡Tenga en cuenta! Dado que el servicio requiere abrir un nuevo puerto, la configuración y ejecución de dicho servicio solo es posible en un servidor virtual. Esta es precisamente la razón por la cual un servidor WebSocket no está disponible para los servicios de alojamiento virtual.
Imagine si cada cliente de alojamiento virtual quisiera ejecutar dicho servicio: se necesitaría abrir un puerto separado para cada uno, lo que comprometería la seguridad del propio servicio. Independientemente del proveedor de alojamiento, la configuración y ejecución solo son posibles en un servidor VPS/VDS.
3. Ejemplo de tarea
Supongamos que tenemos un script PHP https://domain.tld/online-orders.php que genera la salida de datos en formato JSON, y queremos que estos datos se muestren en tiempo real. Actualmente, para lograr esto se requiere una actualización constante.
Tenga en cuenta que, si bien para una PC y un navegador los datos se pueden actualizar aumentando la frecuencia de las solicitudes, en el caso de solicitudes frecuentes y actualizaciones de datos JSON desde un microcontrolador, esto simplemente lo haría colgarse durante 1-2 segundos. Y, por ejemplo, una solicitud POST/GET regular cada 20 segundos todavía no es realmente en tiempo real.
Por lo tanto, necesitamos WSS. Lanzaremos un nuevo VPS en Debian 12 con una configuración mínima de recursos. No se necesita un alto rendimiento para WSS. Todas las configuraciones se realizarán como el usuario root.
3.1 Instalación del servicio
apt update
apt install -y curl git nodejs npm
3.2 Creación del proyecto WebSocket
mkdir ~/websocket-server
cd ~/websocket-server
npm init -y
npm install ws node-fetch express
3.3 Cree un archivo de configuración en la raíz del proyecto websocket-server
nano server.js
import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";
// === Configuración ===
const PORT = 8080; // Elegir un puerto para este servidor WebSocket
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // una vez cada 1 segundo
// === Certificados HTTPS ===
// (Si se necesita WSS, se requiere un certificado SSL. Puede usar Let's Encrypt o autofirmado)
const options = {
key: fs.readFileSync("/etc/ssl/private/your-key.pem"),
cert: fs.readFileSync("/etc/ssl/certs/your-cert.pem")
};
// === Servidor HTTP(S) + WebSocket ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });
let latestData = null;
// === Solicitud JSON periódica ===
async function updateData() {
try {
const res = await fetch(FETCH_URL);
const json = await res.json();
latestData = json;
// Enviar a todos los clientes conectados
wss.clients.forEach(client => {
if (client.readyState === 1) { // WebSocket.OPEN
client.send(JSON.stringify(json));
}
});
} catch (err) {
console.error("Error al obtener datos:", err);
}
}
setInterval(updateData, FETCH_INTERVAL);
updateData(); // carga inicial
// === Conexión WebSocket ===
wss.on("connection", ws => {
console.log("Cliente conectado");
if (latestData) ws.send(JSON.stringify(latestData));
ws.on("close", () => console.log("Cliente desconectado"));
});
server.listen(PORT, () => {
console.log(`Servidor WSS iniciado en el puerto ${PORT}`);
});
3.4 Configuración de SSL para WSS a través de Let's Encrypt
Para SSL, se requiere un dominio. Usaremos un subdominio, por ejemplo, ws.domain.tld.
apt install certbot python3-certbot-nginx
certbot certonly --standalone -d ws.domain.tld
Siga el breve procedimiento, proporcionando una dirección de correo electrónico para las notificaciones. Al finalizar, el certificado y la clave estarán aquí:
/etc/letsencrypt/live/ws.domain.tld/fullchain.pem
/etc/letsencrypt/live/ws.domain.tld/privkey.pem
Reemplace las rutas en el archivo server.js:
const options = {
key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};
3.5 Ejecución y depuración
node server.js
Recibimos un error SyntaxError: Cannot use import statement outside a module.
En este caso, debe modificar el archivo package.json agregando la línea "type": "module",.
Ejemplo de configuración correcta de package.json:
{
"name": "websocket-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
...
Ejecutar de nuevo:
node server.js
Servidor WSS iniciado en el puerto 8080
Cliente conectado
3.6 Configurar el inicio automático:
npm install -g pm2
pm2 start server.js --name websocket
pm2 save
pm2 startup
3.7 Verificar el funcionamiento del servicio
pm2 list
3.8 Conectar usando HTML y JS
Cree un archivo llamado wss-client.html en su PC con el siguiente contenido:
No puede conectarse a un WebSocket directamente a través de un dominio en un navegador como un servidor web; es necesario crear un script. Especifique su dominio en lugar de ws.domain.tld.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Prueba WSS</title>
<style>
body {
font-family: monospace;
background: #111;
color: #0f0;
padding: 20px;
}
input, button {
background: #222;
color: #0f0;
border: 1px solid #0f0;
padding: 6px 10px;
font-family: monospace;
}
#log {
margin-top: 20px;
white-space: pre-wrap;
background: #000;
border: 1px solid #0f0;
padding: 10px;
height: 400px;
overflow-y: auto;
}
</style>
</head>
<body>
<h2>🔌 Cliente de prueba WebSocket</h2>
<label>URL del servidor:</label>
<input id="wsUrl" type="text" size="50" value="wss://ws.domain.tld:8080">
<button onclick="connect()">Conectar</button>
<button onclick="disconnect()">Desconectar</button>
<div id="status">Estado: <b>Desconectado</b></div>
<div id="log"></div>
<script>
let ws;
function log(msg) {
const logDiv = document.getElementById('log');
logDiv.innerText += msg + "\n";
logDiv.scrollTop = logDiv.scrollHeight;
}
function connect() {
const url = document.getElementById('wsUrl').value;
ws = new WebSocket(url);
log("🔄 Conectando a " + url + " ...");
document.getElementById('status').innerHTML = "Estado: <b>Conectando...</b>";
ws.onopen = () => {
log("✅ Conexión establecida");
document.getElementById('status').innerHTML = "Estado: <b style='color:lime'>Conectado</b>";
};
ws.onmessage = event => {
try {
const json = JSON.parse(event.data);
log("📦 Recibido:\n" + JSON.stringify(json, null, 2));
} catch (e) {
log("📦 Texto:\n" + event.data);
}
};
ws.onclose = () => {
log("❌ Conexión cerrada");
document.getElementById('status').innerHTML = "Estado: <b style='color:red'>Desconectado</b>";
};
ws.onerror = err => {
log("⚠️ Error: " + err.message);
};
}
function disconnect() {
if (ws) {
ws.close();
ws = null;
}
}
</script>
</body>
</html>
3.9 Visualización de registros
La conexión debería funcionar; tan pronto como los datos cambien en nuestro script, el WebSocket los actualizará para todos los clientes conectados.
Puede ver el registro de eventos con el comando:
pm2 logs websocket
0|websocket | 5 visitas actualizadas
4. Autorización
En el ejemplo anterior, cualquiera puede conectarse al servidor WebSocket, así que agreguemos autorización basada en tokens.
Código server.js actualizado:
import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";
import url from "url";
// === Configuración ===
const PORT = 8080;
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // cada 1 segundo
const VALID_TOKENS = ["ABC123", "ESP32TOKEN", "MYSECRET"]; // lista de tokens permitidos
// === SSL ===
const options = {
key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};
// === Crear servidor ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });
let latestData = null;
// === Función para obtener JSON periódicamente ===
async function updateData() {
try {
const res = await fetch(FETCH_URL);
const json = await res.json();
latestData = json;
// Enviar a todos los clientes activos
wss.clients.forEach(client => {
if (client.readyState === 1) { // WebSocket.OPEN
client.send(JSON.stringify(json));
}
});
} catch (err) {
console.error("Error al obtener datos:", err);
}
}
setInterval(updateData, FETCH_INTERVAL);
updateData();
// === Manejar conexiones ===
wss.on("connection", (ws, req) => {
const query = url.parse(req.url, true).query;
const token = query.token;
if (!VALID_TOKENS.includes(token)) {
console.log(`❌ Conexión rechazada con token: ${token}`);
ws.close(4001, "Invalid token");
return;
}
console.log(`✅ Cliente conectado con token: ${token}`);
// Enviar el último estado
if (latestData) ws.send(JSON.stringify(latestData));
ws.on("close", () => {
console.log(`🔌 Cliente (${token}) desconectado`);
});
});
server.listen(PORT, () => {
console.log(`✅ Servidor WSS iniciado en el puerto ${PORT}`);
});
4.1 Conexión con autorización por token
Para un ejemplo usando HTML + JS, simplemente cambie la línea a:
<input id="wsUrl" value="wss://ws.domain.tld:8080/?token=ABC123">
5. En conclusión
La configuración está completa; ahora todos los clientes del servidor WebSocket reciben datos en tiempo real utilizando cifrado. En nuestro ejemplo, usamos un script PHP como intermediario entre el sistema (para obtener datos usando una API) y el servidor WebSocket (para transmitirlos).
El script PHP en sí mismo puede eliminarse, y se puede usar una solicitud API directamente en server.js con autorización API al sistema, lo que sería aún más eficiente. Sin embargo, el artículo se volvería bastante extenso, ya que sería necesario cubrir el almacenamiento seguro de la clave API en un archivo .env y la instalación del paquete npm install dotenv para trabajar con datos del archivo de configuración.

