Build a Real-Time Server Dashboard with Python & WebSockets
A Step-by-Step Guide to Monitoring Your VPS Like a Pro
If you're managing a VPS, homelab, or even a Raspberry Pi, you've probably found yourself SSH-ing in just to run htop or glances. What if you could see all your server metrics in a beautiful web dashboard that updates in real-time?
In this tutorial, I'll show you how to build a lightweight server monitoring dashboard using Python (with psutil and aiohttp) and vanilla JavaScript with Chart.js — no React, no Vue, no bloated frameworks.
What We're Building
A dark-themed dashboard that shows:
- CPU usage with real-time sparkline charts
- RAM consumption with percentage and absolute values
- Network throughput (upload/download in KB/s)
- Disk usage across all mounted partitions
- Top processes by CPU usage
- System info (OS, hostname, uptime, cores)
All data is pushed via WebSocket — so the browser updates automatically, no polling needed.
The Architecture
[psutil] → [Python aiohttp server] ←WebSocket→ [Browser Dashboard]
↕
[Static HTML/CSS/JS]
The Python backend:
- Collects system metrics using
psutil - Serves the static dashboard files
- Pushes metric snapshots to all connected browsers via WebSocket every 2 seconds
Step 1: The Backend
First, install the dependencies:
pip install psutil aiohttp
Here's the core metric collection function:
import psutil
import time
_prev_net = psutil.net_io_counters()
_prev_time = time.monotonic()
def collect_metrics():
global _prev_net, _prev_time
now = time.monotonic()
dt = now - _prev_time
_prev_time = now
# CPU
cpu_percent = psutil.cpu_percent(interval=None)
# Memory
mem = psutil.virtual_memory()
# Network (calculate rate)
net = psutil.net_io_counters()
sent_rate = (net.bytes_sent - _prev_net.bytes_sent) / dt
recv_rate = (net.bytes_recv - _prev_net.bytes_recv) / dt
_prev_net = net
# Disk
disks = []
for part in psutil.disk_partitions(all=False):
try:
usage = psutil.disk_usage(part.mountpoint)
disks.append({
"mountpoint": part.mountpoint,
"percent": usage.percent,
"total": usage.total,
"used": usage.used,
})
except PermissionError:
continue
return {
"cpu": {"percent": cpu_percent},
"memory": {"percent": mem.percent, "total": mem.total, "used": mem.used},
"network": {"sent_rate_kbs": sent_rate / 1024, "recv_rate_kbs": recv_rate / 1024},
"disks": disks,
}
The key insight: we track the previous network counter and timestamp to calculate the transfer rate in KB/s.
Step 2: WebSocket Server
Using aiohttp, we serve both the static files and the WebSocket endpoint:
import asyncio
import json
from aiohttp import web
clients = set()
async def ws_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
clients.add(ws)
try:
data = collect_metrics()
await ws.send_json(data)
async for msg in ws:
pass # We only push, never receive
finally:
clients.discard(ws)
return ws
async def broadcast(app):
while True:
await asyncio.sleep(2)
if not clients:
continue
data = collect_metrics()
payload = json.dumps(data)
for ws in set(clients):
try:
await ws.send_str(payload)
except:
clients.discard(ws)
Step 3: The Dashboard UI
For the frontend, I used a single HTML file with inline CSS and JavaScript. The dark theme uses carefully chosen colors:
:root {
--bg-primary: #0a0e17;
--bg-card: #111827;
--accent-blue: #3b82f6;
--accent-cyan: #06b6d4;
--accent-green: #10b981;
}
Each metric gets its own card with an animated progress bar and a dedicated color scheme. Chart.js handles the sparkline charts with smooth Bézier interpolation.
The WebSocket client auto-reconnects if the server goes down:
function connect() {
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateDashboard(data);
};
ws.onclose = () => {
setTimeout(connect, 3000); // Auto-reconnect
};
}
Step 4: Run It
python server.py
Open http://localhost:8765 and watch your server metrics update in real-time!
Going Further
Some ideas to extend this:
- Add alerts: Email or Discord webhook when CPU > 90%
- Authentication: Add basic auth or API key for public-facing deployments
- History storage: Log metrics to SQLite for historical analysis
- Docker: Add a Dockerfile for one-command deployment
Grab the Full Source
If you want the complete, production-ready source code with the full premium dashboard UI, Docker support, and all features — you can grab it on my Gumroad.
I'm a 16-year-old developer from Lithuania building tools for sysadmins and server owners. Follow me for more tutorials on Linux, Docker, and Python!
Top comments (1)
I found the approach to calculating network transfer rates by tracking the previous network counter and timestamp to be particularly insightful. However, I did wonder if using a more advanced method like exponential smoothing could help reduce the impact of brief network spikes on the displayed rate. Would this be a viable alternative, or are there specific reasons for choosing the current method? Additionally, have you considered adding any alerts or notifications for when certain metrics exceed predefined thresholds?