Skip to content

This document is a WORK IN PROGRESS.
This is just a quick personal cheat sheet: treat its contents with caution!


Nginx

Nginx is a robust, small, high performance web server and reverse proxy server.

Reference(s)

Table of contents


Install

Install nginx

# emerge -a www-servers/nginx
# pacman -S nginx
# apt install nginx
# yum install nginx
# dnf install nginx

Check nginx installation

# rc-service nginx start
$ curl http://localhost # should print "Welcome to nginx!"
# rc-service nginx stop
# sv start nginx
$ curl http://localhost # should print "Welcome to nginx!"
# sv stop nginx
# service nginx start
$ curl http://localhost # should print "Welcome to nginx!"
# service nginx stop
# systemctl start nginx
$ curl http://localhost # should print "Welcome to nginx!"
# systemctl stop nginx

Default Nginx config

Warning

The configuration of Nginx depends on the application one want to deploy with. The configuration of Nginx will therefore be addressed on a case by case basis: see the cheat sheet of the application you want to deploy (e.g. Nextcloud or e.g. Radicale).

For example here is my default /etc/nginx/nginx.conf configuration whit nginx v1.20.1 (on Arch Linux):

Default Nginx config
$ sudo vi /etc/nginx/nginx.conf

    > #user http;
    > worker_processes  1;
    >
    > #error_log  logs/error.log;
    > #error_log  logs/error.log  notice;
    > #error_log  logs/error.log  info;
    >
    > #pid        logs/nginx.pid;
    >
    >
    > events {
    >     worker_connections  1024;
    > }
    >
    >
    > http {
    >     include       mime.types;
    >     default_type  application/octet-stream;
    >
    >     types_hash_max_size 4096;           #
    >     server_names_hash_bucket_size 128;  # see https://wiki.archlinux.org/title/nginx#Warning:_Could_not_build_optimal_types_hash
    >
    >     #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    >     #                  '$status $body_bytes_sent "$http_referer" '
    >     #                  '"$http_user_agent" "$http_x_forwarded_for"';
    >
    >     #access_log  logs/access.log  main;
    >
    >     sendfile        on;
    >     #tcp_nopush     on;
    >
    >     #keepalive_timeout  0;
    >     keepalive_timeout  65;
    >
    >     #gzip  on;
    >
    >     server {
    >         listen       80;
    >         server_name  localhost;
    >
    >         #charset koi8-r;
    >
    >         #access_log  logs/host.access.log  main;
    >
    >         location / {
    >             root   /usr/share/nginx/html;
    >             index  index.html index.htm;
    >         }
    >
    >         #error_page  404              /404.html;
    >
    >         # redirect server error pages to the static page /50x.html
    >         #
    >         error_page   500 502 503 504  /50x.html;
    >         location = /50x.html {
    >             root   /usr/share/nginx/html;
    >         }
    >
    >         # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    >         #
    >         #location ~ \.php$ {
    >         #    proxy_pass   http://127.0.0.1;
    >         #}
    >
    >         # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    >         #
    >         #location ~ \.php$ {
    >         #    root           html;
    >         #    fastcgi_pass   127.0.0.1:9000;
    >         #    fastcgi_index  index.php;
    >         #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    >         #    include        fastcgi_params;
    >         #}
    >
    >         # deny access to .htaccess files, if Apache's document root
    >         # concurs with nginx's one
    >         #
    >         #location ~ /\.ht {
    >         #    deny  all;
    >         #}
    >     }
    >
    >
    >     # another virtual host using mix of IP-, name-, and port-based configuration
    >     #
    >     #server {
    >     #    listen       8000;
    >     #    listen       somename:8080;
    >     #    server_name  somename  alias  another.alias;
    >
    >     #    location / {
    >     #        root   html;
    >     #        index  index.html index.htm;
    >     #    }
    >     #}
    >
    >
    >     # HTTPS server
    >     #
    >     #server {
    >     #    listen       443 ssl;
    >     #    server_name  localhost;
    >
    >     #    ssl_certificate      cert.pem;
    >     #    ssl_certificate_key  cert.key;
    >
    >     #    ssl_session_cache    shared:SSL:1m;
    >     #    ssl_session_timeout  5m;
    >
    >     #    ssl_ciphers  HIGH:!aNULL:!MD5;
    >     #    ssl_prefer_server_ciphers  on;
    >
    >     #    location / {
    >     #        root   html;
    >     #        index  index.html index.htm;
    >     #    }
    >     #}
    >
    > }

And here is how I safely modified it:

