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
| Setting | Value |
|---|---|
| Provider | Let's Encrypt (via Certbot) |
| Protocol | TLS 1.2+ |
| HTTP/2 | Enabled |
| HSTS | Enabled (1 year, includeSubDomains) |
| Auto-renewal | Certbot cron job |
Rate Limiting
| Zone | Rate | Burst | Target |
|---|---|---|---|
auth | 5 requests/second | 10 | /api/auth/* routes (Control Center) |
api | 30 requests/second | 50 | /api/* routes (Control Center) |
Static File Caching
| File Type | Cache Duration | Strategy |
|---|---|---|
| JS, CSS (hashed filenames) | 1 year | immutable -- Vite hash in filename handles versioning |
| Images, fonts | 1 year | public, immutable |
| HTML | No cache | Always fetch latest |
WebSocket Support
Socket.IO requires HTTP upgrade headers for WebSocket connections:
| Header | Value | Purpose |
|---|---|---|
Upgrade | $http_upgrade | Protocol upgrade to WebSocket |
Connection | "upgrade" | Keep connection for WS |
proxy_read_timeout | 86400s | Keep WS alive for 24 hours |
Security Headers
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options | SAMEORIGIN (Pulse) / DENY (CC) | Prevent clickjacking |
X-Content-Type-Options | nosniff | Prevent MIME sniffing |
X-XSS-Protection | 1; mode=block | XSS filter |
Strict-Transport-Security | max-age=31536000 | Force HTTPS |
Referrer-Policy | strict-origin-when-cross-origin | Control referrer info |
Sensitive File Blocking
location ~ /\.env { deny all; return 404; }
This prevents accidental exposure of .env files containing secrets.
Configuration Locations
| Config | Location |
|---|---|
| Pulse Nginx | Managed by Docker Compose (web container) |
| Control Center Nginx | UniPulse-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
- Docker -- container networking
- Deployment -- full production deployment
- Control Center Setup -- Control Center Nginx setup
- Environment Variables -- FRONTEND_URL config