This guide explains the native Invoice Ninja installation used by the Sive AppStore image. It is intended to help administrators understand, maintain, back up, and troubleshoot the service after deployment. The reference build installs Invoice Ninja directly on Ubuntu; it does not use Docker or another container platform.
Before you begin
Run the commands as root on a fresh Ubuntu 24.04 LTS x86_64 server. Replace invoice.example.com and admin@example.com with your real values. DNS for the hostname should point to the server before requesting a TLS certificate.
The application lives in /var/www/invoiceninja. Root-only supporting secrets are kept in /etc/invoiceninja. Never paste passwords or the contents of .env into a support ticket or public terminal transcript.
1. Install the operating-system packages
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl fonts-liberation libasound2t64 \
libatk-bridge2.0-0t64 libatk1.0-0t64 libcups2t64 libgbm1 \
libgtk-3-0t64 libnspr4 libnss3 libx11-xcb1 libxcomposite1 \
libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 \
mariadb-server nginx openssl php8.3-bcmath php8.3-cli \
php8.3-common php8.3-curl php8.3-fpm php8.3-gd php8.3-gmp \
php8.3-imagick php8.3-intl php8.3-mbstring php8.3-mysql \
php8.3-soap php8.3-xml php8.3-zip qemu-guest-agent unzip xdg-utils
systemctl enable --now mariadb php8.3-fpm
systemctl enable nginx
The desktop-style libraries are runtime dependencies of the headless Chromium build used by SnapPDF to render invoices and quotations.
2. Apply the PHP settings
Create /etc/php/8.3/mods-available/invoiceninja.ini:
memory_limit=1024M
upload_max_filesize=100M
post_max_size=100M
max_execution_time=300
max_input_time=300
max_input_vars=5000
date.timezone=Africa/Johannesburg
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=30000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
Enable the settings and confirm that the required extensions are present:
phpenmod invoiceninja
systemctl restart php8.3-fpm
for module in bcmath curl dom gd gmp imagick intl mbstring mysqli soap xml zip; do
php -m | grep -Fxiq "$module" || echo "Missing PHP module: $module"
done
3. Create MariaDB and protect its password
install -d -m 0700 -o root -g root /etc/invoiceninja
DB_PASSWORD="$(openssl rand -hex 32)"
printf '%s\n' "$DB_PASSWORD" > /etc/invoiceninja/database-password
chmod 0600 /etc/invoiceninja/database-password
mysql --protocol=socket <<SQL
CREATE DATABASE IF NOT EXISTS invoiceninja
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'invoiceninja'@'localhost'
IDENTIFIED BY '${DB_PASSWORD}';
ALTER USER 'invoiceninja'@'localhost'
IDENTIFIED BY '${DB_PASSWORD}';
GRANT ALL PRIVILEGES ON invoiceninja.* TO 'invoiceninja'@'localhost';
FLUSH PRIVILEGES;
SQL
unset DB_PASSWORD
The generated database password contains hexadecimal characters only, which also makes it safe to use in the SQL statements above. Keep the password file readable only by root.
4. Download and verify the pinned application
The reference image uses the release and checksums below. Checksum verification is mandatory: stop if either command reports a mismatch.
VERSION="5.13.26"
WORK_DIR="$(mktemp -d /var/www/invoiceninja-install.XXXXXX)"
curl -fL --retry 4 --retry-delay 3 \
-o "$WORK_DIR/invoiceninja.tar" \
"https://github.com/invoiceninja/invoiceninja/releases/download/v5.13.26/invoiceninja.tar"
printf '%s %s\n' \
'be344fc3977394b7defda1bf636f33d42108b4045bb310e5aa91176749666dbc' \
"$WORK_DIR/invoiceninja.tar" | sha256sum -c -
curl -fL --retry 4 --retry-delay 3 \
-o "$WORK_DIR/ungoogled.tar" \
'https://pdf.invoicing.co/ungoogled.tar'
printf '%s %s\n' \
'bc241a3a17df91dff21d57326ce5c8bf6f8ef55b8384d94e0c1007892fba2868' \
"$WORK_DIR/ungoogled.tar" | sha256sum -c -
Extract the release and verify its internal version marker:
install -d -m 0755 "$WORK_DIR/app"
tar -xzf "$WORK_DIR/invoiceninja.tar" -C "$WORK_DIR/app"
test "$(tr -d '[:space:]' < "$WORK_DIR/app/VERSION.txt")" = "$VERSION"
systemctl stop invoiceninja-queue.service 2>/dev/null || true
systemctl stop invoiceninja-scheduler.timer 2>/dev/null || true
test ! -e /var/www/invoiceninja
mv "$WORK_DIR/app" /var/www/invoiceninja
CHROMIUM_DIR="/var/www/invoiceninja/vendor/beganovich/snappdf/versions/ungoogled"
install -d -m 0755 "$CHROMIUM_DIR"
tar -xzf "$WORK_DIR/ungoogled.tar" -C "$CHROMIUM_DIR"
printf 'ungoogled\n' > \
/var/www/invoiceninja/vendor/beganovich/snappdf/versions/revision.txt
chmod 0755 "$CHROMIUM_DIR/chrome-linux/chrome" \
"$CHROMIUM_DIR/chrome-linux/chrome_crashpad_handler"
If you are replacing an existing installation, do not use the test ! -e and mv sequence until you have completed the backup and upgrade procedure later in this guide.
5. Configure Invoice Ninja
Generate unique application secrets. Do not reuse the example names as actual secret values:
APP_KEY_VALUE="$(openssl rand -base64 32 | tr -d '\n')"
UPDATE_SECRET_VALUE="$(openssl rand -hex 32)"
WEBCRON_SECRET_VALUE="$(openssl rand -hex 32)"
DB_PASSWORD="$(cat /etc/invoiceninja/database-password)"
Create /var/www/invoiceninja/.env, substituting the generated shell variables and your hostname:
APP_NAME="Invoice Ninja"
APP_ENV=production
APP_KEY=base64:CHANGE_ME_TO_APP_KEY_VALUE
APP_DEBUG=false
APP_URL=https://invoice.example.com
REACT_URL=https://invoice.example.com
CALENDAR_NATIVE_REDIRECT=invoiceninja://calendar_connection/complete
REQUIRE_HTTPS=true
TRUSTED_PROXIES=
DB_CONNECTION=mysql
MULTI_DB_ENABLED=false
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=invoiceninja
DB_USERNAME=invoiceninja
DB_PASSWORD=CHANGE_ME_TO_DATABASE_PASSWORD
LOG_CHANNEL=stack
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
INTERNAL_QUEUE_ENABLED=false
MAIL_MAILER=log
MAIL_FROM_ADDRESS=admin@example.com
MAIL_FROM_NAME="Invoice Ninja"
NINJA_ENVIRONMENT=selfhost
IS_DOCKER=false
FILESYSTEM_DRIVER=public
PDF_GENERATOR=snappdf
SNAPPDF_EXECUTABLE_PATH=/var/www/invoiceninja/vendor/beganovich/snappdf/versions/ungoogled/chrome-linux/chrome
SNAPPDF_SKIP_DOWNLOAD=true
SNAPPDF_EXECUTABLE_ARGUMENTS="--headless --disable-gpu --no-sandbox --disable-dev-shm-usage --disable-crash-reporter"
API_SECRET=
UPDATE_SECRET=CHANGE_ME_TO_UPDATE_SECRET_VALUE
WEBCRON_SECRET=CHANGE_ME_TO_WEBCRON_SECRET_VALUE
DEMO_MODE=false
SCOUT_DRIVER=null
ERROR_EMAIL=
Replace every CHANGE_ME value before continuing. Preserve the existing APP_KEY during future upgrades; changing it can make encrypted application data unreadable.
Apply ownership and permissions:
chown -R www-data:www-data /var/www/invoiceninja
find /var/www/invoiceninja -type d -exec chmod 0755 {} +
find /var/www/invoiceninja -type f -exec chmod 0644 {} +
chmod 0600 /var/www/invoiceninja/.env
chmod 0755 /var/www/invoiceninja/artisan \
/var/www/invoiceninja/vendor/bin/snappdf \
/var/www/invoiceninja/vendor/beganovich/snappdf/versions/ungoogled/chrome-linux/chrome \
/var/www/invoiceninja/vendor/beganovich/snappdf/versions/ungoogled/chrome-linux/chrome_crashpad_handler
install -d -m 0775 -o www-data -g www-data \
/var/www/invoiceninja/storage \
/var/www/invoiceninja/storage/app \
/var/www/invoiceninja/storage/framework/cache \
/var/www/invoiceninja/storage/framework/sessions \
/var/www/invoiceninja/storage/framework/views \
/var/www/invoiceninja/storage/logs \
/var/www/invoiceninja/bootstrap/cache
6. Configure Nginx
Create /etc/nginx/sites-available/invoiceninja:
server {
listen 80;
listen [::]:80;
server_name invoice.example.com;
root /var/www/invoiceninja/public;
index index.php index.html;
client_max_body_size 100M;
server_tokens off;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /index.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 300;
}
location ~ \.php$ {
return 404;
}
location ~ /\.(?!well-known) {
deny all;
}
}
This layout forwards only /index.php to PHP-FPM and rejects requests for any other PHP file.
ln -sfn /etc/nginx/sites-available/invoiceninja \
/etc/nginx/sites-enabled/invoiceninja
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl restart nginx
7. Initialize the database and administrator
cd /var/www/invoiceninja
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan optimize:clear
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan migrate --force
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan db:seed --force
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan storage:link
runuser -u www-data -- env HOME=/var/www/invoiceninja \
php artisan ninja:create-account \
--email='admin@example.com' --password='USE_A_NEW_LONG_RANDOM_PASSWORD'
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan optimize
Run ninja:create-account only when the database does not already contain an account. Store the administrator password in a password manager and change it after the first login if it was exposed in shell history.
8. Run the queue and scheduler with systemd
Create /etc/systemd/system/invoiceninja-queue.service:
[Unit]
Description=Invoice Ninja Laravel queue worker
Requires=mariadb.service
After=network-online.target mariadb.service php8.3-fpm.service
ConditionPathExists=/var/www/invoiceninja/artisan
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/invoiceninja
Environment=HOME=/var/www/invoiceninja
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --timeout=120 --max-time=3600
ExecReload=/usr/bin/php artisan queue:restart
Restart=always
RestartSec=5s
KillSignal=SIGTERM
TimeoutStopSec=360s
UMask=0027
[Install]
WantedBy=multi-user.target
Create /etc/systemd/system/invoiceninja-scheduler.service:
[Unit]
Description=Run the Invoice Ninja Laravel scheduler
Requires=mariadb.service
After=mariadb.service
ConditionPathExists=/var/www/invoiceninja/artisan
[Service]
Type=oneshot
User=www-data
Group=www-data
WorkingDirectory=/var/www/invoiceninja
Environment=HOME=/var/www/invoiceninja
ExecStart=/usr/bin/php artisan schedule:run
Nice=10
UMask=0027
Create /etc/systemd/system/invoiceninja-scheduler.timer:
[Unit]
Description=Run the Invoice Ninja scheduler every minute
[Timer]
OnCalendar=*-*-* *:*:00
AccuracySec=1s
Persistent=true
Unit=invoiceninja-scheduler.service
[Install]
WantedBy=timers.target
Load and start the units:
systemctl daemon-reload
systemctl enable --now invoiceninja-queue.service
systemctl enable --now invoiceninja-scheduler.timer
systemctl status invoiceninja-queue.service --no-pager
systemctl list-timers invoiceninja-scheduler.timer --no-pager
9. Enable HTTPS and the firewall
After the hostname resolves to the server, install Certbot and request the certificate:
apt-get install -y certbot python3-certbot-nginx
certbot --nginx -d invoice.example.com
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
Confirm that APP_URL and REACT_URL exactly match the final HTTPS URL and that REQUIRE_HTTPS=true. Then clear and rebuild the application cache:
cd /var/www/invoiceninja
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan optimize:clear
runuser -u www-data -- env HOME=/var/www/invoiceninja php artisan optimize
systemctl reload invoiceninja-queue.service
systemctl reload nginx
10. Configure outgoing mail
The initial MAIL_MAILER=log setting writes messages to the application log and does not deliver them. Configure a real SMTP service in .env, for example:
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=CHANGE_ME
MAIL_PASSWORD=CHANGE_ME
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=billing@example.com
MAIL_FROM_NAME="Invoice Ninja"
After changing mail settings, run php artisan optimize:clear and php artisan optimize as www-data, then restart the queue worker. Use Invoice Ninja's mail test before sending real invoices.
Health checks
curl -fsSI https://invoice.example.com/
curl -fsS -D - -o /dev/null \
-H 'Host: invoice.example.com' http://127.0.0.1/ \
| grep -Fi 'X-APP-VERSION: 5.13.26'
systemctl is-active mariadb php8.3-fpm nginx invoiceninja-queue.service
systemctl is-active invoiceninja-scheduler.timer
mysql invoiceninja -e 'SELECT COUNT(*) AS accounts FROM accounts;'
sudo -u www-data env HOME=/var/www/invoiceninja/storage \
timeout 45 \
/var/www/invoiceninja/vendor/beganovich/snappdf/versions/ungoogled/chrome-linux/chrome \
--headless --disable-gpu --no-sandbox --disable-dev-shm-usage \
--disable-crash-reporter --dump-dom about:blank >/dev/null
A successful result is an HTTP 200 response, the expected application-version header, active services, at least one account, and a zero exit status from the Chromium smoke test.
Routine maintenance
- Install Ubuntu security updates regularly and reboot when required.
- Monitor
systemctl status invoiceninja-queue.serviceandjournalctl -u invoiceninja-queue.service. - Review
/var/www/invoiceninja/storage/logs/laravel.logfor application errors. - Verify the scheduler with
journalctl -u invoiceninja-scheduler.service. - Check certificate renewal with
certbot renew --dry-run. - Watch free disk space and MariaDB growth; uploaded documents and generated PDFs can grow over time.
Backups and restoration
A usable backup must contain both the database and the files that match it. At minimum, retain:
- A consistent MariaDB dump of the
invoiceninjadatabase. /var/www/invoiceninja/.env, especially its originalAPP_KEY./var/www/invoiceninja/storage, including uploaded documents./etc/invoiceninja, Nginx configuration, and the three systemd unit files.
Example backup commands:
BACKUP_DIR="/root/invoiceninja-backup-$(date +%F-%H%M%S)"
install -d -m 0700 "$BACKUP_DIR"
mariadb-dump --single-transaction --routines --triggers invoiceninja \
> "$BACKUP_DIR/invoiceninja.sql"
tar -C / -czf "$BACKUP_DIR/invoiceninja-files.tar.gz" \
var/www/invoiceninja/.env \
var/www/invoiceninja/storage \
etc/invoiceninja \
etc/nginx/sites-available/invoiceninja \
etc/systemd/system/invoiceninja-queue.service \
etc/systemd/system/invoiceninja-scheduler.service \
etc/systemd/system/invoiceninja-scheduler.timer
chmod -R go-rwx "$BACKUP_DIR"
Copy the backup off the server and test restoration periodically. During restoration, deploy the matching application release, restore files and ownership, import the SQL dump, run migrations, rebuild caches, and restart the worker. Do not generate a new APP_KEY.
Updating Invoice Ninja
Read the Invoice Ninja release notes and requirements before every update. Do not assume that a future release supports the same PHP version or that the downloadable archive has the same checksum.
- Take and verify a complete backup.
- Download the intended release from the official project and verify its published checksum.
- Enable maintenance mode:
sudo -u www-data php artisan down. - Stop the queue worker and scheduler timer.
- Extract the new code to a separate directory. Preserve the existing
.envandstoragedata. - Apply the ownership and permissions described above.
- Run
php artisan optimize:clear,php artisan migrate --force, andphp artisan optimizeaswww-data. - Start the timer and worker, disable maintenance mode with
php artisan up, and repeat every health check.
Keep the previous application tree and database backup until the new version has processed queues and generated a test PDF successfully.
Troubleshooting
502 Bad Gateway
systemctl status php8.3-fpm nginx --no-pager
test -S /run/php/php8.3-fpm.sock
nginx -t
journalctl -u php8.3-fpm -u nginx -n 100 --no-pager
Queue jobs do not run
systemctl status invoiceninja-queue.service --no-pager
journalctl -u invoiceninja-queue.service -n 100 --no-pager
cd /var/www/invoiceninja
sudo -u www-data php artisan queue:failed
sudo -u www-data php artisan queue:restart
Scheduled tasks do not run
systemctl list-timers invoiceninja-scheduler.timer --all
journalctl -u invoiceninja-scheduler.service -n 100 --no-pager
cd /var/www/invoiceninja
sudo -u www-data php artisan schedule:list
sudo -u www-data php artisan schedule:run -v
Invoices or PDFs fail to render
Run the Chromium smoke test from the health-check section as www-data. Confirm that SNAPPDF_EXECUTABLE_PATH exists and is executable, then inspect the Laravel log. A missing Chromium library normally appears in the command's terminal output.
Permission errors
chown -R www-data:www-data /var/www/invoiceninja/storage \
/var/www/invoiceninja/bootstrap/cache
find /var/www/invoiceninja/storage /var/www/invoiceninja/bootstrap/cache \
-type d -exec chmod 0775 {} +
chmod 0600 /var/www/invoiceninja/.env
Configuration changes appear to be ignored
cd /var/www/invoiceninja
sudo -u www-data php artisan optimize:clear
sudo -u www-data php artisan optimize
systemctl restart invoiceninja-queue.service
APP_DEBUG=false, restrict .env and backup permissions, use HTTPS, patch the operating system, and never change the established APP_KEY during normal maintenance.