Modified Nginx config
$ sudo vi /etc/nginx/nginx.conf

    > #user http;
    > worker_processes  1;
    >
    > #error_log  logs/error.log;
    > #error_log  logs/error.log  notice;
    > #error_log  logs/error.log  info;
    >
    > #pid        logs/nginx.pid;
    >
    >
    > events {
    >     worker_connections  1024;
    > }
    >
    >
    > http {
    >     include       mime.types;
    >     default_type  application/octet-stream;
    >
    >     types_hash_max_size 4096;           #
    >     server_names_hash_bucket_size 128;  # see https://wiki.archlinux.org/title/nginx#Warning:_Could_not_build_optimal_types_hash
    >
    >     #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    >     #                  '$status $body_bytes_sent "$http_referer" '
    >     #                  '"$http_user_agent" "$http_x_forwarded_for"';
    >
    >     #access_log  logs/access.log  main;
    >
    >     sendfile        on;
    >     #tcp_nopush     on;
    >
    >     #keepalive_timeout  0;
    >     keepalive_timeout  65;
    >
    >     #gzip  on;
    >
    >     include /etc/nginx/conf.d/default_nginx.conf;
    > }

# mkdir -p /etc/nginx/conf.d
# vi /etc/nginx/conf.d/default_nginx.conf

    > server {
    >     listen       80;
    >     server_name  localhost;
    >
    >     #charset koi8-r;
    >
    >     #access_log  logs/host.access.log  main;
    >
    >     location / {
    >         root   /usr/share/nginx/html;
    >         index  index.html index.htm;
    >     }
    >
    >     #error_page  404              /404.html;
    >
    >     # redirect server error pages to the static page /50x.html
    >     #
    >     error_page   500 502 503 504  /50x.html;
    >     location = /50x.html {
    >         root   /usr/share/nginx/html;
    >     }
    >
    >     # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    >     #
    >     #location ~ \.php$ {
    >     #    proxy_pass   http://127.0.0.1;
    >     #}
    >
    >     # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    >     #
    >     #location ~ \.php$ {
    >     #    root           html;
    >     #    fastcgi_pass   127.0.0.1:9000;
    >     #    fastcgi_index  index.php;
    >     #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    >     #    include        fastcgi_params;
    >     #}
    >
    >     # deny access to .htaccess files, if Apache's document root
    >     # concurs with nginx's one
    >     #
    >     #location ~ /\.ht {
    >     #    deny  all;
    >     #}
    > }
    >
    >
    > # another virtual host using mix of IP-, name-, and port-based configuration
    > #
    > #server {
    > #    listen       8000;
    > #    listen       somename:8080;
    > #    server_name  somename  alias  another.alias;
    >
    > #    location / {
    > #        root   html;
    > #        index  index.html index.htm;
    > #    }
    > #}
    >
    >
    > # HTTPS server
    > #
    > #server {
    > #    listen       443 ssl;
    > #    server_name  localhost;
    >
    > #    ssl_certificate      cert.pem;
    > #    ssl_certificate_key  cert.key;
    >
    > #    ssl_session_cache    shared:SSL:1m;
    > #    ssl_session_timeout  5m;
    >
    > #    ssl_ciphers  HIGH:!aNULL:!MD5;
    > #    ssl_prefer_server_ciphers  on;
    >
    > #    location / {
    > #        root   html;
    > #        index  index.html index.htm;
    > #    }
    > #}

The modified version results in the exact same configuration, but I find it way more convenient. Let's see how convenient with the below simple "Hello world!" example.

Check the Nginx config

Simply run:

# nginx -t

Service

After configuring Nginx, make sure to start the nginx service:

# rc-service nginx start
# sv start nginx
# service nginx start
# systemctl start nginx

And don't forget to enable the nginx service on boot:

# rc-update add nginx default

Depending on your runit implementation, either run:

# ln -s /etc/runit/sv/nginx /service
or run:
# ln -s /etc/runit/sv/nginx /var/service
or run:
# ln -s /etc/runit/sv/nginx /run/runit/service

# chkconfig nginx on
# systemctl enable nginx

Tip

You can find more nginx service related commands here (note that the nginx service name simply is nginx).


Hello world example

# mkdir -p /var/www/hello-world
# echo "Hello world!" > /var/www/hello-world/index.html

# vi /etc/nginx/nginx.conf
    > ...
    >
  ~ >     #include /etc/nginx/conf.d/default_nginx.conf;
  + >     include /etc/nginx/conf.d/hello_world.conf;
    > }

# vi /etc/nginx/conf.d/hello_world.conf
  + > server {
  + >     listen 4242;
  + >     listen [::]:4242;
  + >     server_name  localhost;
  + >
  + >     location / {
  + >         root   /var/www/hello-world;
  + >         index  index.html;
  + >     }
  + > }

