Set up Deno application clustering with worker threads and HAProxy load balancing for production-grade high availability deployments. Configure health checks, monitoring, and automatic failover mechanisms.
Prerequisites
- Root or sudo access
- 4GB+ RAM recommended
- Basic understanding of HTTP and load balancing
- Familiarity with systemd services
What this solves
Deno applications running as single processes can become bottlenecks under high load and create single points of failure in production environments. This tutorial shows you how to configure Deno clustering with worker threads and HAProxy load balancing to distribute traffic across multiple application instances, ensuring high availability and improved performance.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest security updates and package versions.
sudo apt update && sudo apt upgrade -y
sudo dnf update -y
Install Deno runtime
Install the latest Deno runtime using the official installer. This provides the runtime environment for your clustered applications.
curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
deno --version
Install HAProxy load balancer
Install HAProxy to distribute incoming requests across multiple Deno worker processes with health checking capabilities.
sudo apt install -y haproxy
sudo dnf install -y haproxy
Create Deno application directory
Set up the directory structure for your clustered Deno application with proper ownership and permissions.
sudo mkdir -p /opt/deno-cluster
sudo useradd --system --no-create-home --shell /bin/false deno
sudo chown -R deno:deno /opt/deno-cluster
sudo chmod 755 /opt/deno-cluster
Create main application server
Create the main Deno HTTP server that will handle incoming requests. This server includes basic health check endpoints and request logging.
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const PORT = parseInt(Deno.env.get("PORT") || "8000");
const WORKER_ID = Deno.env.get("WORKER_ID") || "main";
const handler = async (request: Request): Promise
Create cluster manager script
Create a cluster manager that spawns multiple Deno worker processes using the Web Workers API for true parallelism.
interface WorkerConfig {
id: string;
port: number;
worker?: Worker;
}
const WORKER_COUNT = parseInt(Deno.env.get("WORKER_COUNT") || "4");
const BASE_PORT = 8001;
const workers: WorkerConfig[] = [];
// Initialize worker configurations
for (let i = 0; i < WORKER_COUNT; i++) {
workers.push({
id: `worker-${i + 1}`,
port: BASE_PORT + i
});
}
// Worker script for subprocess execution
const workerScript = `
self.onmessage = async (e) => {
const { workerId, port } = e.data;
// Set environment variables for the worker
Deno.env.set("WORKER_ID", workerId);
Deno.env.set("PORT", port.toString());
try {
// Import and run the server
await import("./server.ts");
} catch (error) {
console.error(\`Worker \${workerId} failed to start:\`, error);
self.postMessage({ type: "error", workerId, error: error.message });
}
};
`;
// Function to start a worker
const startWorker = (config: WorkerConfig): Promise
Configure HAProxy load balancer
Set up HAProxy to distribute traffic across the Deno worker processes with health checks and session persistence.
global
daemon
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
log stdout local0 info
# SSL/TLS configuration
tune.ssl.default-dh-param 2048
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
defaults
mode http
log global
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
# Statistics interface
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 5s
stats admin if TRUE
# Main frontend for Deno applications
frontend deno_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/certs/example.com.pem
# Security headers
http-response set-header X-Frame-Options DENY
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection "1; mode=block"
http-response set-header Referrer-Policy strict-origin-when-cross-origin
# Redirect HTTP to HTTPS
redirect scheme https if !{ ssl_fc }
default_backend deno_cluster
# Backend configuration for Deno workers
backend deno_cluster
balance roundrobin
option httpchk GET /health
http-check expect status 200
# Worker servers
server worker1 127.0.0.1:8001 check inter 5s fall 3 rise 2
server worker2 127.0.0.1:8002 check inter 5s fall 3 rise 2
server worker3 127.0.0.1:8003 check inter 5s fall 3 rise 2
server worker4 127.0.0.1:8004 check inter 5s fall 3 rise 2
# Backup server configuration
# server backup1 127.0.0.1:8005 check backup
# Connection limits and timeouts
timeout check 3s
option log-health-checks
Create systemd service for Deno cluster
Set up a systemd service to manage the Deno cluster with proper process isolation and resource limits.
[Unit]
Description=Deno Application Cluster
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/opt/deno-cluster
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-env --allow-read --unstable cluster.ts
Restart=always
RestartSec=10
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
# Environment variables
Environment=WORKER_COUNT=4
Environment=NODE_ENV=production
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/deno-cluster
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=deno-cluster
[Install]
WantedBy=multi-user.target
Configure HAProxy logging
Enable detailed logging for HAProxy to monitor load balancer performance and troubleshoot issues.
# HAProxy log configuration
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
# HAProxy logs
local0.* /var/log/haproxy.log
& stop
Set proper file permissions
Configure correct ownership and permissions for all configuration files and directories.
sudo chown -R deno:deno /opt/deno-cluster
sudo chmod 755 /opt/deno-cluster
sudo chmod 644 /opt/deno-cluster/*.ts
sudo chmod 644 /etc/systemd/system/deno-cluster.service
sudo chmod 644 /etc/haproxy/haproxy.cfg
sudo chmod 644 /etc/rsyslog.d/49-haproxy.conf
Enable and start services
Start the Deno cluster and HAProxy services, enabling them to start automatically on system boot.
sudo systemctl daemon-reload
sudo systemctl enable --now deno-cluster
sudo systemctl restart rsyslog
sudo systemctl enable --now haproxy
sudo systemctl status deno-cluster
sudo systemctl status haproxy
Configure firewall rules
Open the necessary ports for HTTP/HTTPS traffic and HAProxy statistics while maintaining security.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8404/tcp comment "HAProxy Stats"
sudo ufw reload
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=8404/tcp
sudo firewall-cmd --reload
Configure health monitoring
Create monitoring script
Set up a monitoring script that checks cluster health and can restart failed workers automatically.
#!/usr/bin/env -S deno run --allow-net --allow-run
interface HealthStatus {
worker: string;
healthy: boolean;
responseTime: number;
error?: string;
}
const WORKER_PORTS = [8001, 8002, 8003, 8004];
const HEALTH_CHECK_INTERVAL = 30000; // 30 seconds
const MAX_RESPONSE_TIME = 5000; // 5 seconds
async function checkWorkerHealth(port: number): Promise
Create monitoring service
Set up a systemd service for the health monitoring script to run continuously.
[Unit]
Description=Deno Cluster Health Monitor
After=deno-cluster.service
Requires=deno-cluster.service
[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/opt/deno-cluster
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-run monitor.ts
Restart=always
RestartSec=15
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=deno-monitor
[Install]
WantedBy=multi-user.target
Enable monitoring service
Start the monitoring service to begin continuous health checking of your Deno cluster.
sudo chmod +x /opt/deno-cluster/monitor.ts
sudo systemctl daemon-reload
sudo systemctl enable --now deno-monitor
sudo systemctl status deno-monitor
Verify your setup
Test the clustered Deno application and load balancer configuration to ensure everything is working correctly.
# Check all services are running
sudo systemctl status deno-cluster haproxy deno-monitor
# Test individual workers
curl -s http://localhost:8001/health | jq
curl -s http://localhost:8002/health | jq
curl -s http://localhost:8003/health | jq
curl -s http://localhost:8004/health | jq
# Test load balancer
curl -s http://localhost/status
curl -s http://localhost/health
# Check HAProxy stats
curl -s http://localhost:8404/stats
# View service logs
journalctl -u deno-cluster -f --lines=20
journalctl -u deno-monitor -f --lines=20
sudo tail -f /var/log/haproxy.log
# Test load distribution
for i in {1..10}; do curl -s http://localhost/ | grep Worker; done
Performance optimization
Configure worker count optimization
Adjust the number of worker processes based on your server's CPU cores and expected load.
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default configuration
WORKER_COUNT=${1:-4}
BASE_PORT=8001
HAPROXY_PORT=8000
INSTALL_DIR="/opt/deno-cluster"
DENO_USER="deno"
# Print usage
usage() {
echo "Usage: $0 [worker_count]"
echo " worker_count: Number of Deno workers to spawn (default: 4)"
exit 1
}
# Validate arguments
if [[ $# -gt 1 ]] || [[ "${1:-}" =~ ^-h|--help$ ]]; then
usage
fi
if [[ "${1:-}" =~ ^[0-9]+$ ]] && [[ $1 -gt 0 ]] && [[ $1 -le 16 ]]; then
WORKER_COUNT=$1
elif [[ $# -eq 1 ]]; then
echo -e "${RED}Error: Worker count must be a number between 1-16${NC}"
usage
fi
# Cleanup function
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop haproxy 2>/dev/null || true
systemctl stop deno-cluster 2>/dev/null || true
rm -rf "${INSTALL_DIR}" 2>/dev/null || true
userdel "${DENO_USER}" 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Error: This script must be run as root${NC}"
exit 1
fi
# Detect OS and set package manager
echo -e "${YELLOW}[1/9] Detecting operating system...${NC}"
if [[ -f /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
;;
*)
echo -e "${RED}Error: Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Error: Cannot detect operating system${NC}"
exit 1
fi
# Update system packages
echo -e "${YELLOW}[2/9] Updating system packages...${NC}"
$PKG_UPDATE
# Install dependencies
echo -e "${YELLOW}[3/9] Installing dependencies...${NC}"
$PKG_INSTALL curl unzip haproxy
# Install Deno
echo -e "${YELLOW}[4/9] Installing Deno runtime...${NC}"
curl -fsSL https://deno.land/install.sh | sh
cp ~/.deno/bin/deno /usr/local/bin/
chmod 755 /usr/local/bin/deno
echo -e "${GREEN}Deno $(deno --version | head -n1) installed${NC}"
# Create system user
echo -e "${YELLOW}[5/9] Creating system user and directories...${NC}"
if ! id "${DENO_USER}" >/dev/null 2>&1; then
useradd --system --no-create-home --shell /bin/false "${DENO_USER}"
fi
mkdir -p "${INSTALL_DIR}"
chown -R "${DENO_USER}:${DENO_USER}" "${INSTALL_DIR}"
chmod 755 "${INSTALL_DIR}"
# Create main application server
echo -e "${YELLOW}[6/9] Creating Deno application files...${NC}"
cat > "${INSTALL_DIR}/server.ts" << 'EOF'
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const PORT = parseInt(Deno.env.get("PORT") || "8000");
const WORKER_ID = Deno.env.get("WORKER_ID") || "main";
const handler = async (request: Request): Promise<Response> => {
const url = new URL(request.url);
const startTime = Date.now();
if (url.pathname === "/health") {
return new Response(JSON.stringify({
status: "healthy",
worker: WORKER_ID,
timestamp: new Date().toISOString(),
uptime: performance.now()
}), {
headers: { "content-type": "application/json" },
status: 200
});
}
if (url.pathname === "/status") {
return new Response(JSON.stringify({
worker: WORKER_ID,
port: PORT,
memory: Deno.memoryUsage(),
timestamp: new Date().toISOString()
}), {
headers: { "content-type": "application/json" }
});
}
const responseTime = Date.now() - startTime;
const response = `Hello from Deno Worker ${WORKER_ID} on port ${PORT}\nRequest processed in ${responseTime}ms\nPath: ${url.pathname}`;
console.log(`[${WORKER_ID}] ${request.method} ${url.pathname} - ${responseTime}ms`);
return new Response(response, {
headers: { "content-type": "text/plain" }
});
};
serve(handler, { port: PORT });
console.log(`Worker ${WORKER_ID} listening on port ${PORT}`);
EOF
# Create cluster manager
cat > "${INSTALL_DIR}/cluster.ts" << 'EOF'
const WORKER_COUNT = parseInt(Deno.env.get("WORKER_COUNT") || "4");
const BASE_PORT = 8001;
const workers: Array<{ id: string; port: number; process?: Deno.ChildProcess }> = [];
for (let i = 0; i < WORKER_COUNT; i++) {
workers.push({
id: `worker-${i + 1}`,
port: BASE_PORT + i
});
}
const startWorker = async (config: typeof workers[0]) => {
const cmd = new Deno.Command("deno", {
args: ["run", "--allow-net", "--allow-env", "server.ts"],
env: {
PORT: config.port.toString(),
WORKER_ID: config.id,
},
cwd: "/opt/deno-cluster",
});
config.process = cmd.spawn();
console.log(`Started ${config.id} on port ${config.port}`);
config.process.status.then(() => {
console.log(`${config.id} exited, restarting...`);
setTimeout(() => startWorker(config), 1000);
});
};
console.log(`Starting ${WORKER_COUNT} workers...`);
workers.forEach(startWorker);
const shutdown = () => {
console.log("Shutting down cluster...");
workers.forEach(w => {
if (w.process) {
w.process.kill("SIGTERM");
}
});
Deno.exit(0);
};
Deno.addSignalListener("SIGTERM", shutdown);
Deno.addSignalListener("SIGINT", shutdown);
EOF
chown -R "${DENO_USER}:${DENO_USER}" "${INSTALL_DIR}"
chmod 644 "${INSTALL_DIR}"/*.ts
# Configure HAProxy
echo -e "${YELLOW}[7/9] Configuring HAProxy load balancer...${NC}"
cat > "${HAPROXY_CONFIG}" << EOF
global
daemon
user haproxy
group haproxy
log stdout local0
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
option httplog
log global
frontend deno_frontend
bind *:${HAPROXY_PORT}
default_backend deno_workers
backend deno_workers
balance roundrobin
option httpchk GET /health
EOF
for ((i=1; i<=WORKER_COUNT; i++)); do
echo " server worker-$i 127.0.0.1:$((BASE_PORT + i - 1)) check inter 5s rise 2 fall 3" >> "${HAPROXY_CONFIG}"
done
# Create systemd service
echo -e "${YELLOW}[8/9] Creating systemd services...${NC}"
cat > /etc/systemd/system/deno-cluster.service << EOF
[Unit]
Description=Deno Cluster Application
After=network.target
Requires=network.target
[Service]
Type=simple
User=${DENO_USER}
Group=${DENO_USER}
WorkingDirectory=${INSTALL_DIR}
Environment=WORKER_COUNT=${WORKER_COUNT}
ExecStart=/usr/local/bin/deno run --allow-net --allow-env --allow-run cluster.ts
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Configure firewall if available
echo -e "${YELLOW}[9/9] Configuring services and firewall...${NC}"
if command -v ufw >/dev/null 2>&1; then
ufw allow "${HAPROXY_PORT}/tcp"
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port="${HAPROXY_PORT}/tcp"
firewall-cmd --reload
fi
# Enable and start services
systemctl daemon-reload
systemctl enable deno-cluster
systemctl enable haproxy
systemctl start deno-cluster
systemctl start haproxy
# Wait for services to start
sleep 5
# Verify installation
echo -e "${YELLOW}Verifying installation...${NC}"
if systemctl is-active --quiet deno-cluster && systemctl is-active --quiet haproxy; then
if curl -s "http://localhost:${HAPROXY_PORT}/health" | grep -q "healthy"; then
echo -e "${GREEN}✓ Installation completed successfully!${NC}"
echo -e "${GREEN}✓ Deno cluster with ${WORKER_COUNT} workers running${NC}"
echo -e "${GREEN}✓ HAProxy load balancer running on port ${HAPROXY_PORT}${NC}"
echo -e "${GREEN}✓ Health check: http://localhost:${HAPROXY_PORT}/health${NC}"
echo -e "${GREEN}✓ Status check: http://localhost:${HAPROXY_PORT}/status${NC}"
else
echo -e "${RED}✗ Services started but health check failed${NC}"
exit 1
fi
else
echo -e "${RED}✗ Failed to start services${NC}"
systemctl status deno-cluster haproxy
exit 1
fi
Review the script before running. Execute with: bash install.sh