This guide covers security hardening for Uptime Kuma deployments, including container security, network isolation, authentication, TLS configuration, audit logging, and compliance considerations for production environments.
Security First: Always implement security controls before exposing Uptime Kuma to production traffic.
Latest Version: v2.1.1 (February 13, 2026)
| Threat | Impact | Mitigation |
|---|---|---|
| Unauthorized Access | High | 2FA, strong passwords, IP restrictions |
| Data Breach | Critical | Encryption, access controls, audit logs |
| Container Escape | Critical | Non-root, capabilities drop, read-only FS |
| Man-in-the-Middle | High | TLS 1.3, HSTS, certificate pinning |
| Denial of Service | Medium | Rate limiting, resource limits |
| Credential Theft | High | Docker secrets, vault integration |
Password Requirements:
Minimum Length: 16 characters
Complexity: Upper, lower, number, special character
History: Last 10 passwords remembered
Expiration: 90 days (recommended)
Lockout: 5 failed attempts = 15 minute lockout
Enable Two-Factor Authentication (2FA):
Note: Uptime Kuma v2.0+ includes built-in 2FA support with TOTP.
| Role | Permissions | Use Case |
|---|---|---|
| Admin | Full access | System administrators |
| Editor | Create/edit monitors | Operations team |
| Viewer | Read-only access | Stakeholders |
| API User | API access only | Automation systems |
# Recommended session settings
session:
timeout: 86400 # 24 hours
secure: true # HTTPS only
http_only: true # No JavaScript access
same_site: Strict
Add additional authentication layer with Nginx:
location / {
auth_basic "Uptime Kuma Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
# Also allow specific IPs without auth
satisfy any;
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://uptime-kuma:3001;
}
Generate htpasswd:
sudo apt-get install apache2-utils
htpasswd -c /etc/nginx/.htpasswd admin
services:
uptime-kuma:
image: louislam/uptime-kuma:2.1.1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- uptime-kuma-data:/app/data
environment:
- TZ=Europe/Berlin
- NODE_ENV=production
# Security options
security_opt:
- no-new-privileges:true
# Drop all capabilities, add only required
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Run as non-root user
user: "1000:1000"
# Read-only filesystem (with tmpfs for writable paths)
read_only: false # Must be false for SQLite
# Temporary filesystems
tmpfs:
- /tmp:noexec,nosuid,size=100m
- /var/tmp:noexec,nosuid,size=50m
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
Image Security:
# Use specific version tags (not :latest)
louislam/uptime-kuma:2.1.1
# Verify image signatures
docker trust inspect louislam/uptime-kuma:2.1.1
# Scan for vulnerabilities
docker scout cve louislam/uptime-kuma:2.1.1
# Use slim images for reduced attack surface
louislam/uptime-kuma:2-slim
Rootless Container (v2.0+):
# Use rootless image variant
louislam/uptime-kuma:2-slim-rootless
# Or configure rootless Docker
dockerd-rootless-setuptool.sh install
Store sensitive data securely:
services:
uptime-kuma:
image: louislam/uptime-kuma:2
secrets:
- db_password
- db_username
environment:
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
- DATABASE_USERNAME_FILE=/run/secrets/db_username
secrets:
db_password:
file: ./secrets/db_password.txt
db_username:
file: ./secrets/db_username.txt
Create secrets:
mkdir -p secrets
echo -n "secure-password-here" > secrets/db_password.txt
echo -n "uptime_kuma" > secrets/db_username.txt
chmod 600 secrets/*
UFW (Ubuntu/Debian):
# Allow SSH
sudo ufw allow 22/tcp
# Allow HTTP/HTTPS (if running reverse proxy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Block direct access to Uptime Kuma (use reverse proxy)
sudo ufw deny 3001/tcp
# Enable firewall
sudo ufw enable
sudo ufw status verbose
firewalld (RHEL/CentOS):
# Add services
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Block direct access
sudo firewall-cmd --permanent --add-rich-rule='rule port port=3001 protocol=tcp reject'
# Reload
sudo firewall-cmd --reload
Docker Network Segmentation:
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
driver_opts:
com.docker.network.bridge.enable_icc: "false"
backend:
driver: bridge
internal: true # No external access
ipam:
config:
- subnet: 172.28.1.0/24
Bind to Localhost Only:
ports:
- "127.0.0.1:3001:3001" # Localhost only
Nginx with TLS 1.3:
server {
listen 443 ssl http2;
server_name uptime.example.com;
# Certificate paths
ssl_certificate /etc/letsencrypt/live/uptime.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/uptime.example.com/privkey.pem;
# TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location / {
proxy_pass http://uptime-kuma:3001;
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;
proxy_read_timeout 86400;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name uptime.example.com;
return 301 https://$server_name$request_uri;
}
Let’s Encrypt Certificate:
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d uptime.example.com
AppArmor Profile:
# Create /etc/apparmor.d/docker-uptime-kuma
sudo tee /etc/apparmor.d/docker-uptime-kuma << 'EOF'
#include <tunables/global>
profile docker-uptime-kuma flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
network inet udp,
network inet icmp,
deny network raw,
/app/data/** rw,
/tmp/** rw,
deny /etc/passwd r,
deny /etc/shadow r,
}
EOF
# Load profile
sudo apparmor_parser -r /etc/apparmor.d/docker-uptime-kuma
SELinux Context:
# Set proper SELinux context
sudo chcon -Rt svirt_sandbox_file_t /opt/uptime-kuma/data
# Verify
ls -Z /opt/uptime-kuma/data
Add to /etc/sysctl.d/99-uptime-kuma.conf:
# Network security
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.forwarding = 1
# Prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
Apply:
sudo sysctl --system
# Set secure permissions on data directory
sudo mkdir -p /opt/uptime-kuma/data
sudo chown -R 1000:1000 /opt/uptime-kuma/data
sudo chmod -R 750 /opt/uptime-kuma/data
# Set secure permissions on Docker socket (if monitoring Docker)
sudo chmod 660 /var/run/docker.sock
sudo chown root:docker /var/run/docker.sock
Uptime Kuma maintains audit logs for:
Access Logs:
# View application logs
docker logs uptime-kuma
# Export logs
docker logs uptime-kuma > uptime-kuma-$(date +%Y%m%d).log
# Search for security events
docker logs uptime-kuma 2>&1 | grep -iE "(auth|login|permission|denied)"
Syslog Integration:
logging:
driver: syslog
options:
syslog-address: "udp://localhost:514"
tag: "uptime-kuma"
syslog-facility: "local0"
Configure rsyslog:
# Create /etc/rsyslog.d/10-uptime-kuma.conf
sudo tee /etc/rsyslog.d/10-uptime-kuma.conf << 'EOF'
:programname, isequal, "uptime-kuma" /var/log/uptime-kuma.log
& stop
EOF
sudo systemctl restart rsyslog
Monitor Failed Logins:
# Create monitoring script
cat > /usr/local/bin/monitor-uptime-kuma-auth.sh << 'EOF'
#!/bin/bash
LOG_FILE="/var/log/uptime-kuma.log"
THRESHOLD=5
FAILED_LOGINS=$(grep -i "failed login" $LOG_FILE | \
awk '{print $1, $2, $3}' | sort | uniq -c | \
awk -v threshold=$THRESHOLD '$1 > threshold {print $4}')
if [ -n "$FAILED_LOGINS" ]; then
echo "ALERT: Multiple failed login attempts detected"
echo "$FAILED_LOGINS" | mail -s "Uptime Kuma Security Alert" admin@example.com
fi
EOF
chmod +x /usr/local/bin/monitor-uptime-kuma-auth.sh
GPG Encryption:
# Generate GPG key (if not exists)
gpg --gen-key
# Export public key for automation
gpg --export -a "uptime-kuma-backup" > backup-key.pub
# Encrypt backup
tar czf - /opt/uptime-kuma/data | gpg --encrypt --recipient "uptime-kuma-backup" --output /backup/uptime-kuma-$(date +%Y%m%d).tar.gz.gpg
# Decrypt backup
gpg --decrypt /backup/uptime-kuma-$(date +%Y%m%d).tar.gz.gpg | tar xzf -
Backup Script with Encryption:
#!/bin/bash
set -e
BACKUP_DIR="/backup/uptime-kuma"
DATE=$(date +%Y%m%d_%H%M%S)
GPG_RECIPIENT="uptime-kuma-backup"
mkdir -p "${BACKUP_DIR}"
# Create and encrypt backup
tar czf - /opt/uptime-kuma/data | \
gpg --encrypt --recipient "${GPG_RECIPIENT}" \
--output "${BACKUP_DIR}/uptime-kuma-${DATE}.tar.gz.gpg"
# Verify encryption
gpg --decrypt "${BACKUP_DIR}/uptime-kuma-${DATE}.tar.gz.gpg" > /dev/null 2>&1
# Cleanup old backups
find "${BACKUP_DIR}" -name "*.tar.gz.gpg" -mtime +30 -delete
echo "Encrypted backup completed: uptime-kuma-${DATE}.tar.gz.gpg"
# Create checksum
sha256sum /backup/uptime-kuma-*.tar.gz.gpg > /backup/checksums.txt
# Verify checksum
sha256sum -c /backup/checksums.txt
# Rotate database password
docker exec -i mariadb mysql -u root -p << EOF
ALTER USER 'uptime_kuma'@'%' IDENTIFIED BY 'new-secure-password';
FLUSH PRIVILEGES;
EOF
# Update Docker secrets
echo -n "new-secure-password" > secrets/db_password.txt
docker compose restart uptime-kuma
| Control | Implementation |
|---|---|
| Access Control | 2FA, RBAC, session timeout |
| Audit Logging | All actions logged, log forwarding |
| Encryption | TLS 1.3, encrypted backups |
| Change Management | Version-controlled configs |
| Incident Response | Documented procedures |
| Requirement | Implementation |
|---|---|
| Data Minimization | Only necessary data collected |
| Access Control | Role-based permissions |
| Audit Trail | Complete action logging |
| Data Portability | SQLite database export |
| Right to Erasure | Manual data deletion process |
| Requirement | Implementation |
|---|---|
| Access Control | Unique user IDs, 2FA |
| Audit Controls | Comprehensive logging |
| Integrity | Checksums, backups |
| Transmission Security | TLS 1.3 encryption |
| Breach Notification | Monitoring and alerting |
Any questions?
Feel free to contact us. Find all contact information on our contact page.