← more articles

NGINX Cookbook

Few examples of common nginx needs.

HTTP to HTTPS redirection

server {
    server_name foo.server.org ;
    location / {
       return 302 https://$host$request_uri;
    }
}

protect with either user/pw or IP

# Limit access to the administration console
location /admin/ {
    allow 1.2.3.4;
    allow 5.6.7.8;
    allow 127.0.0.1;
    deny  all;

    auth_basic           "Administration";
    auth_basic_user_file conf/htpasswd-file;

    # allow either IP or user/password
    satisfy any;
}

htpasswd files

Plain text (insecure but simple):

$ cat htpasswd-file
charlie:{PLAIN}mypassword12345

Salted SHA1 password:

A shell-based mentioned at https://www.nginx.com/resources/wiki/community/faq/:

PASSWORD="SEcRe7PwD"
SALT="$(openssl rand -base64 3)"
SHA1=$(printf "$PASSWORD$SALT" \
      | openssl dgst -binary -sha1 \
      | xxd -ps \
      | sed 's#$#'"`echo -n $SALT | xxd -ps`"'#' | xxd -r -ps | base64);printf "Jim:{SSHA}$SHA1\n")

An easier script from https://github.com/agordon/bin_scripts:

$ create-ssha-passwd -u charlie | sudo tee -a htpasswd-file
Enter password:
Repeat password:

The file will contain something like:

charlie:{SSHA}7qbY4FyI8Dc9zsjC++w1QEYVQ5AuwpTr

For debugging purposes, add “-c” parameter to write the password in plain text after the salted hash (which is insecure but handy):

$ create-ssha-passwd -c -u charlie
charlie:{SSHA}7qbY4FyI8Dc9zsjC++w1QEYVQ5AuwpTr:foobar

Recommended to limit access to the password file:

sudo chown root:www-data htpasswd-file
sudo chmod 0640 htpasswd-file

Proxy error message fallback

Used when the back-end proxy server is down, instead of showing ugly “HTTP 502 Bad Gateway” errors:

server {
    ## Default location is the maintenance message.
    ## On HTTP Errors 502 (Bad Gateway) - show maintenance page.
    root /var/www/proxies/;

    # The content of this file will be displayed if the python proxy isn't working.
    error_page 502 /502.txt ;
    location /502.txt {
    }

   location / {
       proxy_pass http://127.0.0.1:10001;
   }
}

Note:\ the display error is still an ugly text file (502.txt), but more informative. To display pretty HTML files, implicitly allow the HTML file and other files (e.g css/png) BEFORE the catch-all “/” location.

Catch-all locations

For any requested URL starting with “/foobar” - show one specific file.

server {
    root /var/www/;
    location /foobar {
        # Doesn't matter which broken URL brought you here -
        # show the stub home page for this website
        rewrite ^.* /index.html break;
    }
}

directory path (root vs alias)

alias replaces the entire location URL.

root prepends a path to the requested URL.

# http://server/r/foo/bar.txt becomes /srv/git/foo/bar.txt
location /r {
    alias /srv/git;
    autoindex on;
}


# http://server/git/foo/bar.txt becomes /srv/git/foo/bar.txt
location /git {
    root /srv;
    autoindex on;
}

CGI scripts

recommendation

disable the SCRIPT_FILENAME and SCRIPT_NAME settings in /etc/nginx/fastcgi_params. Different settings are needed for different situations, > and having a common settings DOES NOT WORK.

Set them independently in for each CGI script.

Install the standard fcgiwrap package, and ensure it runs as a service:

apt-get install -yq fcgiwarp

On Debian/Ubuntu, spawnfcgi package will also be installed, and will be used (automatically) to start the fcgiwrap program (this is the prefered method).

On Debian/Ubuntu, the package is preconfigured to listen on a unix socket at /var/run/fcgiwrap.socket (set in /etc/init.d/fcgiwrap).

If starting the deamon manually, run something like:

fcgiwrap -c 5 -f -s /var/run/fcgiwrap.socket

Handling fcgiwrap errors

In nginx configuration, the cgi script file can be specified with DOCUMENT_ROOT + SCRIPT_NAME or with SCRIPT_FILENAME.

When the script is not found (or found but not executable), fcgiwrap will report this error message:

2016/12/12 17:28:45 [error] 27282#0: *35 FastCGI sent in stderr:
   "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME
    (or SCRIPT_FILENAME) set and is the script executable?"
   while reading response header from upstream, client: ::1,
   server: localhost, request: "GET /cgi1/ HTTP/1.1",
   upstream: "fastcgi://unix:/var/run/fcgiwrap.socket:",
   host: "localhost"

Sadly, it is not as helpful as it could be, and there’s no easy way to know which variable is set and used.

Be sure the define only one of them.

The following two settings are equivalent:

