Healthchecks is a monitoring service for cron jobs and scheduled tasks. It receives periodic “pings” from your cron jobs and alerts you when pings stop arriving. As a system that manages monitoring state and sends notifications, Healthchecks requires proper security configuration to protect monitoring data and notification credentials. This guide covers security measures for production Healthchecks deployments.
Healthchecks architecture includes these security-sensitive components:
Key security concerns include API key protection, ping endpoint security, notification credential management, database security, and webhook authentication.
Configure firewall rules for Healthchecks:
# Healthchecks web interface
ufw allow from 10.0.0.0/8 to any port 8000 proto tcp
ufw allow from 10.0.0.0/8 to any port 443 proto tcp
# Ping endpoints (if exposed for external cron jobs)
ufw allow from any to any port 443 proto tcp # HTTPS only
# Database
ufw allow from 127.0.0.1 to any port 5432 proto tcp # PostgreSQL
ufw allow from 127.0.0.1 to any port 3306 proto tcp # MySQL
# Block direct database access
ufw deny from any to any port 5432 proto tcp
ufw deny from any to any port 3306 proto tcp
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: healthchecks-network-policy
spec:
podSelector:
matchLabels:
app: healthchecks
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 443
Configure Healthchecks binding:
# /opt/healthchecks/hc/settings.py
ALLOWED_HOSTS = ['healthchecks.company.com', 'hc.company.com']
CSRF_TRUSTED_ORIGINS = ['https://healthchecks.company.com']
# Bind to localhost for reverse proxy
# Run with: python manage.py runserver 127.0.0.1:8000
Configure reverse proxy:
# /etc/nginx/sites-available/healthchecks
server {
listen 80;
server_name healthchecks.company.com hc.company.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name healthchecks.company.com hc.company.com;
ssl_certificate /etc/nginx/certs/healthchecks.crt;
ssl_certificate_key /etc/nginx/certs/healthchecks.key;
location / {
proxy_pass http://127.0.0.1:8000;
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
# Ping endpoints - allow from anywhere (with HTTPS)
location /ping/ {
proxy_pass http://127.0.0.1:8000;
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;
}
}
Configure Django authentication:
# /opt/healthchecks/hc/settings.py
# Session security
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_AGE = 1209600 # 2 weeks
# Password policy
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 12}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# Login settings
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/checks/'
Configure team permissions:
Healthchecks uses team-based access:
- Team Member: View and manage checks within team
- Team Admin: Manage team members and settings
- Superuser: Full administrative access
# Configure in web UI:
# Settings → Team → Members
Secure API access:
# /opt/healthchecks/hc/settings.py
# API settings
API_PING_KEY_REQUIRED = False # Set True to require API key for pings
# API rate limiting
API_PING_LIMIT = '100/hour'
Generate API keys:
# Via web UI: Settings → API Access
# Or via management command
python manage.py create_api_key --user admin
Configure SAML authentication:
# /opt/healthchecks/hc/settings.py
# SAML settings (requires django-saml2-auth)
SAML2_AUTH = {
'METADATA_AUTO_CONF_URL': 'https://sso.company.com/saml/metadata',
'DEFAULT_NEXT_URL': '/checks/',
'CREATE_USER': True,
'CREATE_GROUPS': True,
'NEW_USER_PROFILE': {
'is_active': True
}
}
Enable two-factor authentication:
# /opt/healthchecks/hc/settings.py
# Enable TOTP (requires django-two-factor-auth)
TWO_FACTOR_CALL_GATEWAY = None
TWO_FACTOR_SMS_GATEWAY = None
TWO_FACTOR_TOTP_AVAILABLE = True
Configure HTTPS via reverse proxy:
# /etc/nginx/sites-available/healthchecks
server {
listen 443 ssl http2;
server_name healthchecks.company.com;
ssl_certificate /etc/nginx/certs/healthchecks.crt;
ssl_certificate_key /etc/nginx/certs/healthchecks.key;
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.crt;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src * data:; font-src 'self'; connect-src 'self'" always;
location / {
proxy_pass http://127.0.0.1:8000;
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
}
Secure ping endpoints:
# Ping endpoints must use HTTPS
server {
listen 443 ssl http2;
server_name hc-pings.company.com;
ssl_certificate /etc/nginx/certs/healthchecks.crt;
ssl_certificate_key /etc/nginx/certs/healthchecks.key;
# Force HTTPS for pings
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:8000;
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;
}
}
Configure TLS for database:
# /opt/healthchecks/hc/settings.py
# PostgreSQL with SSL
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'healthchecks',
'USER': 'healthchecks',
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': 'localhost',
'PORT': '5432',
'OPTIONS': {
'sslmode': 'require',
'sslrootcert': '/etc/postgresql/ca.crt',
'sslcert': '/etc/postgresql/client.crt',
'sslkey': '/etc/postgresql/client.key',
},
}
}
Secure Healthchecks API:
| Endpoint | Risk Level | Access Control |
|---|---|---|
GET /api/v1/checks/ |
Medium | API key required |
POST /api/v1/checks/ |
High | API key required |
GET /api/v1/checks/{uuid} |
Medium | API key required |
POST /api/v1/checks/{uuid}/pause |
High | API key required |
DELETE /api/v1/checks/{uuid} |
Critical | API key required |
GET /ping/{code} |
Low | Public (unguessable code) |
POST /ping/{code} |
Low | Public (unguessable code) |
Secure ping endpoints:
# /opt/healthchecks/hc/settings.py
# Require unique code for pings (default behavior)
# Codes are UUIDs, making them unguessable
# Optional: Require API key for pings
API_PING_KEY_REQUIRED = True
# Rate limiting for pings
PING_LIMIT = '1000/hour'
Secure webhook integrations:
# /opt/healthchecks/hc/settings.py
# Webhook signature verification
WEBHOOK_SIGNATURE_HEADER = 'X-Webhook-Signature'
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')
Configure outgoing webhooks with authentication:
In Healthchecks UI:
- Check Settings → Integrations → Webhook
- Set custom headers: Authorization: Bearer ${TOKEN}
Secure notification credentials:
# /opt/healthchecks/hc/settings.py
# Use environment variables for credentials
EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD')
SLACK_CLIENT_SECRET = os.getenv('SLACK_CLIENT_SECRET')
# Discord webhook URL
DISCORD_WEBHOOK_URL = os.getenv('DISCORD_WEBHOOK_URL')
Secure Healthchecks database:
-- Create dedicated database user
CREATE USER healthchecks WITH PASSWORD '${DB_PASSWORD}';
CREATE DATABASE healthchecks OWNER healthchecks;
GRANT ALL PRIVILEGES ON DATABASE healthchecks TO healthchecks;
-- Enable SSL requirement
ALTER USER healthchecks WITH PASSWORD '${DB_PASSWORD}';
Enable database encryption:
-- PostgreSQL with pgcrypto
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Or use TDE (Enterprise Edition)
Secure sensitive data:
# /opt/healthchecks/hc/settings.py
# Healthchecks encrypts sensitive fields by default
# Ensure SECRET_KEY is strong and unique
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
Secure sensitive configuration:
# Use environment variables
export DB_PASSWORD="secure_password"
export DJANGO_SECRET_KEY="secure_secret_key"
export SMTP_PASSWORD="secure_smtp_password"
export SLACK_CLIENT_SECRET="secure_slack_secret"
# Or use Django secrets
python manage.py create_secret_key
For Kubernetes:
apiVersion: v1
kind: Secret
metadata:
name: healthchecks-secrets
type: Opaque
data:
db-password: <base64-encoded>
secret-key: <base64-encoded>
smtp-password: <base64-encoded>
Secure Healthchecks backups:
#!/bin/bash
# Secure backup script
BACKUP_DIR="/secure/backups/healthchecks"
DATE=$(date +%Y%m%d)
# Database backup
pg_dump healthchecks > ${BACKUP_DIR}/healthchecks-db-${DATE}.sql
# Encrypt backup
gpg -e --recipient security@company.com ${BACKUP_DIR}/healthchecks-db-${DATE}.sql
# Set restrictive permissions
chmod 600 ${BACKUP_DIR}/healthchecks-db-${DATE}.sql.gpg
# Remove unencrypted backup
rm ${BACKUP_DIR}/healthchecks-db-${DATE}.sql
Enable logging:
# /opt/healthchecks/hc/settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/healthchecks/healthchecks.log',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
Configure reverse proxy access logging:
# /etc/nginx/sites-available/healthchecks
access_log /var/log/nginx/healthchecks_access.log combined;
error_log /var/log/nginx/healthchecks_error.log warn;
Monitor Healthchecks for security events:
#!/bin/bash
# /usr/local/bin/check-healthchecks-security.sh
# Check for failed login attempts
FAILED_LOGINS=$(grep -c "Failed login" /var/log/healthchecks/healthchecks.log 2>/dev/null || echo 0)
if [ "$FAILED_LOGINS" -gt 10 ]; then
echo "CRITICAL: Multiple failed login attempts"
exit 2
fi
# Check for API abuse
API_ERRORS=$(grep -c "401 Unauthorized" /var/log/nginx/healthchecks_access.log 2>/dev/null || echo 0)
if [ "$API_ERRORS" -gt 50 ]; then
echo "WARNING: Possible API abuse"
exit 1
fi
Forward logs to SIEM:
# /etc/rsyslog.d/healthchecks.conf
:programname, isequal, "healthchecks" /var/log/healthchecks/syslog.log
:programname, isequal, "healthchecks" @siem.company.com:514