Skip to main content

Nginx

Nginx serves as the reverse proxy in production for both the main Pulse application and the Control Center. It handles SSL termination, rate limiting, static file serving, WebSocket proxying, and security headers.


Architecture


Pulse Production Configuration

# Simplified production Nginx config for Pulse
server {
listen 443 ssl http2;
server_name app.unipulse.tech;

ssl_certificate /etc/letsencrypt/live/app.unipulse.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.unipulse.tech/privkey.pem;

# Security headers
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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Block sensitive files
location ~ /\.env { deny all; return 404; }

# Gzip compression
gzip on;
gzip_types text/plain application/json application/javascript text/css image/svg+xml;
gzip_min_length 256;

# API proxy
location /api/ {
proxy_pass http://api:3000;
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;
}

# Static files (Vite build output)
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;

# Long cache for hashed assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}

# HTTP to HTTPS redirect
server {
listen 80;
server_name app.unipulse.tech;
return 301 https://$server_name$request_uri;
}

Control Center Nginx Configuration

The Control Center uses a separate Nginx config with additional security measures:

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;

server {
listen 443 ssl http2;
server_name ops.unipulse.tech;

ssl_certificate /etc/letsencrypt/live/ops.unipulse.tech/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ops.unipulse.tech/privkey.pem;

# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self'" always;

# Block .env files
location ~ /\.env { deny all; return 404; }

# Auth routes with strict rate limiting (5 requests/second)
location /api/auth/ {
limit_req zone=auth burst=10 nodelay;
proxy_pass http://localhost:3001;
}

# API routes with moderate rate limiting (30 requests/second)
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://localhost:3001;
}

# WebSocket proxy for Socket.IO
location /socket.io/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # Keep alive for 24h
}

# Static frontend files
location / {
root /var/www/control-center;
try_files $uri $uri/ /index.html;
}
}

Key Configuration Details

SSL/TLS

SettingValue
ProviderLet's Encrypt (via Certbot)
ProtocolTLS 1.2+
HTTP/2Enabled
HSTSEnabled (1 year, includeSubDomains)
Auto-renewalCertbot cron job

Rate Limiting

ZoneRateBurstTarget
auth5 requests/second10/api/auth/* routes (Control Center)
api30 requests/second50/api/* routes (Control Center)

Static File Caching

File TypeCache DurationStrategy
JS, CSS (hashed filenames)1 yearimmutable -- Vite hash in filename handles versioning
Images, fonts1 yearpublic, immutable
HTMLNo cacheAlways fetch latest

WebSocket Support

Socket.IO requires HTTP upgrade headers for WebSocket connections:

HeaderValuePurpose
Upgrade$http_upgradeProtocol upgrade to WebSocket
Connection"upgrade"Keep connection for WS
proxy_read_timeout86400sKeep WS alive for 24 hours

Security Headers

HeaderValuePurpose
X-Frame-OptionsSAMEORIGIN (Pulse) / DENY (CC)Prevent clickjacking
X-Content-Type-OptionsnosniffPrevent MIME sniffing
X-XSS-Protection1; mode=blockXSS filter
Strict-Transport-Securitymax-age=31536000Force HTTPS
Referrer-Policystrict-origin-when-cross-originControl referrer info

Sensitive File Blocking

location ~ /\.env { deny all; return 404; }

This prevents accidental exposure of .env files containing secrets.


Configuration Locations

ConfigLocation
Pulse NginxManaged by Docker Compose (web container)
Control Center NginxUniPulse-Control-Center/nginx/control-center.conf
SSL Certificates/etc/letsencrypt/live/{domain}/

Setup Commands

# Copy Control Center Nginx config
cp nginx/control-center.conf /etc/nginx/sites-available/
ln -s /etc/nginx/sites-available/control-center.conf /etc/nginx/sites-enabled/

# Test configuration
nginx -t

# Reload Nginx
systemctl reload nginx

# Generate SSL certificate
certbot certonly --webroot -w /var/www/certbot -d app.unipulse.tech
certbot certonly --webroot -w /var/www/certbot -d ops.unipulse.tech

Cross-Reference