location /cgi1/ {
    fastcgi_param SCRIPT_FILENAME  /path/to/script/cgi.sh;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
location /cgi2/ {
    fastcgi_param DOCUMENT_ROOT     /path/to/script;
    fastcgi_param SCRIPT_NAME       /cgi.sh;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

Simple CGI script (no parameters)

Visiting “http://server/servertime/” will run the CGI script and return its output:

location /servertime/ {
    fastcgi_param SCRIPT_FILENAME      /path/to/script.sh;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

The script can be as simple as needed, e.g.:

#!/bin/sh
printf "content-type: text/plain\r\n\r\n"

echo "Hello !"
echo "Your remove address is ${REMOTE_ADDR}"
echo "Server time is: $(date)"

CGI script with HTTP GET parameters

Same nginx configuration as above.

HTTP/GET parameters will be sent to the script as the ‘QUERY_STRING’ environment variable. If you change the script above to:

#!/bin/sh
printf "content-type: text/plain\r\n\r\n"

echo "Hello !"
echo "Your remove address is ${REMOTE_ADDR}"
echo "Server time is: $(date)"
echo "parameters = '$QUERY_STRING'

and use a URL such as http://server/servertime/?timezone=JST

Then ‘QUERY_STRING’ will contain ‘timezone=JST’.

Perl’s CGI is handy:

#!/usr/bin/env perl
use CGI qw/:standard/;

@names = param;

print header('text/plain');
print "Hello! you've sent ", length(@names), " arameters:\n";
for (@names) {
    print $_, " => ", param($_), "\n";
}

with Python’s CGI module: (see https://docs.python.org/2/library/cgi.html)

#!/usr/bin/env python

import cgi
form = cgi.FieldStorage()

print "Content-Type: text/plain"
print ""

print "Hello!, you've sent these parameters:"
for i in form.keys():
    print i, " => ", form[i].value

PATH_INFO CGI parameter

In some setups is it preferable to pass the PATH of the URI to the CGI script, e.g. given http://server/downloads/foo/bar/file.txt we want to run downloader.pl and pass it /foo/bar/file.txt as a parameter.

The examples above would work as-is, except they would also pass the “/downloads/” part of the PATH to the CGI script (in the ‘$REQUEST_URI’ variable).

Using nginx’s fastcgi_split_path_info one can separate the base path (e.g. ‘/downloads/’) from the parameter part (e.g. ‘/foo/bar/file.txt’). This statement must have exactly two regex subexpressions. The first being the base path, the second being the parameter to forward to the CGI script.

location /downloads {
    # This command tells NGINX how to extract the path info part
    # from the requested URL: it trims the 'releases-redirect' part,
    # leaving only what follows it.
    fastcgi_split_path_info            ^(/downloads)(/?.+)$;

    # This command takes NGINX's path_info variable (extracted above),
    # and sends it to the FastCGI daemon as the PATH_INFO environment variable.
    fastcgi_param PATH_INFO            $fastcgi_path_info;

    fastcgi_param SCRIPT_FILENAME      /path/to/script/downloader.sh;

    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

The downloader.sh script can be:

#!/bin/sh
printf "content-type: text/plain\r\n\r\n"

echo "Hello !"
echo "You've asked to download the following file: '$PATH_INFO'"
echo "Extra parameters: '$QUERY_STRING'"

Given the URL http://localhost/downloads/foo/bar/file.txt?method=raw, the output of downloader.sh will be:

Hello !
You've asked to download the following file: '/foo/bar/file.txt'
Extra parameters: 'method=raw'

gitweb setup

gitweb is a perl-based web interface to git repositories. It is part of the standard git source code.

Two Debian packages provide the ‘gitweb’ cgi: git (in /usr/share/gitweb/gitweb.cgi) and gitweb (in /usr/lib/cgi-bin/gitweb.cgi).

Install either of them with apt-get install -yq gitweb git .

Option 1: gitweb repositories as CGI paraemters

The following configuration is used on gnu savannah.

The URL for a specific repository includes the repository name as a visible CGI parameter, e.g. http://server/gitweb/?p=sed.git.

location "/gitweb/static/" {
    # static files (png/css) served from /usr/share/gitweb/static
    root /usr/share/ ;
    expires 30d;
}

location /gitweb/ {
    fastcgi_param GITWEB_CONFIG    /etc/gitweb.conf;
    fastcgi_param SCRIPT_FILENAME  /usr/share/gitweb/gitweb.cgi;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

option 2: gitweb repositories as path components

The following configuration allows repositories to appear as part of the URL’s path, e.g. http://server/gitweb/sed.git.

location /gitweb/ {
    index gitweb.cgi
    fastcgi_param GITWEB_CONFIG  /etc/gitweb.conf;
    fastcgi_param DOCUMENT_ROOT  /usr/share/gitweb/;
    fastcgi_param SCRIPT_NAME    /gitweb.cgi$fastcgi_path_info;
    fastcgi_split_path_info      ^(/gitweb)(/?.+)$;

    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}

For this to work, add the following line to /etc/gitweb.conf:

$feature{'pathinfo'}{'default'} = [1];

cgit setup

cgit is a fast web interface to GIT repositories written in C.

See the https://git.zx2c4.com/cgit/tree/README file for installation instructions (there is currently no debian package for it).

The build files are minimal. Assuming they were installed to /opt/cgit:

$ cd /opt/cgit
$ ls -lhog
total 5.8M
-rwxrwxr-x 1 5.7M Oct 29 19:51 cgit
-rw-rw-r-- 1  13K Oct 29 19:51 cgit.css
-rw-rw-r-- 1 1.4K Oct 29 19:51 cgit.png
-rw-rw-r-- 1 1.1K Oct 29 19:51 favicon.ico
-rw-rw-r-- 1   47 Oct 29 19:51 robots.txt

The following nginx configuration will work:

location ~ "/cgit/cgit\.(png|css)$" {
        # Serve static files directly
        root /opt/ ;
        expires 30d;
}

location /cgit/ {
    fastcgi_param CGIT_CONFIG      /etc/cgitrc;
    fastcgi_param DOCUMENT_ROOT    /opt/cgit/;
    fastcgi_param SCRIPT_NAME      /cgit$fastcgi_path_info;
    fastcgi_split_path_info        ^(/cgit)(/?.+)$;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
← more articles