Using nginx to serve from non-root path

The docs explain how to configure nginx to serve invoiceninja from https://www.example.com/.

Unfortunately I can’t dedicate an entire server for just one app - so I’m trying to serve it from a subdomain: https://apps.example.com/invoiceninja/. (Other paths, e.g. /phpmyadmin/ are for different apps.)

Serving on a non-root path using nginx is very tricky - if you’ve ever needed to do that before then you know what I mean. Especially if the app doesn’t support a “behind reverse proxy” header like HTTP_X_FORWARDED_HOST. Or one missing/extra trailing slash and nothing works.

So, does someone have an nginx config to share for this setup? Would be much appreciated, and maybe we could add it to the docs as well… Thanks!

Maybe this will help?

https://github.com/invoiceninja/dockerfiles/issues/14#issuecomment-241860289

Hi thanks for that! Unfortunately that issue is not for a subdirectory. :frowning:

After two days of hacking, I think it’s not possible to do this. (Which is weird because this is a standard use case.)

It works as foo.example.com, but not as foo.example.com/invoiceninja.

I use subdomains for every ‘app’ i run on my single server. A-Record for .example.com ist pointing to Server-IP so i just write a new entry for every app in my reverse proxy (HAProxy) like whatever.example.com. HAProxy also does the SSL-Termination using a LE Wildcard cert (.example.com) so SSL is already set up for every possible subdomain. Works fine with invoiceninja.

Hey @winkelement, thanks!

I’m using nginx and certbot, and got InvoiceNinja working on a subdomain root (invoiceninja.example.com).

Another approach, which is just as common, is to run apps on non-root paths:
apps.example.com/invoiceninja
apps.example.com/portainer
apps.example.com/crm
apps.example.com/logs
…etc.

That’s because most people don’t have wildcard certs, so a cert publicly lists all subdomains, and increases your attack surface. So to conceal your internal apps from the public, you need to use a wildcard cert, or the approach above.

I noticed you said you use a letsencrypt wildcard cert - I like that option, but most DNS providers don’t offer an API, so it’s impossible to automate dns-01 challenges. And so impossible to use wildcard.

I’m surprised InvoiceNinja doesn’t support this, as it’s the only app in my server I couldn’t set up this way.

It’s so good though… oh well! :slight_smile:

BTW @winkelement, in your LE wildcard setup, are you using a DNS vendor that offers an API, or are you using a tool like “acme-dns”?

I’m using a 5$ Digital Ocean Droplet to handle all routing via haproxy. Once you have at least one Droplet running you can manage DNS settings through DO for every domain you own. DO offers indeed an API so i am using the certbot-dns-digitalocean plugin for the LE wildcard cert(s).

@winkelement Thanks for confirming. Unfortunately switching hosters would be a major effort, but it’s good to know it’s possible.


If anyone stumbles on this thread and knows how to setup IN on a non-root path, please let us know!

I currently have what you’re asking about working for my site. I use NGINX to serve up Invoice Ninja and as reverse proxy. It took me quite a bit of research and watching the logs to figure out what was going wrong.

Here is my proxy config:

location ^~ /billing {
  proxy_set_header	Host $host;
  proxy_set_header	X-Real-IP $remote_addr;
  proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header	X-Forwarded-Proto $scheme;
  proxy_redirect	off;
  proxy_pass		http://invoice_ninja;
 }

Here is my nginx config for Invoice Ninja:

server {
    listen      80;
    server_name _;

    root /var/www/invoice_ninja/public/;
    index index.php index.html index.htm;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?is_args$args;
    }

    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/invoice-ninja.access.log;
    error_log   /var/log/nginx/invoice-ninja.error.log;

    location /billing {
        alias /var/www/invoice_ninja/public;
        try_files $uri $uri/ @billing;

        location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        }
    }

    location @billing {
        rewrite /billing/(.*)$ /billing/index.php?/ last;
    }

    location ~ /\.ht {
        deny all;
    }
}

Invoice Ninja is accessible from the /billing subfolder on my main site. The big piece to getting this working were the try_files $uri $uri/ @billing; and location @billing { rewrite /billing/(.*)$ /billing/index.php?/ last; }portions.
Getting the rewrite working properly was the largest hurdle.

Hope it helps.

@GhostZeroQ Thanks for posting your solution!

I tried your config… unfortunately it didn’t work for me. I actually tried that approach before. The only difference from your setup is I’m running in docker via a named network endpoint (not a socket). Or maybe it’s something else.

The error I get is from the container’s php-fpm process: Primary script unknown.

Also I’m unsure how you got it working without stripping the /billing/ prefix in the split_path directive… Does your install path have a /billing/ component in it maybe? e.g. /var/www/billing/app/public/ or something like that?