Nginx Configuration
Basic Setup
Production Configuration
# /etc/nginx/sites-available/heimdall
upstream heimdall {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name heimdall.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name heimdall.example.com;
# TLS Configuration
ssl_certificate /etc/ssl/certs/heimdall.pem;
ssl_certificate_key /etc/ssl/private/heimdall.key;
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 10m;
ssl_stapling on;
ssl_stapling_verify on;
# 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 Referrer-Policy "strict-origin-when-cross-origin" always;
# Logging
access_log /var/log/nginx/heimdall_access.log;
error_log /var/log/nginx/heimdall_error.log;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=heimdall_limit:10m rate=10r/s;
limit_req zone=heimdall_limit burst=20 nodelay;
# Max Upload Size (for repository zip uploads)
client_max_body_size 500M;
# General Proxy Settings
location / {
proxy_pass http://heimdall;
proxy_http_version 1.1;
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 Connection "";
proxy_buffering off;
proxy_request_buffering off;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# SSE (Server-Sent Events) - Critical for scan progress
location /api/scans/ {
proxy_pass http://heimdall;
proxy_http_version 1.1;
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;
# SSE requires these settings
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
# Disable compression for SSE
gzip off;
# Chunked transfer encoding
chunked_transfer_encoding on;
}
# Health Check
location /health {
proxy_pass http://heimdall;
access_log off;
}
}
Critical: SSE (Server-Sent Events) endpoints require
proxy_buffering off and proxy_cache off to work correctly. Without these settings, scan progress updates will not stream in real-time.TLS/SSL Configuration
Obtaining Certificates
- Let's Encrypt (Recommended)
- Self-Signed (Development)
- Custom CA
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d heimdall.example.com
# Auto-renewal (runs twice daily)
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# Test renewal
sudo certbot renew --dry-run
# Generate self-signed certificate
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/heimdall.key \
-out /etc/ssl/certs/heimdall.pem \
-subj "/CN=heimdall.local"
# Set permissions
sudo chmod 600 /etc/ssl/private/heimdall.key
sudo chmod 644 /etc/ssl/certs/heimdall.pem
# Copy your certificates
sudo cp your-cert.pem /etc/ssl/certs/heimdall.pem
sudo cp your-key.pem /etc/ssl/private/heimdall.key
# Set permissions
sudo chmod 600 /etc/ssl/private/heimdall.key
sudo chmod 644 /etc/ssl/certs/heimdall.pem
# Verify certificate
openssl x509 -in /etc/ssl/certs/heimdall.pem -text -noout
SSL Best Practices
Use strong protocols
ssl_protocols TLSv1.2 TLSv1.3;
Disable TLSv1.0 and TLSv1.1 — they are deprecated and insecure.
Configure secure ciphers
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 Labs test
open https://www.ssllabs.com/ssltest/analyze.html?d=heimdall.example.com
# Local test
openssl s_client -connect heimdall.example.com:443 -tls1_3
SSE (Server-Sent Events) Configuration
Heimdall uses SSE for real-time scan progress updates. Proper proxy configuration is critical.Why SSE Requires Special Configuration
SSE streams data incrementally over a long-lived HTTP connection. Default proxy buffering breaks this behavior.
Required Nginx Settings
location /api/scans/ {
# Disable buffering (critical)
proxy_buffering off;
proxy_cache off;
# Long timeout for streaming
proxy_read_timeout 3600s;
# Disable compression
gzip off;
# Enable chunked transfer
chunked_transfer_encoding on;
proxy_pass http://heimdall;
}
Testing SSE
# Test SSE endpoint
curl -N -H "Accept: text/event-stream" \
https://heimdall.example.com/api/scans/123/progress/stream
# Should output streaming events:
# event: scan_update
# data: {"stage":"ingest","status":"running"}
#
# event: scan_update
# data: {"stage":"tyr","status":"completed"}
If you see buffered output or timeouts, check:
proxy_buffering offis setproxy_cache offis set- No intermediate proxies or CDNs are buffering
Security Hardening
Rate Limiting
Protect against abuse:# Define rate limit zones
http {
limit_req_zone $binary_remote_addr zone=heimdall_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=heimdall_login:10m rate=5r/m;
server {
# General rate limit
limit_req zone=heimdall_limit burst=20 nodelay;
# Stricter limit for auth endpoints
location ~ ^/api/auth/(login|register) {
limit_req zone=heimdall_login burst=3 nodelay;
proxy_pass http://heimdall;
}
}
}
IP Whitelisting
Restrict access to specific IPs:# Allow specific IPs
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
Block Malicious Bots
# Block common bot patterns
if ($http_user_agent ~* (bot|crawler|spider|scraper)) {
return 403;
}
DDoS Protection
# Connection limiting
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# Request body size
client_body_buffer_size 128k;
client_max_body_size 500M; # For zip uploads
# Timeouts
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
Load Balancing
For high availability, run multiple Heimdall instances:upstream heimdall {
least_conn; # Use least connections algorithm
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
location / {
proxy_pass http://heimdall;
# Health checks
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
}
When running multiple instances, ensure they share the same database and filesystem for repository storage.
Health Checks
Monitor backend health:location /health {
proxy_pass http://heimdall;
access_log off;
# Return 503 if backend is down
proxy_intercept_errors on;
error_page 502 503 504 = @healthcheck_failed;
}
location @healthcheck_failed {
return 503 "Heimdall is currently unavailable";
}
# Prometheus blackbox exporter
- job_name: 'heimdall'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://heimdall.example.com/health
Logging and Monitoring
Access Logs
# Custom log format
log_format heimdall_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/heimdall_access.log heimdall_log;
Error Logs
error_log /var/log/nginx/heimdall_error.log warn;
Log Rotation
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
Troubleshooting
SSE Not Working
502 Bad Gateway
# Check Heimdall is running
sudo systemctl status heimdall
curl http://localhost:8080/health
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
# Test upstream
curl -v http://127.0.0.1:8080/health
SSL Certificate Errors
# Verify certificate
openssl x509 -in /etc/ssl/certs/heimdall.pem -text -noout
# Check certificate chain
openssl s_client -connect heimdall.example.com:443 -showcerts
# Test SSL configuration
nginx -t
High Load
# Check Nginx status
sudo systemctl status nginx
# Check worker processes
ps aux | grep nginx
# Monitor connections
ss -s
netstat -an | grep :443 | wc -l
Alternative: Caddy
Caddy provides automatic HTTPS and simpler configuration:# Caddyfile
heimdall.example.com {
reverse_proxy localhost:8080 {
# SSE support (automatic)
flush_interval -1
}
# Security headers (automatic)
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
# Rate limiting
rate_limit {
zone dynamic {
key {remote_host}
events 10
window 1s
}
}
}
sudo caddy run --config /etc/caddy/Caddyfile
Next Steps
Production Deployment
Complete production deployment guide
Docker Deployment
Docker and Docker Compose setup