Nginx configuration¶
The frontend uses Nginx as a reverse proxy and static file server. The configuration lives in
frontend/nginx.conf.template.
Architecture¶
flowchart LR
Browser --> Nginx
Nginx -->|"/api/*"| Backend["Backend :443"]
Nginx -->|"static files"| Static["Static files"]
Nginx serves two purposes: static file server for the Svelte frontend build, and reverse proxy for API requests to the backend.
Configuration breakdown¶
Server block¶
| Directive | Purpose |
|---|---|
listen 5001 |
Internal container port (mapped via Docker Compose) |
server_name _ |
Catch-all server name |
root |
Static files from Svelte build |
Compression¶
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/x-font-ttf font/opentype image/svg+xml image/x-icon;
gzip_disable "msie6";
Gzip compression reduces bandwidth for text-based assets. Binary files (images, fonts) are excluded as they're already compressed.
API proxy¶
location /api/ {
proxy_pass ${BACKEND_URL};
proxy_ssl_verify off;
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;
# Forward cookies
proxy_pass_request_headers on;
proxy_set_header Cookie $http_cookie;
| Directive | Purpose |
|---|---|
proxy_pass |
Forward to backend service over HTTPS |
proxy_ssl_verify off |
Skip certificate verification (internal traffic) |
X-Real-IP |
Pass client IP to backend for rate limiting |
X-Forwarded-Proto |
Preserve original protocol for redirect URLs |
Cookie |
Forward authentication cookies |
SSE (Server-Sent Events)¶
SSE endpoints require special handling to prevent buffering:
# SSE specific settings for /api/v1/events/
location ~ ^/api/v1/events/ {
proxy_pass ${BACKEND_URL};
proxy_ssl_verify off;
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;
# Forward cookies
proxy_pass_request_headers on;
proxy_set_header Cookie $http_cookie;
# SSE configuration
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# Disable buffering for SSE
proxy_set_header X-Accel-Buffering no;
# CORS headers for SSE
add_header Cache-Control 'no-cache';
add_header X-Accel-Buffering 'no';
}
| Directive | Purpose |
|---|---|
Connection '' |
Disable connection header for HTTP/1.1 keep-alive |
proxy_http_version 1.1 |
Required for chunked transfer encoding |
proxy_buffering off |
Stream responses immediately |
proxy_read_timeout 86400s |
24-hour timeout for long-lived connections |
X-Accel-Buffering no |
Disable upstream buffering |
Without these settings, SSE events would be buffered and delivered in batches instead of real-time.
Static asset caching¶
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff";
}
# Cache build directory assets with long expiry
location /build/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Content-Type-Options "nosniff";
}
# HTML files should not be cached
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
Svelte build outputs hashed filenames (app.abc123.js), making them safe to cache indefinitely. HTML files must never
be cached to ensure users get the latest asset references.
Security headers¶
location / {
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; connect-src 'self';";
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";
try_files $uri $uri/ /index.html;
}
Content Security Policy¶
| Directive | Value | Purpose |
|---|---|---|
default-src |
'self' |
Fallback for unspecified directives |
script-src |
'self' 'unsafe-inline' |
Allow inline scripts (Svelte) |
style-src |
'self' 'unsafe-inline' |
Allow inline styles (Svelte) |
img-src |
'self' data: blob: |
Allow data: URLs for SVG icons |
font-src |
'self' data: |
Allow embedded fonts |
object-src |
'none' |
Block plugins (Flash, Java) |
frame-ancestors |
'none' |
Prevent clickjacking |
connect-src |
'self' |
XHR/fetch/WebSocket same-origin |
The data: source is required for the Monaco editor's inline SVG icons.
Other security headers¶
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options |
SAMEORIGIN |
Legacy clickjacking protection |
X-Content-Type-Options |
nosniff |
Prevent MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Limit referrer leakage |
Permissions-Policy |
Deny geolocation, mic, camera | Disable unused APIs |
SPA routing¶
The try_files $uri $uri/ /index.html directive enables client-side routing. When a URL like /editor is requested
directly, Nginx serves index.html and lets the Svelte router handle the path.
Deployment¶
The nginx configuration uses environment variable substitution via the official nginx Docker image's built-in envsubst
feature:
# Production stage
FROM nginx:alpine
# Copy built static files
COPY --from=builder /app/public /usr/share/nginx/html
# Copy nginx config template (envsubst runs at container startup)
# The nginx image automatically processes /etc/nginx/templates/*.template
# and outputs to /etc/nginx/conf.d/ with the .template suffix removed
COPY nginx.conf.template /etc/nginx/templates/default.conf.template
The nginx image automatically processes files in /etc/nginx/templates/*.template and outputs the result to
/etc/nginx/conf.d/ with the .template suffix removed.
Environment variables¶
| Variable | Purpose | Example |
|---|---|---|
BACKEND_URL |
Backend service URL for API proxy | https://backend:443 |
Set this via the docker-compose environment section.
Rebuilding¶
To apply nginx configuration changes:
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
| SSE connections dropping | Default 60s proxy_read_timeout |
Verify 86400s timeout is set |
| CSP blocking resources | Missing source in directive | Check browser console, add blocked source |
| 502 Bad Gateway | Backend unreachable | docker compose logs backend |
| Assets not updating | Browser cache | Clear cache or verify no-cache on HTML |