Files

224 lines
10 KiB
Nginx Configuration File

# Preserve X-Forwarded-Proto from upstream proxy, fall back to $scheme
map $http_x_forwarded_proto $real_scheme {
default $http_x_forwarded_proto;
'' $scheme;
}
# Custom log format: use $uri (path only) instead of $request to prevent
# query string tokens (e.g., SSE ?token=...) from leaking into access logs
# (must be at http level, not inside server block)
log_format stripped '$remote_addr - $remote_user [$time_local] "$request_method $uri" $status $body_bytes_sent "$http_referer"';
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
server_tokens off;
client_max_body_size 25m;
access_log /var/log/nginx/access.log stripped;
# Allow headers with underscores (e.g., HTTP_ID from TRMNL devices)
underscores_in_headers on;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript font/woff2;
# Security headers (repeated in location blocks that use add_header,
# since nginx does not inherit server-level add_header into locations with their own)
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: https://geocoding-api.open-meteo.com https://api.open-meteo.com https://nominatim.openstreetmap.org; frame-ancestors 'none';" always;
# Serve static files
location / {
try_files $uri $uri/ /index.html;
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: https://geocoding-api.open-meteo.com https://api.open-meteo.com https://nominatim.openstreetmap.org; frame-ancestors 'none';" always;
}
# Cache control for HTML files
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: https://geocoding-api.open-meteo.com https://api.open-meteo.com https://nominatim.openstreetmap.org; frame-ancestors 'none';" always;
}
# Serve fonts with proper MIME types and CORS headers
location /fonts/ {
types {
font/woff2 woff2;
font/woff woff;
font/ttf ttf;
}
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Content-Type-Options "nosniff" always;
expires 1y;
}
# Plugin and device-image render endpoints — strip security headers for ESP32 devices
location ~ ^/api/(plugins/instances/\d+/render|device-images/) {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Host $http_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 $real_scheme;
proxy_hide_header Content-Security-Policy;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-DNS-Prefetch-Control;
proxy_hide_header X-Frame-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Origin-Agent-Cluster;
proxy_hide_header X-Download-Options;
proxy_hide_header X-Permitted-Cross-Domain-Policies;
proxy_hide_header Referrer-Policy;
proxy_hide_header Permissions-Policy;
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "no-store";
}
# Device API endpoints — strip security headers for ESP32 devices
# (ESP32 has ~8-16KB HTTP buffer; Helmet headers can overflow it)
location ~ ^/api/(setup|display|log)(/?)$ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Host $http_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 $real_scheme;
proxy_hide_header Content-Security-Policy;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-DNS-Prefetch-Control;
proxy_hide_header X-Frame-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Origin-Agent-Cluster;
proxy_hide_header X-Download-Options;
proxy_hide_header X-Permitted-Cross-Domain-Policies;
proxy_hide_header Referrer-Policy;
proxy_hide_header Permissions-Policy;
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "no-store";
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://127.0.0.1:3002/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $http_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 $real_scheme;
proxy_cache_bypass $http_upgrade;
}
# Serve frontend build assets (JS, CSS) as static files,
# proxy backend assets (default-screen.png, setup.png) to backend
location /assets/ {
try_files $uri @backend_assets;
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
location @backend_assets {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Host $http_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 $real_scheme;
# Strip Helmet security headers from image responses — ESP32 devices
# have limited HTTP buffer and excessive headers cause download failures
proxy_hide_header Content-Security-Policy;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-DNS-Prefetch-Control;
proxy_hide_header X-Frame-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Origin-Agent-Cluster;
proxy_hide_header X-Download-Options;
proxy_hide_header X-Permitted-Cross-Domain-Policies;
proxy_hide_header Referrer-Policy;
proxy_hide_header Permissions-Policy;
# Override server-level security headers (add_header in a location
# block prevents inheriting server-level add_header directives)
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "public, max-age=0";
}
# Proxy uploads to backend (images, captures, drawings)
location /uploads/ {
proxy_pass http://127.0.0.1:3002/uploads/;
proxy_http_version 1.1;
proxy_set_header Host $http_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 $real_scheme;
expires 7d;
# Strip Helmet security headers — ESP32 devices have limited HTTP buffer
proxy_hide_header Content-Security-Policy;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-DNS-Prefetch-Control;
proxy_hide_header X-Frame-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header Cross-Origin-Opener-Policy;
proxy_hide_header Cross-Origin-Resource-Policy;
proxy_hide_header Origin-Agent-Cluster;
proxy_hide_header X-Download-Options;
proxy_hide_header X-Permitted-Cross-Domain-Policies;
proxy_hide_header Referrer-Policy;
proxy_hide_header Permissions-Policy;
# Minimal headers only (overrides server-level security headers)
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "public, max-age=604800";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}