Serving via nginx on Debian and Ubuntu

This document is an extension of the platform-independent SCGI instructions, which may suffice for your purposes if your needs are simple.

Here, we add more detailed information on nginx itself, plus details about running it on Debian type OSes. We focus on Debian 10 (Buster) and Ubuntu 18.04 here, which are common Tier 1 OS offerings for virtual private servers. This material may not work for older OSes. It is known in particular to not work as given for Debian 9 and older!

If you want to add TLS to this configuration, that is covered in a separate document which was written with the assumption that you’ve read this first.


This scheme is considerably more complicated than the standalone HTTP server and CGI options. Even with the benefit of this guide and pre-built binary packages, it requires quite a bit of work to set it up. Why should you put up with this complexity? Because it gives many benefits that are difficult or impossible to get with the less complicated options:

Fossil Service Modes

Fossil provides four major ways to access a repository it’s serving remotely, three of which are straightforward to use with nginx:

SCGI it is, then.

Installing the Dependencies

The first step is to install some non-default packages we’ll need. SSH into your server, then say:

   $ sudo apt install fossil nginx

Running Fossil in SCGI Mode

For the following nginx configuration to work, it needs to contact a Fossil instance speaking the SCGI protocol. There are many ways to set that up. For Debian type systems, we recommend following our systemd system service guide.

There are other ways to arrange for Fossil to run as a service backing nginx, but however you do it, you need to match up the TCP port numbers between it and those in the nginx configuration below.


On Debian and Ubuntu systems the primary user-level configuration file for nginx is /etc/nginx/sites-enabled/default. I recommend that this file contain only a list of include statements, one for each site that server hosts:

  include local/
  include local/

Those files then each define one domain’s configuration. Here, /etc/nginx/local/ contains the configuration for * and its alias *; and local/ contains the configuration for *

The configuration for our web site, stored in /etc/nginx/sites-enabled/local/ is:

  server {
      server_name "";
      include local/generic;

      access_log /var/log/nginx/;
       error_log /var/log/nginx/;

      # Bypass Fossil for the static documentation generated from
      # our source code by Doxygen, so it merges into the embedded
      # doc URL hierarchy at Fossil’s $ROOT/doc without requiring that
      # these generated files actually be stored in the repo.  This
      # also lets us set aggressive caching on these docs, since
      # they rarely change.
      location /code/doc/html {
          root /var/www/;

          location ~* \.(html|ico|css|js|gif|jpg|png)$ {
              expires 7d;
              add_header Vary Accept-Encoding;
              access_log off;

      # Redirect everything else to the Fossil instance
      location /code {
          include scgi_params;
          scgi_param SCRIPT_NAME "/code";

As you can see, this is a pure extension of the basic nginx service configuration for SCGI, showing off a few ideas you might want to try on your own site, such as static asset proxying.

The local/generic file referenced above helps us reduce unnecessary repetition among the multiple sites this configuration hosts:

  root /var/www/$host;

  listen 80;
  listen [::]:80;

  charset utf-8;

There are some configuration directives that nginx refuses to substitute variables into, citing performance considerations, so there is a limit to how much repetition you can squeeze out this way. One such example is the access_log and error_log directives, which follow an obvious pattern from one host to the next. Sadly, you must tolerate some repetition across server { } blocks when setting up multiple domains on a single server.

The configuration for is similar.

See the nginx docs for more ideas.

Proxying HTTP Anyway

Above, we argued that proxying SCGI is a better option than making nginx reinterpret Fossil’s own implementation of HTTP. If you want Fossil to speak HTTP, just set Fossil up as a standalone server. And if you want nginx to provide TLS encryption for Fossil, proxying HTTP instead of SCGI provides no benefit.

However, it is still worth showing the proper method of proxying Fossil’s HTTP server through nginx if only to make reading nginx documentation on other sites easier:

    location /code {
        rewrite ^/code(/.*) $1 break;

The most common thing people get wrong when hand-rolling a configuration like this is to get the slashes wrong. Fossil is senstitive to this. For instance, Fossil will not collapse double slashes down to a single slash, as some other HTTP servers will.

Return to the top-level Fossil server article.