Files
explorer-monorepo/deployment/nginx/explorer.conf

208 lines
6.8 KiB
Plaintext

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=50r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Upstream servers
upstream explorer_api {
server 127.0.0.1:8080;
keepalive 32;
}
upstream explorer_frontend {
server 127.0.0.1:3000;
keepalive 32;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name explorer.d-bis.org www.explorer.d-bis.org;
# Allow Let's Encrypt validation
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Redirect all other traffic to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
# Main HTTPS server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name explorer.d-bis.org www.explorer.d-bis.org;
# SSL Configuration (Cloudflare handles SSL, but we can add local certs too)
# ssl_certificate /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/explorer.d-bis.org/privkey.pem;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 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;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Content Security Policy (adjust as needed)
# CSP: unsafe-eval required by ethers.js v5 UMD from CDN
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data: https:; font-src 'self' data: https://cdnjs.cloudflare.com; connect-src 'self' https://api.cloudflare.com https://explorer.d-bis.org wss://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org http://192.168.11.221:8545 ws://192.168.11.221:8546;" always;
# Logging
access_log /var/log/nginx/explorer-access.log combined buffer=32k flush=5m;
error_log /var/log/nginx/explorer-error.log warn;
# Client settings
client_max_body_size 10M;
client_body_timeout 60s;
client_header_timeout 60s;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/rss+xml
font/truetype
font/opentype
application/vnd.ms-fontobject
image/svg+xml;
# Brotli compression (if available)
# brotli on;
# brotli_comp_level 6;
# brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Frontend
location / {
limit_req zone=general_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://explorer_frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_send_timeout 300s;
# Buffering
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# API endpoints
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 5;
proxy_pass http://explorer_api;
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 X-Forwarded-Host $host;
proxy_set_header Connection "";
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_send_timeout 300s;
# Disable buffering for API responses
proxy_buffering off;
# CORS headers (Cloudflare will also add these)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, X-API-Key, Authorization" always;
# Handle preflight
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, X-API-Key, Authorization";
add_header Access-Control-Max-Age 1728000;
add_header Content-Type "text/plain; charset=utf-8";
add_header Content-Length 0;
return 204;
}
}
# WebSocket support
location /ws {
proxy_pass http://explorer_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 75s;
}
# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp|avif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff";
access_log off;
log_not_found off;
}
# Health check endpoint (internal only)
location /health {
access_log off;
proxy_pass http://explorer_api/health;
proxy_set_header Host $host;
}
# Block access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ \.(env|git|gitignore|md|sh)$ {
deny all;
access_log off;
log_not_found off;
}
}