Invoice Ninja is an open-source, Laravel-based invoicing app for sending invoices, tracking payments, expenses, and time, a free alternative to FreshBooks or QuickBooks. This guide covers two install paths on Ubuntu 24.04: a native LEMP-stack release install, and a Docker Compose stack with Nginx, MariaDB, and Redis.
Prerequisites: an Ubuntu 24.04 server, non-root sudo user, a domain A record (e.g.
invoiceninja.example.com).
Common Setup
$sudo apt update
$sudo ufw allow http && sudo ufw allow https
Pick one of the two install paths below.
Option A: Install from the Release File
Gets you the latest features and runs on a standard LEMP stack. Requires PHP 8.3+.
Install Dependencies
$sudo apt install nginx mysql-server php -y
$php -v
$sudo apt install php8.3-bcmath php8.3-gmp php8.3-fileinfo \
php8.3-gd php8.3-mbstring php8.3-pdo php8.3-xml php8.3-cli \
php8.3-curl php8.3-zip php8.3-gmp php8.3-mysql php8.3-fpm -y
$sudo systemctl enable nginx
$sudo systemctl start nginx
$sudo systemctl status nginx
Create the Database
$sudo mysql -u root
mysql> CREATE DATABASE invoiceninjadb;
mysql> CREATE USER 'invoiceninja-admin'@'localhost' IDENTIFIED BY 'secure-password';
mysql> GRANT ALL PRIVILEGES ON invoiceninjadb.* TO 'invoiceninja-admin'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> EXIT;
Download and Extract
Check the releases page for the current version:
$sudo mkdir -p /var/www/invoiceninja
$cd /var/www/invoiceninja
$sudo wget https://github.com/invoiceninja/invoiceninja/releases/download/v5.11.72/invoiceninja.tar.gz
$sudo tar -xvf invoiceninja.tar.gz
$sudo rm invoiceninja.tar.gz
$sudo cp .env.example .env
$sudo chown -R www-data:www-data /var/www/invoiceninja
Set Up the Scheduler
$sudo -u www-data crontab -e
* * * * * php8.3 /var/www/invoiceninja/artisan schedule:run >> /dev/null 2>&1
$sudo -u www-data crontab -l
Configure Nginx
$sudo nano /etc/nginx/sites-available/invoiceninja.conf
server {
listen 80;
listen [::]:80;
server_name invoiceninja.example.com;
root /var/www/invoiceninja/public;
index index.php index.html index.htm;
client_max_body_size 20M;
charset utf-8;
access_log /var/log/nginx/ininja.access.log;
error_log /var/log/nginx/ininja.error.log;
gzip on;
gzip_types application/javascript application/x-javascript text/javascript text/plain application/xml application/json;
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 1000;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
if (!-e $request_filename) {
rewrite ^(.+)$ /index.php?q= last;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
}
$sudo rm /etc/nginx/sites-enabled/default
$sudo ln -s /etc/nginx/sites-available/invoiceninja.conf /etc/nginx/sites-enabled/invoiceninja.conf
$sudo nginx -t
$sudo systemctl restart nginx
TLS via Let's Encrypt
$sudo apt install python3-certbot-nginx
$sudo certbot --nginx -d invoiceninja.example.com -m admin@example.com --agree-tos
$sudo systemctl restart nginx
Run the Web Setup
Visit https://invoiceninja.example.com/setup:
- Enter your domain in URL, keep HTTPS Require enabled.
- Under Database Connection: driver
MySQL, hostlocalhost, port3306, and the DB credentials created above. Click Test connection. - Fill in admin user details, accept terms, click Submit.
Option B: Install with Docker Compose
Runs Nginx, MariaDB, Redis, and the app as containers — no manual package installs.
$sudo apt install docker.io docker-compose certbot python3-certbot-nginx -y
$sudo apt purge nginx -y
$cd
$mkdir invoiceninja && cd invoiceninja
$mkdir -p storage config public
$sudo chmod -R 777 storage public
Generate an app key:
$sudo docker run --rm -it invoiceninja/invoiceninja php artisan key:generate --show
Create docker-compose.yml:
$nano docker-compose.yml
version: '3.7'
services:
nginx:
image: nginx
restart: unless-stopped
volumes:
- ./config/in-vhost.conf:/etc/nginx/conf.d/in-vhost.conf:ro
- ./public:/var/www/app/public:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- app
ports:
- "80:80"
- "443:443"
healthcheck:
test: curl -f http://localhost:80/ || exit 1
networks:
- invoice_ninja_network
db:
image: mariadb:10.4
restart: unless-stopped
volumes:
- ./data:/var/lib/mysql:rw,delegated
environment:
MARIADB_PASSWORD: ninja
MARIADB_ROOT_PASSWORD: ninja
MARIADB_DATABASE: ninja
MARIADB_USER: ninja
PUID: "1026"
PGID: "100"
networks:
- invoice_ninja_network
cache:
image: redis
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cliping||exit1"]
networks:
- invoice_ninja_network
app:
image: invoiceninja/invoiceninja:5
restart: unless-stopped
volumes:
- ./public:/var/www/app/public:rw,delegated
- ./storage:/var/www/app/storage:rw,delegated
depends_on:
db:
condition: service_started
cache:
condition: service_healthy
environment:
APP_NAME: "InvoiceNinja"
APP_ENV: "production"
APP_KEY: base64:REPLACE_WITH_YOUR_GENERATED_KEY
APP_DEBUG: "0"
APP_URL: "https://invoiceninja.example.com"
IS_DOCKER: "true"
PHANTOMJS_PDF_GENERATION: "0"
PDF_GENERATOR: "snappdf"
TRUSTED_PROXIES: "*"
MULTI_DB_ENABLED: "0"
DB_HOST: db
DB_DATABASE: ninja
DB_USERNAME: ninja
DB_PASSWORD: ninja
DB_PORT: "3306"
PUID: "1026"
PGID: "100"
CACHE_DRIVER: redis
SESSION_DRIVER: redis
REDIS_HOST: cache
MAIL_MAILER: smtp
MAIL_HOST: smtp.example.com
MAIL_PORT: "465"
MAIL_USERNAME: your-smtp-user
MAIL_PASSWORD: your-smtp-password
MAIL_ENCRYPTION: SSL
MAIL_FROM_ADDRESS: "invoiceninja@example.com"
MAIL_FROM_NAME: "YourName"
REQUIRE_HTTPS: "1"
NINJA_ENVIRONMENT: "selfhost"
IN_USER_EMAIL: admin@example.com
IN_PASSWORD: set-a-strong-password
networks:
- invoice_ninja_network
networks:
invoice_ninja_network:
driver: bridge
Replace APP_KEY, APP_URL, the MARIADB_*/DB_* values, MAIL_* settings, and IN_USER_EMAIL/IN_PASSWORD before starting. Nginx proxies to the app container on port 9000.
Issue a certificate before bringing up Nginx (standalone mode needs port 80 free):
$sudo certbot certonly --standalone -d invoiceninja.example.com --email invoiceninja@example.com --agree-tos --non-interactive
Create the vhost config:
$nano config/in-vhost.conf
server {
listen 80 default_server;
server_name invoiceninja.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
server_name invoiceninja.example.com;
server_tokens off;
client_max_body_size 100M;
ssl_certificate /etc/letsencrypt/live/invoiceninja.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/invoiceninja.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
root /var/www/app/public/;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~* /storage/.*\.php$ {
return 503;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
}
$sudo docker-compose up -d
$sudo docker container ls
Automate cert renewal:
$sudo nano /etc/crontab
0 0 * * * root certbot renew --quiet
Use Invoice Ninja
- Visit
https://invoiceninja.example.com/login, sign in with your admin credentials. - On first login, set your company name and default currency.
- Fill in company details under Settings.
- Invoices → New Invoice → New Client to add a customer.
- Set invoice date, due date, invoice number, PO, and discount fields.
- Add Item → New Product to define line items with unit cost and quantity.
- Fill in notes/terms/footer, preview, then Save — download or print from there.
Next Steps
Invoice Ninja is running with either a native LEMP stack or a containerized Docker Compose setup. From here:
- Configure recurring invoices and payment reminders under company settings
- Connect a payment gateway (Stripe, PayPal) for online payment collection
- Set up scheduled database backups given financial data lives here
For the full guide, visit the original article on Vultr Docs.