# nginx -t

Then start (or reload if already started) the nginx service.

Now you should get the following result:

$ curl http://localhost:4242 # should print "Hello world!"

Easy.


Reverse proxy

Reference(s)

Let's say that the previous "Hello world!" example is still hosted and running on your "host server" (e.g. with IP 123.123.42.42). And now you want to set up a reverse proxy on your "proxy server" (through the 4242 port for example).

On your proxy server, you will have to install Nginx like described above. Then you will just have to configure it like so:

# vi /etc/nginx/nginx.conf
    > ...
  + >     include /etc/nginx/conf.d/hello_world_proxy.conf;
    > }

# vi /etc/nginx/conf.d/hello_world_proxy.conf
  + > server {
  + >     listen 80;
  + >     listen [::]:80;
  + >     server_name localhost;
  + >
  + >     location / {
  + >         proxy_pass http://123.123.42.42:4242;
  + >     }
  + > }

# nginx -t

Don't forget to start (or reload if already started) the nginx service.

If your proxy server IP address is 111.222.1.2, then you should get the following result:

$ curl http://111.222.1.2 # should print "Hello world!"

Warning

You should probably check the NAT table of the router in front of your "host server", in order to open the 4242 port of your router and redirect it to the 4242 port of your "host server". When doing so, in order to have a safer reverse proxy config, you might also want to only allow traffic originating from the IP address of your reverse proxy.

In addition, you should make sure that the firewall (if any) of your "host server" is not blocking the 4242 port.


Hosting multiple websites

Now lets say you want to serve multiple websites, in addition to the previous "Hello world!" site you also want a "Goodbye world!" site at the same time:

# mkdir -p /var/www/goodbye-world
# echo "Goodbye world!" > /var/www/goodbye-world/index.html

# vi /etc/nginx/nginx.conf
    > ...
    >
    >     #include /etc/nginx/conf.d/default_nginx.conf;
    >     include /etc/nginx/conf.d/hello_world.conf;
  + >     include /etc/nginx/conf.d/goodbye_world.conf;
    > }

# vi /etc/nginx/conf.d/goodbye_world.conf
  + > server {
  + >     listen 4243;
  + >     listen [::]:4243;
  + >     server_name  localhost;
  + >
  + >     location / {
  + >         root   /var/www/goodbye-world;
  + >         index  index.html;
  + >     }
  + > }

# nginx -t

Then start (or reload if already started) the nginx service.

Now you should get the following result:

$ curl http://localhost:4243 # should print "Goodbye world!"


Password protection

Reference(s)

Create an .htpasswd file containing a user name and the salted hash of its password (thanks to the Perl crypt("your-password","your-salt") function):

$ hash=$(perl -le 'print crypt("GPpoGPAX8H3A8N28", "ZXYv7trSx7zxvXib")')
$ echo "username:$hash" | sudo tee /path/to/.htpasswd

Now you can password protect a server, e.g.:

server {
    ...

    auth_basic "Login";
    auth_basic_user_file /path/to/.htpasswd;

    ...
}

You can also password protect a single location, e.g.:

server {
    ...

    location /your-location {
        auth_basic "Login";
        auth_basic_user_file /path/to/.htpasswd;

        ...
    }

    ...
}


HTTPS digital certificate with Certbot

Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt - an open certificate authority launched by the EFF, Mozilla, and others - and deploys it to a web server.

Reference(s)

Prerequisite(s)

  • IPv6 must be enabled on your server!
  • Install certbot and certbot-nginx:
$ sudo emerge -a app-crypt/certbot app-crypt/certbot-nginx
$ sudo pacman -S certbot certbot-nginx
$ sudo apt install python-certbot python-certbot-nginx
$ sudo yum install certbot python-certbot-nginx
$ sudo dnf install certbot python-certbot-nginx
  • Get a certificate for your domain:

A wildcard certificate is a certificate that includes one or more names starting with *. Browsers will accept any label in place of the asterisk (*). For example, a certificate for *.example.com will be valid for www.example.com, mail.example.com, hello.example.com, and goodbye.example.com.

However, a wildcard certificate including only the name *.example.com will not be valid for example.com: the substituted label can not be empty. If you want the certificate to be valid for example.com, you also need to include example.com (i.e. without the *. part) on the certificate.

Additionally, the asterisk can only be substituted by a single label and not by multiple labels. For example, the name hello.goodbye.example.com will not be covered by a certificate including only the name *.example.com. It will be covered however, by *.goodbye.example.com. Note that a wildcard name can not contain multiple asterisks. For example, *.*.example.com is not valid.

