# Flarum vhost — serves the Flarum public dir over php-fpm, and proxies the # realtime websocket path (/app) to the flarum/realtime server on :6001. # Generic server_name: put your own reverse proxy / TLS in front, or expose # port 80 directly. X-Forwarded-Proto is honoured so HTTPS is detected when a # TLS-terminating proxy sits in front. map $http_x_forwarded_proto $flarum_https { default ""; https "on"; } # WebSocket upgrade helper for the realtime proxy below. map $http_upgrade $connection_upgrade { default upgrade; "" close; } server { listen 80 default_server; server_name _; root /var/www/html/public; index index.php; client_max_body_size 64M; charset utf-8; # Clickjacking: only the site itself and the BlizzNux desktop launcher may # frame these pages. The launcher (Tauri 2, Linux) shows blizznux.com in an # iframe inside its own page, served from tauri://localhost; that is also # why blizznux.com runs COOKIE_SAMESITE=none. No website can have that # origin, so allowing it costs the other sites nothing. # No X-Frame-Options on purpose: it cannot name the launcher, and modern # browsers use frame-ancestors anyway. `always` covers error pages too. add_header Content-Security-Policy "frame-ancestors 'self' tauri://localhost" always; gzip on; gzip_vary on; gzip_comp_level 5; gzip_min_length 256; gzip_proxied any; gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml font/woff font/woff2; location / { try_files $uri $uri/ /index.php?$query_string; } # Both of these can be real files in public/, but an extension may also # register a Flarum route at the same path (fof/sitemap serves /robots.txt). # Without the fallback nginx answers its own 404 before PHP is ever reached, # so try the file first and hand anything else to Flarum. location = /favicon.ico { access_log off; log_not_found off; try_files $uri /index.php?$query_string; } location = /robots.txt { access_log off; log_not_found off; try_files $uri /index.php?$query_string; } # flarum/realtime websocket server (supervisor program on :6001). The JS # client connects to wss:///app — proxy that to the local ws server so # a single exposed port 80 (behind your TLS proxy) is all that's needed. location /app { proxy_pass http://127.0.0.1:6001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $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 600s; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; fastcgi_param HTTPS $flarum_https if_not_empty; fastcgi_read_timeout 900; } # Long-cache real assets, but fall back to Flarum rather than 404 on a URL # that merely looks static: routes ending in .svg/.js/.png are legitimate # (avatar and logo routes, extension-served assets). A missing file now # costs a PHP request instead of a cheap 404, which is the right trade when # the alternative is an unreachable route. location ~* \.(?:css|js|gif|jpe?g|png|webp|svg|woff2?|ttf|eot|ico|map)$ { access_log off; log_not_found off; expires 30d; add_header Cache-Control "public"; try_files $uri /index.php?$query_string; } location ~ /\.(?!well-known).* { deny all; } }