Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Setting Up FOSSBilling with Nginx, PHP-FPM, and MariaDB on Debian

profile picture
Author
Kürşad Ölmez
Published
2026-11-09
Time to read
12 minutes reading time

Introduction

FOSSBilling is an open-source billing and client management platform, commonly used by hosting providers, freelancers, and small IT businesses to handle invoicing, recurring billing, and client accounts without depending on a commercial SaaS product.

This tutorial covers a complete production setup on Debian with Nginx and PHP-FPM: the LEMP stack, a hardened Nginx configuration that blocks access to sensitive files, and the mandatory cron job FOSSBilling relies on for renewals and notifications.

By the end of this tutorial, you will have:

  • A working FOSSBilling installation running on Nginx and PHP-FPM, with MariaDB as the database.
  • A hardened Nginx configuration matching FOSSBilling's own security recommendations.
  • The required cron job configured and verified.

Prerequisites

  • A server running Debian 13, with root or sudo access.
  • A domain or subdomain pointed at the server's IP (for example billing.<example.com>).
  • Basic familiarity with the Linux command line, Nginx, and PHP.

Example terminology

This tutorial uses the following placeholders. Replace them with your own values:

  • Domain: billing.<example.com>
  • FOSSBilling document root: /var/www/fossbilling
  • Database name: fossbilling
  • Database user: fossbilling_user

The <...> placeholders above are not real values - replace each one with your own domain, database name, and password before running any command.

Step 1 - Install the LEMP Stack

Install Nginx and MariaDB:

sudo apt update
sudo apt install nginx mariadb-server -y
sudo systemctl enable --now nginx mariadb

Secure the MariaDB installation. This removes anonymous users, disables remote root login, and removes the test database:

sudo mariadb-secure-installation

It will ask you a series of yes/no questions. Here's what each one means and what to answer:

Prompt Answer Why
Enter current password for root (enter for none): Press Enter No password has been set yet - this is expected on a fresh install.
Switch to unix_socket authentication [Y/n] n Debian's MariaDB already uses this (see below) - the question is really asking whether to re-confirm it, and answering n is safe.
Change the root password? [Y/n] n Keep the passwordless unix_socket setup rather than adding a traditional password - it's the more secure default on Debian, not a weaker option.
Remove anonymous users? [Y/n] Y Anonymous accounts shouldn't exist on a production database.
Disallow root login remotely? [Y/n] Y Root should only ever be reachable locally, as you're doing throughout this tutorial.
Remove test database and access to it? [Y/n] Y The default test database isn't used by FOSSBilling and doesn't need to exist.
Reload privilege tables now? [Y/n] Y Applies everything you just answered.

Why unix_socket instead of a password: Debian's MariaDB package authenticates the root database user via unix_socket by default - meaning "root" can only log in when you're already the OS root user (via sudo), and there's no separate database password to leak, forget, or brute-force. That's why the tutorial keeps using sudo mysql rather than mysql -u root -p in the next step.

FOSSBilling officially supports PHP 8.3, 8.4, or 8.5. Debian 13 conveniently ships PHP 8.4 as its default version in the standard repositories - no third-party repository needed. (This is also why this tutorial targets Debian 13 specifically rather than Debian 12, which only ships PHP 8.2 by default and would need an extra repository to meet FOSSBilling's requirements.)

Install PHP-FPM and the recommended extensions. FOSSBilling's required extensions - curl, dom, iconv, intl, json, openssl, pdo_mysql, xml, and zlib - are mostly built into Debian's core PHP packages already; the packages below cover those plus the recommended ones (mbstring, opcache, and gd for image/PDF handling):

sudo apt install php-fpm php-cli php-common php-mysql php-curl \
  php-intl php-mbstring php-xml php-gd php-opcache -y

Confirm PHP is installed and every extension FOSSBilling requires is actually enabled:

php -v
php -m | grep -Ei 'curl|dom|iconv|intl|json|openssl|pdo_mysql|xml|zlib|mbstring|opcache|gd'

Step 2 - Create the Database

FOSSBilling requires MySQL 8.4+ or MariaDB 11.4+ - Debian 13's mariadb-server package ships MariaDB 11.8, which satisfies this.

sudo mysql

At the MariaDB prompt:

CREATE DATABASE fossbilling CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'fossbilling_user'@'localhost' IDENTIFIED BY '<a_strong_password>';
GRANT ALL PRIVILEGES ON fossbilling.* TO 'fossbilling_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Keep this database name, username, and password handy - the web installer will ask for them in Step 6.

Step 3 - Download and Extract FOSSBilling

FOSSBilling doesn't publish a single stable download link - each version has its own release asset on GitHub. Rather than querying the GitHub API to find the latest version (which has a strict 60-requests-per-hour limit for unauthenticated requests, easy to hit if you're behind a shared IP), resolve it from the redirect that github.com/.../releases/latest sends you to instead - this endpoint isn't subject to that limit:

sudo mkdir -p /var/www/fossbilling
cd /var/www/fossbilling
sudo apt install unzip -y

FOSSBILLING_VERSION=$(curl -sI https://github.com/FOSSBilling/FOSSBilling/releases/latest | grep -i '^location:' | sed -E 's#.*/tag/##' | tr -d '\r\n')
echo "Latest version: ${FOSSBILLING_VERSION}"
sudo curl -L -o FOSSBilling.zip "https://github.com/FOSSBilling/FOSSBilling/releases/download/${FOSSBILLING_VERSION}/FOSSBilling-${FOSSBILLING_VERSION}.zip"
sudo unzip FOSSBilling.zip
sudo rm FOSSBilling.zip

If the echo above prints an empty version, GitHub's response format has likely changed since this was written - browse the releases page directly and set FOSSBILLING_VERSION manually instead (for example FOSSBILLING_VERSION=0.8.9).

Set ownership to the user PHP-FPM runs as, so the application (and the web installer) can write to the directories it needs:

sudo chown -R www-data:www-data /var/www/fossbilling
sudo find /var/www/fossbilling -type d -exec chmod 750 {} \;
sudo find /var/www/fossbilling -type f -exec chmod 640 {} \;

Step 4 - Configure Nginx

FOSSBilling's own documentation provides a recommended NGINX template. Beyond the basic PHP-FPM proxy, it takes a deliberately restrictive approach: rather than trying to block a list of sensitive files by name, it only allows PHP-FPM to execute a small, explicit allow-list of entry-point scripts (index.php, ipn.php, and the two installer scripts) and returns a 403 for every other .php file - including config.php, cron.php, and any other file an attacker might otherwise try to hit directly. This tutorial's version is adapted from that template.

First, obtain a certificate (this tutorial assumes you already have a DNS record pointing at this server - see Hetzner's Certbot guide if you need help with this step):

sudo apt install certbot python3-certbot-nginx -y
sudo rm /etc/nginx/sites-enabled/default

Create the site configuration. Note the use of sudo tee rather than sudo cat > here - the > redirection is handled by your own shell before sudo ever runs, so it would fail with a permission error against /etc/nginx/; tee runs as the sudo'd process instead, so it can actually write there:

Replace billing.<example.com> with your own domain.

sudo tee /etc/nginx/sites-available/fossbilling > /dev/null << 'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name billing.<example.com>;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name billing.<example.com>;

    ssl_certificate /etc/letsencrypt/live/billing.<example.com>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/billing.<example.com>/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/billing.<example.com>/fullchain.pem;
    ssl_stapling on;
    ssl_stapling_verify on;

    root /var/www/fossbilling;
    index index.php;
    sendfile off;
    include /etc/nginx/mime.types;

    # Serve existing files directly; hand everything else to the front controller.
    try_files $uri $uri/ /index.php$is_args$args;

    # Block access to sensitive file types
    location ~* \.(ini|sh|inc|bak|twig|sql|conf|lock|log|old|ya?ml|htaccess|htpasswd)$ {
        return 403;
    }

    # Block /vendor completely
    location ^~ /vendor/ {
        return 403;
    }

    # Block per-theme configuration directories
    location ~* ^/themes/[^/]+/config/ {
        return 403;
    }

    # Block hidden files except .well-known (needed for ACME/Certbot renewals)
    location ~ /\.(?!well-known\/) {
        return 403;
    }

    # Deny access to runtime data
    location ^~ /data/ {
        return 403;
    }

    # Only these entry points are allowed to run through PHP-FPM
    location ~ ^/(index\.php|ipn\.php|install/index\.php|install/install\.php)$ {
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
        include fastcgi_params;
    }

    # Block direct execution of every other PHP file
    location ~* \.php$ {
        return 403;
    }
}
EOF

Confirm the socket this config points to actually exists before moving on:

ls /run/php/php8.4-fpm.sock

Obtain the certificate and let Certbot fill in the paths automatically:

sudo certbot --nginx -d billing.<example.com>

Enable the site and check the configuration before reloading:

sudo ln -s /etc/nginx/sites-available/fossbilling /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 5 - Tune PHP-FPM

FOSSBilling's documentation recommends a minimum memory_limit of 64M (more if you run many modules or expect high traffic), the default max_execution_time of 30 seconds is usually fine, and allow_url_fopen should be turned on if you use remote images in PDFs or emails.

sudo nano /etc/php/8.4/fpm/php.ini

Set the following values:

upload_max_filesize = 16M
post_max_size = 32M
memory_limit = 256M
max_execution_time = 45
allow_url_fopen = On

allow_url_fopen matters specifically if you plan to use remote (URL-based) images in generated invoice PDFs - without it, those images silently fail to render.

Restart PHP-FPM to apply the change:

sudo systemctl restart php8.4-fpm

Step 6 - Run the Web Installer

Visit https://billing.<example.com> in your browser. If you see FOSSBilling's installer welcome page, the web server and PHP are wired together correctly - if you see an HTTP 500 or 404 instead, double-check the fastcgi_pass socket path in Step 4 actually matches your installed PHP version, and confirm the permissions from Step 3 are in place.

Follow the setup wizard:

  1. Accept the license.

  2. Enter the database name, username, and password from Step 2. The example values were:

    Example value
    Database hostname localhost
    Database name fossbilling
    Username fossbilling_user
    Password <a_strong_password>
  3. Create your administrator account - use a strong, unique password, since this account has full control over client data and payment configuration.

  4. Confirm the public URL FOSSBilling detected matches your actual domain.

  5. Select your default currency and price format.

  6. Click install and wait for the success screen.

If the installer detects that it's running behind a reverse proxy (relevant if you're not terminating SSL directly on this server), it will offer to enable trusted proxy support - only enable this if the proxy in front of it is one you control.

