From b3fbed49a7af4d556d691344f0f763afff9b5436 Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Wed, 2 Sep 2026 16:22:08 +0100 Subject: [PATCH 1/4] Modernize NGINX installation config The sample NGINX config was stuck on FOSSBilling's old BoxBilling-era layout: - location ~ \.php$ executed any .php file through PHP-FPM instead of only the app's real entry points (index.php, ipn.php, install/index.php, install/install.php), contradicting the app's own .htaccess/DDEV config. - Missing blocks present elsewhere in the app: /themes/*/config/ and several sensitive extensions (.htaccess, .htpasswd, .conf, .lock, .log, .old, .yaml). - A dead block referencing /css, /img, /js, /flv, /swf, /download - none of which exist in the current codebase (flv/swf are Flash formats). - The @rewrite block manually rebuilt ?_url=... for routing, which is legacy: FOSSBilling's RequestFactory::normalizeRoutePath() already falls back to Symfony's getPathInfo() when _url is absent, and the /page/ -> /custompages/ remap already happens in PHP. Replaced with the standard try_files front-controller pattern. - listen 443 ssl http2 is deprecated nginx syntax since 1.25.1; split into listen ssl + http2 on. Added IPv6 listeners and bumped the PHP-FPM socket example to reflect the PHP 8.3+ requirement. Verified against real nginx + php-fpm + the actual Symfony Request class in an isolated test harness: routing/query-string preservation, all block rules, and PATH_INFO-bypass resistance (e.g. /console.php/x.jpg does not execute console.php). --- .../docs/getting-started/installation.mdoc | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/content/docs/getting-started/installation.mdoc b/src/content/docs/getting-started/installation.mdoc index 4c4b6ff..fb9e1f9 100644 --- a/src/content/docs/getting-started/installation.mdoc +++ b/src/content/docs/getting-started/installation.mdoc @@ -31,33 +31,36 @@ Apache usually requires no extra application-specific configuration. The templat **OpenLiteSpeed users:** After installation, remember to reload the web server so it picks up the new `.htaccess` file. {% /tabitem %} {% tabitem label="NGINX" %} - Use the configuration below as a starting point for running FOSSBilling on NGINX. Be sure to replace placeholder values (like `%%DOMAIN%%` and `%%SOURCE_PATH%%`) and adjust SSL paths to match your environment. + Use the configuration below as a starting point for running FOSSBilling on NGINX. Be sure to replace placeholder values (like `%%DOMAIN%%` and `%%SOURCE_PATH%%`) and adjust SSL paths to match your environment. `%%SOURCE_PATH%%` is the directory you extracted the release archive into — the one that directly contains `index.php`, not a `src` subfolder. ```nginx server { listen 80; + listen [::]:80; server_name %%DOMAIN%%; return 301 https://$host$request_uri; } server { - listen 443 ssl http2; + listen 443 ssl; + listen [::]:443 ssl; + http2 on; ssl_certificate /path/to/ssl/certificate.crt; ssl_certificate_key /path/to/ssl/certificate.key; ssl_stapling on; ssl_stapling_verify on; - set $root_path %%SOURCE_PATH%%; server_name %%DOMAIN%%; - + root %%SOURCE_PATH%%; index index.php; - root $root_path; - try_files $uri $uri/ @rewrite; sendfile off; include /etc/nginx/mime.types; - # Block access to sensitive files - location ~* .(ini|sh|inc|bak|twig|sql)$ { + # Serve existing files directly; otherwise hand off to the front controller. + try_files $uri $uri/ /index.php$is_args$args; + + # Block access to sensitive files and file types + location ~* \.(ini|sh|inc|bak|twig|sql|conf|lock|log|old|ya?ml|htaccess|htpasswd)$ { return 403; } @@ -65,13 +68,13 @@ server { location ^~ /vendor/ { return 403; } - - # Block direct access to config.php - location = /config.php { + + # Block access to per-theme configuration directories + location ~* ^/themes/[^/]+/config/ { return 403; } - # Block access to hidden files except .well-known + # Block access to hidden files except .well-known (needed for ACME challenges) location ~ /\.(?!well-known\/) { return 403; } @@ -81,31 +84,22 @@ server { return 403; } - location @rewrite { - rewrite ^/page/(.*)$ /index.php?_url=/custompages/$1; - rewrite ^/(.*)$ /index.php?_url=/$1; + # Only these entry points are allowed to run through PHP-FPM. + location ~ ^/(index\.php|ipn\.php|install/index\.php|install/install\.php)$ { + # fastcgi_pass needs to match your PHP-FPM setup. FOSSBilling requires PHP 8.3+, + # so the socket is often something like /run/php/php8.3-fpm.sock. + # A TCP address such as 127.0.0.1:9000 also works. + # Please check your server setup. + fastcgi_pass unix:/run/php/php8.3-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_intercept_errors on; + include fastcgi_params; } - location ~ \.php$ { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - - # fastcgi_pass need to be changed according your server setup: - # phpx.x is your server setup - # examples: /var/run/phpx.x-fpm.sock, /var/run/php/phpx.x-fpm.sock or /run/php/phpx.x-fpm.sock are all valid options - # Or even localhost:port (Default 9000 will work fine) - # Please check your server setup - - fastcgi_pass unix:/run/php/phpx.x-fpm.sock; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_intercept_errors on; - include fastcgi_params; - } - - location ~* ^/(css|img|js|flv|swf|download)/(.+)$ { - root $root_path; - expires off; - } + # Block direct execution of every other PHP file + location ~* \.php$ { + return 403; + } } ``` {% /tabitem %} From 25dc1316c315f91064ac89a82aac0816050d2682 Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Wed, 2 Sep 2026 16:53:41 +0100 Subject: [PATCH 2/4] Address review feedback: nginx compatibility and BoxBilling IPN alias - Revert to the combined "listen ... ssl http2;" form. The standalone "http2 on;" directive only exists from nginx 1.25.1 (Jun 2023) onward; on older nginx (e.g. 1.18, still the default in several LTS distro repos) it's an unrecognized directive and fails config parsing entirely, which is worse than the deprecation warning it was avoiding. Verified against both nginx:alpine (latest) and nginx:1.18-alpine. - Add a callout documenting that the NGINX config doesn't replicate .htaccess's bb-ipn.php alias / bb_* legacy query-param translation for old BoxBilling PayPal IPN URLs. Confirmed via git history this gap predates this PR (the NGINX sample never had this), and via ipn.php's source that there's no PHP-side fallback for the bb_* names - .htaccess is the only place doing this translation. A faithful port needs chained if/map blocks doing manual query-string surgery, which doesn't belong in a beginner-facing starting-point config, so this documents the gap instead of shipping a fragile partial workaround. --- src/content/docs/getting-started/installation.mdoc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/content/docs/getting-started/installation.mdoc b/src/content/docs/getting-started/installation.mdoc index fb9e1f9..2ff7b87 100644 --- a/src/content/docs/getting-started/installation.mdoc +++ b/src/content/docs/getting-started/installation.mdoc @@ -42,9 +42,8 @@ server { } server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; + listen 443 ssl http2; + listen [::]:443 ssl http2; ssl_certificate /path/to/ssl/certificate.crt; ssl_certificate_key /path/to/ssl/certificate.key; ssl_stapling on; @@ -102,6 +101,10 @@ server { } } ``` + + {% aside type="caution" title="Migrating from BoxBilling?" %} + The `.htaccess` file aliases the old `bb-ipn.php` callback to `ipn.php` and translates its legacy `bb_invoice_id`, `bb_gateway_id`, `bb_redirect`, and `bb_invoice_hash` query parameters, since old PayPal recurring-payment IPN URLs can't be updated after the fact. The NGINX config above doesn't replicate this. If you still receive callbacks at the old URL or parameter names, either use Apache/LiteSpeed instead, or add an equivalent rewrite for your NGINX setup. + {% /aside %} {% /tabitem %} {% /tabs %} From ad157fdb87f754e0e9f4339d8c4160e692a5372e Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Wed, 2 Sep 2026 17:03:27 +0100 Subject: [PATCH 3/4] Fix CI: aside tag not supported inside tabitem The Markdoc "tabitem" node doesn't accept a nested "aside" tag as a child (astro build failed with "Node 'tabitem' is missing closing" / "Missing required attribute: 'label'" regardless of the aside's position or content - reproduced locally and bisected to confirm). Replaced it with a plain bold-prefixed paragraph, matching the existing "**OpenLiteSpeed users:** ..." callout already used the same way in the Apache tabitem right above it. Verified locally: npm run build, npm run check, and npm run format:check all pass. --- src/content/docs/getting-started/installation.mdoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/content/docs/getting-started/installation.mdoc b/src/content/docs/getting-started/installation.mdoc index 2ff7b87..f36c97d 100644 --- a/src/content/docs/getting-started/installation.mdoc +++ b/src/content/docs/getting-started/installation.mdoc @@ -102,9 +102,7 @@ server { } ``` - {% aside type="caution" title="Migrating from BoxBilling?" %} - The `.htaccess` file aliases the old `bb-ipn.php` callback to `ipn.php` and translates its legacy `bb_invoice_id`, `bb_gateway_id`, `bb_redirect`, and `bb_invoice_hash` query parameters, since old PayPal recurring-payment IPN URLs can't be updated after the fact. The NGINX config above doesn't replicate this. If you still receive callbacks at the old URL or parameter names, either use Apache/LiteSpeed instead, or add an equivalent rewrite for your NGINX setup. - {% /aside %} + **Migrating from BoxBilling?** The `.htaccess` file aliases the old `bb-ipn.php` callback to `ipn.php` and translates its legacy `bb_invoice_id`, `bb_gateway_id`, `bb_redirect`, and `bb_invoice_hash` query parameters, since old PayPal recurring-payment IPN URLs can't be updated after the fact. The NGINX config above doesn't replicate this. If you still receive callbacks at the old URL or parameter names, either use Apache/LiteSpeed instead, or add an equivalent rewrite for your NGINX setup. {% /tabitem %} {% /tabs %} From 55635ce02cae397ae14426afc91f919bee1e1f28 Mon Sep 17 00:00:00 2001 From: Adam Daley Date: Wed, 2 Sep 2026 17:13:17 +0100 Subject: [PATCH 4/4] Update NGINX configuration instructions in installation.mdoc Removed unnecessary reference to the extracted release archive directory in NGINX configuration instructions. --- src/content/docs/getting-started/installation.mdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/getting-started/installation.mdoc b/src/content/docs/getting-started/installation.mdoc index f36c97d..ce9989b 100644 --- a/src/content/docs/getting-started/installation.mdoc +++ b/src/content/docs/getting-started/installation.mdoc @@ -31,7 +31,7 @@ Apache usually requires no extra application-specific configuration. The templat **OpenLiteSpeed users:** After installation, remember to reload the web server so it picks up the new `.htaccess` file. {% /tabitem %} {% tabitem label="NGINX" %} - Use the configuration below as a starting point for running FOSSBilling on NGINX. Be sure to replace placeholder values (like `%%DOMAIN%%` and `%%SOURCE_PATH%%`) and adjust SSL paths to match your environment. `%%SOURCE_PATH%%` is the directory you extracted the release archive into — the one that directly contains `index.php`, not a `src` subfolder. + Use the configuration below as a starting point for running FOSSBilling on NGINX. Be sure to replace placeholder values (like `%%DOMAIN%%` and `%%SOURCE_PATH%%`) and adjust SSL paths to match your environment. ```nginx server {