Learn how we build a scalable real-time app with Node.js, Express, and Socket.IO on Ubuntu 24.04.
Introduction
Web environment, real-time features like live chat, notifications, and collaborative dashboards have moved from “nice-to-have” to “must-have.” In this tutorial, we walk through every configuration and code snippet needed to launch a production-grade WebSocket server on Ubuntu 24.04 using Node.js and Socket.IO.
We explain why each step matters— from installing the latest Node LTS via NodeSource, to setting up Nginx as a reverse proxy with SSL, to managing uptime with PM2. By the end, our application will support secure, low-latency two-way communication, ready for scalable deployments. Let’s dive in and transform our Ubuntu server into a real-time powerhouse.
Prerequisites
Before we begin, let’s ensure we have the following in place:
- A Ubuntu 24.04 dedicated server or KVM VPS.
- A basic programming knowledge.
- A domain name pointing at server IP.
Setup a Real-Time Node.js & Socket.IO on Ubuntu
1. Preparing the Ubuntu 24.04 Server
Before anything else, keep our system secure and up to date:
sudo apt update && sudo apt upgrade -y
Next, install essential build tools:
sudo apt install -y build-essential curl git
- build-essential gives us gcc, make, and other compilers needed by some Node modules.
- curl and git will let us fetch Node.js installers and clone repos.
2. Installing Node.js via NodeSource
Ubuntu’s default Node.js may lag behind current versions. We’ll use NodeSource to get the latest LTS:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
- setup_lts.x ensures we track the latest long-term support (e.g., 22.x).
- nodejs includes npm automatically.
Verify versions:
node -v # e.g. v22.x.x
npm -v # e.g. 11.x.x
3. Bootstrapping Our Socket.IO Project
Create a project directory and enter it:
mkdir realtime-app && cd realtime-app
Initialize NPM:
npm init -y
Install dependencies:
npm install express socket.io
- express for handling HTTP requests and serving static files.
- socket.io for WebSocket abstraction, automatic reconnection, and rooms.
Install dev-tools (optional):
npm install --save-dev nodemon
nodemon restarts our server on file changes—handy during development.
4. Writing the WebSocket Server
Create a file named server.js
:
nano server.js
Add following code:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "*", methods: ["GET","POST"] }
});
// Serve a simple client
app.use(express.static('public'));
io.on('connection', socket => {
console.log(`Client connected [id=${socket.id}]`);
// Listen for messages from clients
socket.on('message', data => {
console.log(`Received: ${data}`);
// Broadcast to all connected clients
io.emit('message', data);
});
socket.on('disconnect', () => {
console.log(`Client disconnected [id=${socket.id}]`);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
- Why split http.createServer and Socket.IO? It lets us handle both HTTP and WebSocket on the same port.
- CORS config allows our client to connect from any origin during development; tune it down in production.
5. Building a Simple Client
Create public directory
sudo mkdir public
Create index.html
inside public
directory
nano public/index.html
Add following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Real-Time Chat</title>
</head>
<body>
<h1>Real-Time Chat</h1>
<div id="messages"></div>
<input id="input" autocomplete="off"/><button id="send">Send</button>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const input = document.getElementById('input');
const send = document.getElementById('send');
const msgs = document.getElementById('messages');
send.addEventListener('click', () => {
const text = input.value.trim();
if (!text) return;
socket.emit('message', text);
input.value = '';
});
socket.on('message', msg => {
const el = document.createElement('div');
el.textContent = msg;
msgs.appendChild(el);
});
</script>
</body>
</html>
- Why static files? Socket.IO client script is served automatically from the server at /socket.io/socket.io.js.
- How it works: On sending, we emit a message event; on receiving, we append messages to the DOM.
6. Managing the Process with PM2
To keep our server running and restart on failure:
npm install -g pm2
pm2 start server.js --name realtime-app
pm2 save
pm2 startup systemd # Auto-start on reboot
- pm2 save writes the running processes to be resurrected on system boot.
- systemd integration means our app will start automatically when the server reboots.
7. Configuring Nginx as a Reverse Proxy
A reverse proxy handles HTTPS, load balancing, and scale. Install Nginx:
sudo apt install -y nginx
Create Nginx conf file:
nano /etc/nginx/sites-available/realtime
Add following code:
server {
listen 80;
server_name example.com; # replace with our domain
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # WebSocket upgrade
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Enable and test:
sudo ln -s /etc/nginx/sites-available/realtime /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
proxy_set_header
Upgrade is essential for WebSocket handshakes.- No hardcoding ports on clients—we’ll connect simply to
https://example.com
.
8. Configure Firewall
If you have enabled UFW, add HTTP and HTTPS ports:
ufw allow 80/tcp
ufw allow 443/tcp
9. Securing with HTTPS (Let’s Encrypt)
Encrypt traffic for privacy:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
- Certbot auto-edits our Nginx config to listen on 443 and handle renewals.
- Test certificate renewal: sudo certbot renew --dry-run.
10. Testing and Scaling
- Local test: curl
http://localhost:3000
should fetch our index.html. - Browser test: Visit
https://example.com
and open DevTools → Network to confirm WebSocket frames (look for “101 Switching Protocols
”).
Scaling tips:
- Use Redis adapter for Socket.IO to emit events across multiple Node.js instances.
- Add load balancers (e.g., AWS ELB) in front of multiple Nginx servers.
- Monitor with PM2 dashboard or integrate with Prometheus.
11. Maintenance Best Practices %
- Logging: Integrate Winston or Bunyan for structured logs.
- Environment variables: Store secrets and ports in a .env file and load via dotenv.
- Automatic restarts: Use PM2’s watch mode (careful in production).
- Health checks: Create a simple /health endpoint returning 200 OK for uptime monitoring.
- Backups: Regularly snapshot your server or use containerized deployments (Docker).
We've learn how we build a scalable real-time app with Node.js, Express, and Socket.IO on Ubuntu 24.04. By following these steps, we’ve built a robust real-time application on Ubuntu 24.04, complete with process management, secure WebSocket proxying, and SSL. Our application can now handle live updates—chat messages, live dashboards, collaborative tools—with confidence and scalability.