Install Invoice Ninja v5 on Ubuntu 20.04

Install Invoice Ninja v5 on Ubuntu 20.04

If you are self-hosting, installing on Ubuntu is the best possible method, and highly suggested. Running Ubuntu as a host allows you to install with the least amount of effort, fewer dependencies, and auto-update the application through the admin portal user interface. The ‘’ package that is compiled by the Invoice NInja team every release has many dependency packages bundled into it, but it is only compatible with Ubuntu, and maybe other distros more similar to Ubuntu. Because this package is used for the auto updates as well, auto updates only work on Ubuntu.

First steps: Install dependencies

Invoice Ninja on Ubuntu production servers depends on PHP 7.4 and multiple PHP 7.4 extensions that aren’t yet available on PHP 8.0, mariadb for SQL, and because it is a laravel application we will install composer. I don’t believe you need composer to install, or update Invoice Ninja on Ubuntu. You may consider removing composer, but I would suggest keeping it installed anyways, because it is generally reccomended on production servers running laravel applications.

# apt update
# apt dist-upgrade -y

# apt install php7.4 php7.4-{fpm,bcmath,ctype,fileinfo,json,mbstring,pdo,tokenizer,xml,curl,zip,gmp,gd,mysqli} mariadb-server mariadb-client curl git nginx vim composer -y

Done installing things. Let’s configure them.

Debian Users

You have different paths for php-fpm’s unix socket than Ubuntu users. This guide assumes you spawn /run/php/php-fpm.sock for a socket, but you do not. Other distros based on Debian or Ubuntu should pay attention to php-fpm.sock mentions in the nginx config example below, and change them to match the php-fpm path on your distro.

Check where php-fpm runs its socket with:

 # systemctl status php7.4-fpm

Second: Configure Mariadb

Start, and enable services for mariadb, the program/service that manages your SQL database and the incoming and outgoing communication it has with other applications like Invoice Ninja.

# systemctl enable --now mariadb

This command will take you through a guided wizard to initialize the SQL database.

# mysql_secure_installation
Enter current password for root (enter for none): 

Remove anonymous users? [Y/n] y

Disallow root login remotely? [Y/n] y

Remove test database and access to it? [Y/n] y

Reload privilege tables now? [Y/n] y

These commands will directly access the SQL database through the services provided by mariadb. We will create a database with any arbitrary name ‘ninjadb’ in this example, and create arbitrary username and password combination ‘ninja’ and ‘ninjapass’. The database name will be used by InvoiceNinja during the server setup after installation is complete, as well as the username and password you specify here, in order for InvoiceNinja to login to the SQL database with read/write permission.

# mysql -u root -p
Enter Password:  ******
MariaDB .. > create database ninjadb;
MariaDB .. > create user 'ninja'@'localhost' identified by 'ninjapass';
MariaDB .. > grant all privileges on ninjadb.* to 'ninja'@'localhost';
MariaDB .. > flush privileges;
MariaDB .. > exit

Third: Configure NGINX

FOR TESTING - NOT for Production use - OpenSSL certification

You should not need to do this. I am about to show you NGINX configuration file that points to an example based openssl cert. Most of you would be using letsencrypt, or some other CA. I am not going to provide a guide to setup letsencrypt. So for the purposes of this guide, the NGINX configuration will use an instant OpenSSL certificate that is not particularly trusted on the Internet.

# mkdir -p /etc/nginx/cert
# openssl req -new -x509 -days 365 -nodes -out /etc/nginx/cert/ninja.crt -keyout /etc/nginx/cert/ninja.key

Secure NGINX

The default NGINX install on Ubuntu has a pesky default website located at /etc/nginx/sites-enabled/default - and for our cases, we do not want this default website hosted by nginx. When left unconfigured, this page presents some security loopholes. It can also cause conflicts sometimes. Lets remove it.

# rm /etc/nginx/sites-enabled/default

NGINX configuration page for website

Create a text file with the ‘.conf’ ending in the /etc/nginx/conf.d/ directory, and any code in it will be included and run with the settings under /etc/nginx/nginx.conf.

# vim /etc/nginx/conf.d/invoiceninja.conf

Press ‘i’ to enter insert mode, and paste this server configuration.

Review it line by line, and edit the server name, root path, ssl path, php-fpm socket path, etc, as necessary.