If you’d like to obtain a wildcard certificate with Certbot (i.e. from Let’s Encrypt), you will have to install one of the Certbot’s DNS plugins : https://certbot.eff.org/docs/using.html#dns-plugins

For example, here is how I obtained a wildcard certificate for *.stephane.plus (and also stephane.plus), with the certbot-dns-ovh DNS plugin:

$ certbot plugins # verify that the Certbot plugins were installed correctly
$ sudo vi /etc/nginx/.ovh.ini # or `.linode.ini`, `cloudflare.ini` or whatever
    > # OVH API credentials used by Certbot
    > dns_ovh_endpoint = ovh-eu # or `ovh-na` if your VPS is in North America and not Europe
    > dns_ovh_application_key = MDAwMDAwMDAwMDAw
    > dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
    > dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw

$ sudo chmod 600 /etc/nginx/.ovh.ini

$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.ori_back # backup your original conf file
$ sudo certbot --installer nginx --authenticator dns-ovh --dns-ovh-credentials /etc/nginx/.ovh.ini --dns-ovh-propagation-seconds 60 -d *.stephane.plus -d stephane.plus

$ sudo certbot certificates # review certificates list

$ sudo certbot renew --dry-run --installer nginx --authenticator dns-ovh --dns-ovh-credentials /etc/nginx/.ovh.ini --dns-ovh-propagation-seconds 60

$ sudo crontab -e
    > # * * * * *  command_or_script_to_execute
    > # - - - - -
    > # | | | | |
    > # | | | | +- day of week (0 - 7) (where sunday is 0 and 7)
    > # | | | +--- month (1 - 12)
    > # | | +----- day (1 - 31)
    > # | +------- hour (0 - 23)
    > # +--------- minute (0 - 59)
    >
    > 0 0 1 * * certbot renew --installer nginx --authenticator dns-ovh --dns-ovh-credentials /etc/nginx/.ovh.ini --dns-ovh-propagation-seconds 60
    > ...

$ certbot plugins # verify that plugins were installed correctly
$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.ori_back # backup your original conf file
$ certbot --nginx -d example.com
$ certbot certificates # review certificates list
$ sudo certbot renew --dry-run --nginx
$ sudo crontab -e
    > # * * * * *  command_or_script_to_execute
    > # - - - - -
    > # | | | | |
    > # | | | | +- day of week (0 - 7) (where sunday is 0 and 7)
    > # | | | +--- month (1 - 12)
    > # | | +----- day (1 - 31)
    > # | +------- hour (0 - 23)
    > # +--------- minute (0 - 59)
    >
    > 0 0 1 * * certbot renew --nginx
    > ...

Tip

Certbot certificates will be saved in /etc/letsencrypt/live/.

Certbot tips and tricks

  • Remove all certificates and re-generate them:

    • First, delete your generated certificates:

      $ sudo certbot delete # interactively select the certificates to delete
      

    • Then, remove the Certbot generated web server’s configuration leftover lines from your nginx.conf file (i.e. remove the "# managed by Certbot" lines) and replace them by your previous backup:

      $ cd /etc/nginx
      
      $ sudo diff nginx.conf nginx.conf.ori_back # review differences between original backup conf and current Certbot conf
      $ sudo vi nginx.conf.ori_back # optionnaly edit the original backup conf in order to back-port eventual changes (DO NOT copy any "# managed by Certbot" lines here!)
      
      $ sudo mv nginx.conf nginx.conf.certbot_back_$(date +"%Y-%m-%d_%T") # backup current Certbot conf, just in case
      $ sudo cp nginx.conf.ori_back nginx.conf # restore original backup
      
      $ sudo systemctl restart nginx.service
      $ sudo systemctl status nginx.service
      

    • Finally, re-generate your certificates. For example, here is how I re-generated a wildcard certificate for *.stephane.plus (and also stephane.plus), with the certbot-dns-ovh DNS plugin:

      $ certbot plugins # verify that the Certbot plugins were installed correctly
      $ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.ori_back # backup your original conf file
      $ sudo certbot --installer nginx --authenticator dns-ovh --dns-ovh-credentials /etc/nginx/.ovh.ini --dns-ovh-propagation-seconds 60 -d *.stephane.plus -d stephane.plus
      
      $ sudo certbot certificates # review certificates list
      
      $ sudo certbot renew --dry-run --installer nginx --authenticator dns-ovh --dns-ovh-credentials /etc/nginx/.ovh.ini --dns-ovh-propagation-seconds 60
      

If this cheat sheet has been useful to you, then please consider leaving a star here.