Step 7 - Configure the Cron Job

FOSSBilling depends on a cron job for renewals, expired-order handling, outgoing email, and other scheduled background work. Skipping this step, or configuring it to run too infrequently, means invoices and renewal notices silently stop going out - with no obvious symptom until a client complains about a missed reminder.

The exact command to use is shown on the installation-complete screen, and again under Settings → Scheduled Tasks in the admin panel - copy it from there rather than guessing at the path, since it includes your specific installation's PHP binary and script path. Edit the crontab for the www-data user specifically, matching the file ownership from Step 3, rather than root's crontab:

sudo crontab -u www-data -e

Add a line like the following, run every five minutes as recommended:

*/5 * * * * php /var/www/fossbilling/cron.php > /dev/null 2>&1

FOSSBilling shows a warning banner in the admin panel if more than 15 minutes pass without a cron run - treat that warning as urgent, not cosmetic, since it usually means client-facing billing events are being missed in the meantime.

Step 8 - Validate

  • https://billing.<example.com> loads with a valid certificate, and you can log in to the admin panel at https://billing.<example.com>/admin.
  • Attempting to access https://billing.<example.com>/config.php or a file under /vendor/ directly returns a 403.
  • Settings → Scheduled Tasks in the admin panel shows cron running on schedule, with no warning banner.
  • Creating a test invoice and generating its PDF works, including any remote images if you use them.
  • php -m on the server still shows all the required extensions from Step 1 (a routine apt upgrade can occasionally disable one if it was reinstalled without recommended packages).

Conclusion

You now have a production FOSSBilling installation with a hardened Nginx configuration, working PDF generation, and a cron job keeping renewals and notifications flowing. From here, you can configure payment gateways, customize your invoice templates, and add staff accounts from the admin panel.

Next steps:

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue

Get new tutorials via RSS / Atom

Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2026 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more
Report Issue

Get new tutorials via RSS / Atom