# SvxPortal web — nginx server block. # Install: sudo cp install/nginx-svxportal.conf /etc/nginx/sites-available/svxportal # sudo ln -s /etc/nginx/sites-available/svxportal /etc/nginx/sites-enabled/ # sudo nginx -t && sudo systemctl reload nginx # Replace: svxportal.example.com, the certificate paths, and the PHP version in the socket names. server { listen 80; listen [::]:80; server_name svxportal.example.com; # Let's Encrypt HTTP challenge (certbot --webroot -w /var/www/letsencrypt) location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/letsencrypt; allow all; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name svxportal.example.com; root /var/www/html/svxportal; index index.html index.php; ssl_certificate /etc/letsencrypt/live/svxportal.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/svxportal.example.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; access_log /var/log/nginx/svxportal-access.log; error_log /var/log/nginx/svxportal-error.log; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always; # The UI loads Bootstrap/icons from cdn.jsdelivr.net; everything else is same-origin. add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'" always; location = /robots.txt { default_type text/plain; alias /var/www/html/svxportal/robots.txt; } location = /sitemap.xml { default_type application/xml; alias /var/www/html/svxportal/sitemap.xml; } location = /privacy { default_type text/plain; alias /var/www/html/svxportal/privacy; } # Reflector admin API runs in its own PHP-FPM pool (see install/php-fpm-svxadmin.conf). # It needs exec() for the sudo helpers; the normal pool should keep exec disabled. location = /auth/admin_reflector_api.php { include /etc/nginx/fastcgi.conf; try_files $uri =404; fastcgi_hide_header X-Powered-By; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/run/php/php8.4-fpm-svxadmin.sock; fastcgi_index index.php; fastcgi_read_timeout 30; } # Never serve dotfiles (.git, .gitignore ...) or editor/backup files. location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off; } location ~* \.(bak|orig|save|old|swp|inc|sql|dist|tpl)(\.[0-9]+)?$ { deny all; access_log off; log_not_found off; } # Server-Sent Events endpoint: must not be buffered. location = /auth/events.php { include /etc/nginx/fastcgi.conf; try_files $uri =404; fastcgi_pass unix:/run/php/php8.4-fpm.sock; fastcgi_buffering off; fastcgi_read_timeout 3600; gzip off; } location ~ \.php$ { include /etc/nginx/fastcgi.conf; try_files $uri =404; fastcgi_hide_header X-Powered-By; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/run/php/php8.4-fpm.sock; fastcgi_index index.php; fastcgi_read_timeout 25; } location = / { try_files /index.html =404; } location / { try_files $uri $uri/ =404; } # Live data API (/svxportal/api/status, /stream, /lastheard, /monitor-nodes, # /local-trunks). The web UI expects a JSON/SSE backend here on 127.0.0.1:8098. # That backend is NOT part of this repository — see INSTALL.md, section 8. location ^~ /svxportal/api/ { proxy_pass http://127.0.0.1:8098/api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Connection ""; proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; gzip off; add_header X-Accel-Buffering no; } }