Deploy multiple Uptime Kuma instances across different geographic locations to monitor your services from various vantage points. This setup provides comprehensive monitoring coverage, reduces false positives, and enables regional performance analysis with centralized alerting.
Prerequisites
- Multiple servers in different geographic locations
- Basic knowledge of NGINX reverse proxy
- Understanding of systemd service management
What this solves
A single monitoring instance can miss regional outages or give false positives due to local network issues. Multi-location monitoring with distributed Uptime Kuma instances provides comprehensive coverage by checking your services from multiple geographic locations. This approach helps identify regional connectivity problems, reduces monitoring blind spots, and provides better insights into global service performance.
Step-by-step installation
Update system packages
Start by updating your package manager on all monitoring nodes to ensure you get the latest versions.
sudo apt update && sudo apt upgrade -y
Install Node.js and npm
Uptime Kuma requires Node.js version 14 or higher. Install the latest LTS version along with npm package manager.
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
Create dedicated user for Uptime Kuma
Create a system user to run Uptime Kuma services securely without root privileges.
sudo adduser --system --group --home /opt/uptime-kuma uptime-kuma
sudo mkdir -p /opt/uptime-kuma
sudo chown uptime-kuma:uptime-kuma /opt/uptime-kuma
Install Uptime Kuma on primary instance
Install the primary Uptime Kuma instance which will serve as your main monitoring dashboard and central coordination point.
sudo -u uptime-kuma bash
cd /opt/uptime-kuma
npm install pm2 -g
git clone https://github.com/louislam/uptime-kuma.git .
npm run setup
Configure primary instance systemd service
Create a systemd service file to manage the primary Uptime Kuma instance with automatic startup and proper logging.
[Unit]
Description=Uptime Kuma Primary Instance
After=network.target
[Service]
Type=simple
User=uptime-kuma
Group=uptime-kuma
WorkingDirectory=/opt/uptime-kuma
ExecStart=/usr/bin/node server/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=HOST=0.0.0.0
SyslogIdentifier=uptime-kuma-primary
[Install]
WantedBy=multi-user.target
Install Uptime Kuma on monitoring nodes
Repeat the installation process on each monitoring node. Use different ports and working directories for each node.
sudo -u uptime-kuma bash
cd /opt/uptime-kuma
mkdir -p node-{us-east,eu-west,asia-pacific}
cd node-us-east
git clone https://github.com/louislam/uptime-kuma.git .
npm run setup
Configure monitoring node systemd services
Create separate systemd services for each monitoring node with unique ports and identifiers.
[Unit]
Description=Uptime Kuma US East Node
After=network.target
[Service]
Type=simple
User=uptime-kuma
Group=uptime-kuma
WorkingDirectory=/opt/uptime-kuma/node-us-east
ExecStart=/usr/bin/node server/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3002
Environment=HOST=0.0.0.0
SyslogIdentifier=uptime-kuma-us-east
[Install]
WantedBy=multi-user.target
Configure EU West monitoring node
Create the European monitoring node service with its dedicated port and configuration.
[Unit]
Description=Uptime Kuma EU West Node
After=network.target
[Service]
Type=simple
User=uptime-kuma
Group=uptime-kuma
WorkingDirectory=/opt/uptime-kuma/node-eu-west
ExecStart=/usr/bin/node server/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3003
Environment=HOST=0.0.0.0
SyslogIdentifier=uptime-kuma-eu-west
[Install]
WantedBy=multi-user.target
Setup reverse proxy for centralized access
Configure NGINX to provide centralized access to all monitoring nodes through a single domain with path-based routing.
sudo apt install -y nginx
Configure NGINX reverse proxy
Set up NGINX configuration to route requests to different Uptime Kuma instances based on the URL path. For enhanced security, consider implementing ModSecurity web application firewall protection.
upstream uptime-kuma-primary {
server 127.0.0.1:3001;
}
upstream uptime-kuma-us-east {
server 127.0.0.1:3002;
}
upstream uptime-kuma-eu-west {
server 127.0.0.1:3003;
}
server {
listen 80;
server_name monitoring.example.com;
location / {
proxy_pass http://uptime-kuma-primary;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /us-east/ {
rewrite ^/us-east(.*)$ $1 break;
proxy_pass http://uptime-kuma-us-east;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /eu-west/ {
rewrite ^/eu-west(.*)$ $1 break;
proxy_pass http://uptime-kuma-eu-west;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable NGINX configuration
Activate the NGINX configuration and restart the service to apply changes.
sudo ln -s /etc/nginx/sites-available/uptime-kuma-multi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Start all Uptime Kuma services
Enable and start all Uptime Kuma instances to begin monitoring operations.
sudo systemctl daemon-reload
sudo systemctl enable --now uptime-kuma-primary
sudo systemctl enable --now uptime-kuma-us-east
sudo systemctl enable --now uptime-kuma-eu-west
Configure firewall rules
Open necessary ports for NGINX while keeping individual Uptime Kuma ports restricted to localhost.
sudo ufw allow 'Nginx Full'
sudo ufw enable
Setup inter-node communication script
Create a synchronization script to share monitoring configurations between nodes and enable centralized management.
#!/bin/bash
Configuration
PRIMARY_URL="http://localhost:3001"
NODES=("http://localhost:3002" "http://localhost:3003")
API_KEY="your-api-key-here"
Export monitors from primary
export_monitors() {
curl -s "${PRIMARY_URL}/api/monitors" \
-H "Authorization: Bearer ${API_KEY}" > /tmp/monitors.json
}
Import monitors to nodes
import_to_nodes() {
for node in "${NODES[@]}"; do
echo "Syncing to ${node}"
curl -X POST "${node}/api/monitors/import" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d @/tmp/monitors.json
done
}
export_monitors
import_to_nodes
echo "Monitor synchronization completed"
Make sync script executable and schedule
Set proper permissions for the synchronization script and schedule it to run automatically.
sudo chmod +x /opt/uptime-kuma/sync-monitors.sh
sudo chown uptime-kuma:uptime-kuma /opt/uptime-kuma/sync-monitors.sh
Add to crontab for uptime-kuma user
sudo -u uptime-kuma crontab -e
Add this line to run synchronization every 10 minutes:
/10 * /opt/uptime-kuma/sync-monitors.sh >/dev/null 2>&1
Configure centralized alerting webhook
Set up a webhook endpoint to aggregate alerts from all monitoring nodes and prevent duplicate notifications.
const express = require('express');
const app = express();
const port = 3100;
app.use(express.json());
// Store recent alerts to prevent duplicates
const recentAlerts = new Map();
const DUPLICATE_WINDOW = 300000; // 5 minutes
app.post('/webhook/:location', (req, res) => {
const location = req.params.location;
const alert = req.body;
const alertKey = ${alert.monitorName}-${alert.status};
const now = Date.now();
// Check for recent duplicate
if (recentAlerts.has(alertKey)) {
const lastAlert = recentAlerts.get(alertKey);
if (now - lastAlert.timestamp < DUPLICATE_WINDOW) {
console.log(Duplicate alert suppressed for ${alertKey});
return res.status(200).send('Duplicate suppressed');
}
}
// Store alert
recentAlerts.set(alertKey, { timestamp: now, location });
// Forward to your notification system
console.log(Alert from ${location}: ${alert.monitorName} is ${alert.status});
// Here you would integrate with Slack, Discord, email, etc.
res.status(200).send('Alert processed');
});
app.listen(port, () => {
console.log(Alert aggregator listening on port ${port});
});
Configure monitoring checks
Set up initial admin accounts
Access each Uptime Kuma instance through your browser and set up admin accounts. Use strong, unique passwords for each instance.
# Primary instance
echo "Access primary: http://monitoring.example.com"
US East node
echo "Access US East: http://monitoring.example.com/us-east"
EU West node
echo "Access EU West: http://monitoring.example.com/eu-west"
Create monitoring checks on each node
Configure identical monitoring checks on each node to monitor the same services from different locations. This provides comprehensive coverage and helps identify regional issues.
Configure notification channels
Set up notification channels that include location information to distinguish alerts from different monitoring nodes. For more advanced monitoring integration, consider implementing Prometheus federation for metrics aggregation.
Verify your setup
Check that all services are running and accessible through the reverse proxy.
# Check service status
sudo systemctl status uptime-kuma-primary
sudo systemctl status uptime-kuma-us-east
sudo systemctl status uptime-kuma-eu-west
sudo systemctl status nginx
Check listening ports
ss -tlnp | grep -E ':(3001|3002|3003|80)'
Test connectivity
curl -I http://localhost:3001
curl -I http://localhost:3002
curl -I http://localhost:3003
Check logs for errors
sudo journalctl -u uptime-kuma-primary -n 20
sudo journalctl -u uptime-kuma-us-east -n 20
sudo journalctl -u uptime-kuma-eu-west -n 20
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service fails to start | Port already in use | Check with ss -tlnp | grep :PORT and change port in systemd service |
| Cannot access through NGINX | Proxy configuration error | Check NGINX error logs: sudo tail -f /var/log/nginx/error.log |
| WebSocket connection failed | Missing Upgrade headers | Ensure proxy_set_header Upgrade and Connection are configured in NGINX |
| High memory usage | Too many concurrent monitors | Reduce check frequency or distribute monitors across more nodes |
| Database permission errors | Incorrect ownership | sudo chown -R uptime-kuma:uptime-kuma /opt/uptime-kuma |
| Alerts not aggregating | Webhook endpoint misconfigured | Check alert-aggregator logs: node /opt/uptime-kuma/alert-aggregator.js |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
UPTIME_KUMA_USER="uptime-kuma"
UPTIME_KUMA_HOME="/opt/uptime-kuma"
DOMAIN="${1:-}"
NODES=("us-east" "eu-west" "asia-pacific")
PRIMARY_PORT=3001
NODE_PORTS=(3002 3003 3004)
# Print usage
usage() {
echo "Usage: $0 <domain>"
echo "Example: $0 monitoring.example.com"
exit 1
}
# Utility functions
log() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup on failure
cleanup() {
error "Installation failed. Cleaning up..."
systemctl stop uptime-kuma-* 2>/dev/null || true
userdel -r $UPTIME_KUMA_USER 2>/dev/null || true
rm -rf $UPTIME_KUMA_HOME 2>/dev/null || true
}
trap cleanup ERR
# Check arguments
[[ -z "$DOMAIN" ]] && usage
# Check if running as root
[[ $EUID -eq 0 ]] || { error "This script must be run as root"; exit 1; }
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
else
error "Cannot detect distribution"
exit 1
fi
echo "[1/10] Updating system packages..."
$PKG_UPDATE
echo "[2/10] Installing Node.js and npm..."
if [[ "$PKG_MGR" == "apt" ]]; then
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
$PKG_INSTALL nodejs
else
curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
$PKG_INSTALL nodejs npm
fi
echo "[3/10] Installing required packages..."
$PKG_INSTALL git nginx
echo "[4/10] Creating dedicated user..."
if ! id "$UPTIME_KUMA_USER" &>/dev/null; then
useradd --system --create-home --home-dir $UPTIME_KUMA_HOME --shell /bin/bash $UPTIME_KUMA_USER
fi
mkdir -p $UPTIME_KUMA_HOME
chown $UPTIME_KUMA_USER:$UPTIME_KUMA_USER $UPTIME_KUMA_HOME
echo "[5/10] Installing primary Uptime Kuma instance..."
sudo -u $UPTIME_KUMA_USER bash -c "
cd $UPTIME_KUMA_HOME
npm install pm2 -g
git clone https://github.com/louislam/uptime-kuma.git primary
cd primary
npm run setup
"
echo "[6/10] Creating systemd service for primary instance..."
cat > /etc/systemd/system/uptime-kuma-primary.service << EOF
[Unit]
Description=Uptime Kuma Primary Instance
After=network.target
[Service]
Type=simple
User=$UPTIME_KUMA_USER
Group=$UPTIME_KUMA_USER
WorkingDirectory=$UPTIME_KUMA_HOME/primary
ExecStart=/usr/bin/node server/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=$PRIMARY_PORT
Environment=HOST=0.0.0.0
SyslogIdentifier=uptime-kuma-primary
[Install]
WantedBy=multi-user.target
EOF
echo "[7/10] Installing monitoring nodes..."
for i in "${!NODES[@]}"; do
node="${NODES[$i]}"
port="${NODE_PORTS[$i]}"
log "Setting up node: $node on port $port"
sudo -u $UPTIME_KUMA_USER bash -c "
cd $UPTIME_KUMA_HOME
git clone https://github.com/louislam/uptime-kuma.git node-$node
cd node-$node
npm run setup
"
cat > /etc/systemd/system/uptime-kuma-$node.service << EOF
[Unit]
Description=Uptime Kuma $node Node
After=network.target
[Service]
Type=simple
User=$UPTIME_KUMA_USER
Group=$UPTIME_KUMA_USER
WorkingDirectory=$UPTIME_KUMA_HOME/node-$node
ExecStart=/usr/bin/node server/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=$port
Environment=HOST=0.0.0.0
SyslogIdentifier=uptime-kuma-$node
[Install]
WantedBy=multi-user.target
EOF
done
echo "[8/10] Configuring NGINX reverse proxy..."
if [[ "$PKG_MGR" == "apt" ]]; then
conf_file="$NGINX_CONF_DIR/uptime-kuma"
else
conf_file="$NGINX_CONF_DIR/uptime-kuma.conf"
fi
cat > "$conf_file" << EOF
server {
listen 80;
server_name $DOMAIN;
location / {
proxy_pass http://127.0.0.1:$PRIMARY_PORT;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
EOF
for i in "${!NODES[@]}"; do
node="${NODES[$i]}"
port="${NODE_PORTS[$i]}"
cat >> "$conf_file" << EOF
location /$node {
proxy_pass http://127.0.0.1:$port;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
EOF
done
echo "}" >> "$conf_file"
# Enable site for Debian-based systems
if [[ -n "$NGINX_ENABLED_DIR" ]]; then
ln -sf "$conf_file" "$NGINX_ENABLED_DIR/uptime-kuma"
rm -f "$NGINX_ENABLED_DIR/default"
fi
echo "[9/10] Starting services..."
systemctl daemon-reload
systemctl enable uptime-kuma-primary
systemctl start uptime-kuma-primary
for node in "${NODES[@]}"; do
systemctl enable uptime-kuma-$node
systemctl start uptime-kuma-$node
done
systemctl enable nginx
systemctl restart nginx
# Configure firewall
if command -v ufw >/dev/null; then
ufw allow 'Nginx Full' 2>/dev/null || true
elif command -v firewall-cmd >/dev/null; then
firewall-cmd --permanent --add-service=http 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
echo "[10/10] Verifying installation..."
sleep 5
# Check services
for service in uptime-kuma-primary uptime-kuma-us-east uptime-kuma-eu-west uptime-kuma-asia-pacific nginx; do
if systemctl is-active --quiet $service; then
log "$service is running"
else
error "$service is not running"
systemctl status $service --no-pager -l
fi
done
# Check ports
for port in $PRIMARY_PORT "${NODE_PORTS[@]}"; do
if netstat -tuln | grep -q ":$port "; then
log "Port $port is listening"
else
warn "Port $port is not listening"
fi
done
log "Installation complete!"
log "Primary instance: http://$DOMAIN"
for node in "${NODES[@]}"; do
log "Node $node: http://$DOMAIN/$node"
done
Review the script before running. Execute with: bash install.sh