Loading the panel over the domain produced a white page while the same
panel served directly from the frontend container rendered fine. The built
SPA requests 86 files on a cold load — one entry module plus 85 preloaded
chunks — and the server-level
limit_req zone=general burst=20 nodelay; # general = 10r/s
applies to those static assets too. Measured against the live site, 60 of
the 86 came back 503 and nginx logged "limiting requests ... by zone
general" for each. The entry module was among the casualties, so React
never booted and the document stayed empty.
Give static assets their own zone and restate the limit in a location that
matches them. The limit has to be restated rather than omitted: nginx
inherits limit_req from the enclosing level only when a location declares
none, and there is no `limit_req off`.
Both templates get the block, since the non-SSL config serves the same
bundle. Verified on the live deployment: 86/86 assets now return 200 and
the login page renders with no console errors.
134 lines
4.5 KiB
Plaintext
134 lines
4.5 KiB
Plaintext
# NGINX Configuration Template for Server Panel
|
|
# Replace DOMAIN_NAME with your actual domain
|
|
|
|
# HTTP - redirect to HTTPS
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name DOMAIN_NAME;
|
|
|
|
# Let's Encrypt challenge
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/certbot;
|
|
}
|
|
|
|
# Redirect all HTTP to HTTPS
|
|
location / {
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
}
|
|
|
|
# HTTPS
|
|
server {
|
|
listen 443 ssl;
|
|
listen [::]:443 ssl;
|
|
http2 on;
|
|
server_name DOMAIN_NAME;
|
|
|
|
# SSL certificates
|
|
ssl_certificate /etc/letsencrypt/live/DOMAIN_NAME/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem;
|
|
|
|
# SSL configuration (Mozilla Modern)
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
|
ssl_prefer_server_ciphers off;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
# No OCSP stapling: Let's Encrypt no longer publishes an OCSP responder URL
|
|
# in its certificates, so ssl_stapling had nothing to staple and only
|
|
# produced a warning on every reload.
|
|
|
|
# Security headers
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
# Logging
|
|
access_log /var/log/nginx/pvemanager_access.log;
|
|
error_log /var/log/nginx/pvemanager_error.log;
|
|
|
|
# Rate limiting
|
|
limit_req zone=general burst=20 nodelay;
|
|
limit_conn addr 10;
|
|
|
|
# Health check endpoint
|
|
location /health {
|
|
proxy_pass http://pvemanager-app:8000;
|
|
access_log off;
|
|
}
|
|
|
|
# API proxy
|
|
location /api/ {
|
|
limit_req zone=api burst=50 nodelay;
|
|
proxy_pass http://pvemanager-app:8000;
|
|
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;
|
|
}
|
|
|
|
# Proxmox API proxy
|
|
location /proxmox/ {
|
|
proxy_pass http://pvemanager-app:8000;
|
|
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;
|
|
# Загрузка ISO/образов: файлы многогигабайтные, дефолтный
|
|
# client_max_body_size 100M и короткие таймауты им не подходят.
|
|
client_max_body_size 20g;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
client_body_timeout 3600s;
|
|
}
|
|
|
|
# WebSocket support — tasks и terminal
|
|
location ~ ^/(ws|proxmox/ws)/ {
|
|
proxy_pass http://pvemanager-app:8000;
|
|
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 86400;
|
|
proxy_send_timeout 86400;
|
|
}
|
|
|
|
# Frontend build output. A cold load of the SPA requests ~86 files at once,
|
|
# which the server-level limit_req rejects with 503 — leaving a blank page.
|
|
# The limit has to be restated here rather than omitted: nginx inherits
|
|
# limit_req from the server level only when a location declares none.
|
|
location ~ ^/(assets/|favicon\.|icons\.svg|logo\.png) {
|
|
limit_req zone=static burst=200 nodelay;
|
|
proxy_pass http://pvemanager-frontend:80;
|
|
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;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# Backend static files (legacy, logo, etc.)
|
|
location /static/ {
|
|
proxy_pass http://pvemanager-app:8000;
|
|
proxy_cache_valid 200 1d;
|
|
expires 1d;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# SPA frontend — all other routes
|
|
location / {
|
|
proxy_pass http://pvemanager-frontend:80;
|
|
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;
|
|
}
|
|
}
|