You cannot copy paste this entire document, you will need to edit appropriate sections to accomodate your own environment. I will try to “”“indicate”"" when specific strings of text need your attention.

  server {
# NOTE That the 'default_server' option is only necessary if this is your primary domain application.
# If you run multiple subdomains from the same host already, remove the 'default_server' option.
   listen       443 ssl http2 default_server;
   listen       [::]:443 ssl http2 default_server;
   server_name  """""";
   client_max_body_size 20M;

 # This if statement will forcefully redirect any connection attempts to explicitly use the domain name.  
 # If not, and your DNS doesn't provide IP address protection, accessing the server with direct IP can
 # cause glitches with the services provided by the app, that could be security, or usability issues.

   if ($host != $server_name) {
     return 301 https://$server_name$request_uri;

 # Here, enter the path to your invoiceninja directory, in the public dir.  VERY IMPORTANT
 # DO NOT point the root directly at your invoiceninja directory, it MUST point at the public folder
 # This is for security reasons.
   root         /usr/share/nginx/"""invoiceninja"""/public;

   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;

   index index.php index.html index.htm;

 # Enter the path to your existing ssl certificate file, and certificate private key file
 # If you don’t have one yet, you can configure one with openssl in the next step.
   ssl_certificate "/etc/nginx/cert/ninja.crt";
   ssl_certificate_key "/etc/nginx/cert/ninja.key";

   ssl_session_cache shared:SSL:1m;
   ssl_session_timeout  10m;
   ssl_ciphers 'AES128+EECDH:AES128+EDH:!aNULL';
   ssl_prefer_server_ciphers on;
   ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

   charset utf-8;

 # Load configuration files for the default server block.
   include /etc/nginx/default.d/*.conf;

   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)(/.+)$;
      # Here we pass php requests to the php7.4-fpm listen socket.  
      # PHP errors are often because this value is not correct.  
      # Verify your php7.4-fpm.sock socket file exists at the below directory
      # and that the php7.4-fpm service is running.
           fastcgi_pass unix:/run/php/php7.4-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;

   location ~ /\.ht {
       deny all;

   location = /favicon.ico { access_log off; log_not_found off; }
   location = /robots.txt { access_log off; log_not_found off; }

   access_log /var/log/nginx/ininja.access.log;
   error_log /var/log/nginx/ininja.error.log;

   sendfile off;


  server {
      listen      80;
      server_name """""";
      add_header Strict-Transport-Security max-age=2592000;
      rewrite ^ https://$server_name$request_uri? permanent;

That’s great! Hope you remembered to check root, server_name, and SSL certificate paths. Next Steps.

For Ubuntu 20.04, I had to disable apache2 in order to enable nginx to run on ports 80 and 443 without conflict. You might prefer to use apache2, but I am only supporting one web server conf file, and am already using nginx.

Disable & stop Apache2. Start & enable NGINX.

  $ sudo systemctl stop apache2
  $ sudo systemctl disable apache2
  $ sudo systemctl start nginx
  $ sudo systemctl enable nginx

Fourth: Installing Invoice Ninja

Installing the application files in the NGINX web server directory.

Please visit to get the latest github release of InvoiceNinja from the team. Look closely at what you are downloading, the list also includes recent updates to v4.

  $ cd /usr/share/nginx
  $ sudo mkdir invoiceninja && cd invoiceninja
  $ sudo wget <latest zip url>
  $ sudo unzip

Populate .env file with new random encryption key

You have an automatically generated .env file with preset encryption key and ready to initialize the setup page.

Importnat Note!

It seems some versions of the InvoiceNinja package don’t come with a template .env file, for whatever reason.

Look first, to verify if a default .env file exists in your version of the package. If you already have a .env, you can skip to “Encryption and Backup”.

If you find yourself without a .env file, you will at least have a .env.example file. If you copy this file and name the copy .env. Then you want to edit the .env file and change the values of these variables: DB_DATABASE, DB_USERNAME, and DB_PASSWORD to match the database name you chose, and credentials you set to login to it with mariadb and mysql.

 # cp .env.example .env
 # vim .env

Reminder from earlier in the guide:

MariaDB .. > create database ninjadb;
MariaDB .. > create user 'ninja'@'localhost' identified by 'ninjapass';

The variables above highlighted in bold must be assigned the same values you used in these steps from earlier in the guide. This is how they should look in the .env file.


Encryption and Backup

Strongly reccomend you to, populate the .env file with a new genuine encryption key. Then BACKUP this file, because it is your key to your invoicing data. Do not lose this forever by accidentally deleting or overwriting it one day.

  # php7.4 artisan key:generate
  # cp .env /path/to/backup/.env.backup

Prepare the environment after installation / any changes

Often when you are hit with a 500 or page not working problem, you just have to run one of these two commands to verify permissions and reoptimize the software.

Set permissions for the directory and all its contents to allow web server permission to view and edit files.

  # chown -R www-data:www-data /usr/share/nginx/invoiceninja

Run auto configure process, something you must do again if you ever change the values of .env or other files within the invoiceninja directory.

  # php7.4 artisan optimize

Enable Cron job automation

Now, there is need to enable cron job that will run some sort of regular maintenance, or you get a nasty red exclamation mark error in InvoiceNinja after logging in. See here for more:

  $ sudo -u www-data crontab -e

Then copy paste the following into the bottom of your cron file, which will be run for something to do with laravel which InvoiceNinja depends on.

This will run, as user www-data, with the explicitly correct php version, the artisan schedule command in order to provide Invoice Ninja application with the backend services it needs to work properly. Edit the path according to your environment

  * * * * * php7.4 /usr/share/nginx/"""invoiceninja"""/artisan schedule:run >> /dev/null 2>&1

Optional Extra Steps

Acquire a PhantomJS Cloud Key

The default handler for generating PDFs is PhantomJsCloud service, which has a low limit for daily use based on IP address. You can register for a free account with up to 500 generated PDF per day at PhantomJsCloud API Service.

Your server will need a trusted SSL certificate for this service to work. OpenSSL certificates will not work.

Then you would enter the API key you are assigned by them into your .env file on the appropriate line:


Remember to also run artisan optimize after making changes to your .env file:

 # php artisan optimize



I won’t provide a step by step for how to backup your files, maybe not at this time yet, but ensure you have backed up your .env file, the public/storage directory, and the SQL database. As an admin, when you attach files to a client or invoice, to be viewed on the client portal for example, those files are stored in the public/storage directory. The SQL database contains all your important information directly. The .env file is precious, because it contains the encryption key for your SQL database, and you can get locked out without it.


Great Job, thanks. In my case, the only change I did was:
$ php `which composer` install
instead of
$ php `which composer` update

1 Like

Thanks for the positive feedback.

Thanks a lot, you have a small typo error “inatll” :wink:

1 Like

I followed the steps and when submitting the setup form I get a 500 response. Do you have any idea where I should look?

1 Like

Please check the web server error logs and the app error logs in storage/logs/laravel-error.log for details about the error.

i’ve seen this howto. But i have a litte error in nginx. I gat a 403 Forbidden. The log says only
*2 directory index of "/var/www/invoiceninja/" is forbidden
Any hints for me?

@CKMartens does the webuser have read/write access to that directory?

Update as of 5.0.35:

Follow all OP’s instructions as written for setting up webserver and unziping
Then do as follows:

cd /path/to/invoiceninja
sudo php /usr/bin/composer install --no-dev
sudo npm install --no-optional
sudo chown -R www-data:www-data ../invoiceninja
sudo chmod -R g+s ../invoiceninja
sudo chmod -R 775 ../invoiceninja/storage

Do not need to optimize at this point and you do not need to create storage links.

Run crontab as www-data (I had some issues with root owning files causing errors):

sudo crontab -u www-data -e

Past this into www-data’s crontab:

* * * * * php /usr/share/nginx/invoiceninja/artisan schedule:run >> /dev/null 2>&1

Hit the web interface and run setup.

If you are getting “Validation token was expired. Please try again.” on the client interface: edit .env, set SESSION_DRIVER=file, then run:

sudo php artisan optimize

1 Like

I know about storage:link already, those steps are guarunteeing permissions in the user directory are correct, and that the group permission will not change later if you do something in there. Group permission might be irrelevant, and an ACL edit might be more effective, but I would test that a bit more first before I change it.

Unless you are extra verbose, and type ‘sudo -u www-data’ for every command you run in that directory ever, you will need to reassign those permissions. Seeing as it’s a very common issue for users to miss it, or break the permissions, I’m inclined to leave it.

I’ve added the ‘sudo -u www-data crontab -e’ command though. Alternatively you can run it as root anyways, and add ‘www-data’ right before the ‘php’ command in the cron entry, and it ought to run that command as www-data user.


thank you for the guide.
i tried and it worked

1 Like

Hey there. Thanks for the guide. I would like to reach out for help. I’m using ubuntu 20.04 LTS with nginx. I have followed the instructions provided by invoiceninja and here. I can’t pass the check_pdf. It shows “Oops, looks like something isn’t correct!” which is not descriptive to me. I tried to look for the controller in the repository but I can’t find any. My laravel log seems good. Can someone help me?

Here is my logs
[2020-12-28 02:00:01] staging.INFO: Sending recurring invoices 2020-12-28 02:00:01
[2020-12-28 02:00:01] staging.INFO: 2020-12-28 Sending Recurring Invoices. Count = 0
[2020-12-28 02:06:02] staging.INFO: GET - xxx/setup: 2 queries - 0.15784692764282
[2020-12-28 02:06:09] staging.INFO: POST - xxx/setup/check_pdf: 0 queries - 5.3190200328827
[2020-12-28 02:07:11] staging.INFO: POST - xxx/setup/check_pdf: 0 queries - 5.8493161201477
[2020-12-28 02:09:39] staging.INFO: GET - xxx/setup: 0 queries - 0.052273988723755
[2020-12-28 02:09:53] staging.INFO: POST - xxx/setup/check_pdf: 0 queries - 3.8224358558655

Thanks for the guide!
I would ask you to remove vim from the install list and replace occurences of sudo vim with sudoedit or alternatively use $EDITOR to respect the user preferences and not install unrelated packages :slight_smile:

Further, you could replace this:

with sudo systemctl enable --now mariadb

That’s a new function to me so I will look into it when I have time sure.

Just an update, NPM/Node is no longer required for installation. It also appears that on some systems, the MariaDB client isn’t installed, this is required to perform the initial migration it can be installed by running

sudo apt install MariaDB-client



On Ubuntu 20.04, I also had to install the packages ‘libgbm-dev’ and ‘libxshmfence-dev’. Otherwise, no PDF invoice could be generated.

1 Like

Hey there, installation for missing dependencies is noted in our docs:

Yes, but ‘libgbm-dev’ and ‘libxshmfence-dev’ are not listed there.

Thanks for clarifying. I’ve updated the snappdf documentation :+1: