Configure NGINX as a Secure Reverse Proxy

NGINX (pronounced as engine-x) is a versatile (reverse) proxy service for Linux which can be used for many purposes. This post gives a relative small and easy example that I use at home for accessing insecure web services in my home. These are:

  • Domoticz
    Free and opensource Domotica software
  • SabNZBd
    Free and opensource software for downloading binaries from usenet. Available for multiple operating systems
  • Sonarr
    (former NZBDrone) is a so-called PVR (personal video recorder) for Usenet users, which checks multiple RSS feeds (also called Indexer) for new episodes of the shows you're following.

These services run on different platforms and are not protected by username/password or encryption. Something that's not done if you want to access this over the Internet.
To get secure access to these services you might want to use a VPN solution into your home, but you can also achieve this by using a reverse proxy that 'protects' these services.

I run my NGINX reverse proxy on Ubuntu Linux, but it will also run on the average Raspberry Pi.

The following diagram gives an overview of the setup (it misses the domoticz service):

NGINX Reverse Proxy Setup

The goal I want to achieve is the following:

  • Access the inside services from the Internet by using 1 external IP address (or hostname).
  • All communication needs to be secured by SSL.
  • Access is allowed only by providing the correct username and password.

I won't go into detail about the NGINX proxy installation. Same goes for the configuration of the internet gateway/modem, and the generation of the certificate used for the SSL connection. There are more than enough resources available online that covers these topics.

To be on the safe side, I suggest that you test the functionality from the inside. This way, nobody might end up on your inside network in the case you missed a piece of configuration. So the port-forwarding in the Internet modem is done last.

Installation and configuration of NGINX

To install NGINX on Ubuntu Linux (also valid for the Raspberry Pi) type the following:

sudo install nginx

After the Installation you need to edit the config (nginx.conf) file located in /etc/nginx/. I made sure I made a backup of the original file. I also made sure that the browser is always using a secure connection by redirecting regular http (port 80) traffic to https (port 443).

The SSL settings are generated online to make sure the security for SSL is optimal. I would suggest the usage of a Let's Encrypt certificate for the SSL connection. More info on that can be found online.

The authentication part requires a separate u/p file on the filesystem. This can be created by using htpasswd, which is a part of the apache2-utils package. This can be installed via:

sudo apt-get install apache2-utils

To use Sonarr with a reverse proxy, you need to make a change to the Sonarr configuration. This is done under 'Settings -> General -> URL Base'. The (new) value for that setting is:

/sonarr

Reverse Proxy Settings for Sonarr

All these configuration settings are easily identified in the configuration file below.

The NGINX Config File

user www-data;
worker_processes auto;
pid /run/nginx.pid;
 
events {
     worker_connections 768;
     # multi_accept on;
}
 
http {
 
        ##
        # Basic Settings
        ##
 
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;
 
        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;
 
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
 
        ##
        # Logging Settings
        ##
 
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
 
     server {
          listen 80 default_server;
          listen [::]:80 default_server;
 
          # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
          return 301 https://$host$request_uri;
     }
 
        server {
          ####################################################################
          # SSL Stuff
          # https://mozilla.github.io/server-side-tls/ssl-config-generator/
          ####################################################################
          listen 443 ssl http2;
          listen [::]:443 ssl http2;
 
          # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
          ssl_certificate           /etc/nginx/cert.crt;
          ssl_certificate_key       /etc/nginx/cert.key;
          ssl_session_timeout 1d;
          ssl_session_cache shared:SSL:50m;
          ssl_session_tickets off;
 
 
          # modern configuration. tweak to your needs.
          ssl_protocols TLSv1.2;
          ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
          ssl_prefer_server_ciphers on;
 
          # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
          add_header Strict-Transport-Security max-age=15768000;
 
          # OCSP Stapling ---
          # fetch OCSP records from URL in ssl_certificate and cache them
          ssl_stapling on;
          ssl_stapling_verify on;
         
          ## verify chain of trust of OCSP response using Root CA and Intermediate certs
#         ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
 
 
          ##########################################################
          # AUTHENTICATION
          ##########################################################
          # sudo apt-get install apache2-utils
          # sudo htpasswd -c /etc/nginx/auth.d/auth.pwd <username>
          ##########################################################
          auth_basic "Restricted Content";
          auth_basic_user_file /etc/nginx/auth.d/auth.pwd;
          # allow client from the subnet without the u/p requirement:
#         allow 192.168.0.0/24;
#         deny all;
#         satisfy any;
 
          ##########################################################
          # REVERSE PROXY LOCATION SETTINGS
          ##########################################################
          location /domo/ {
                proxy_pass http://192.168.0.25:8080/;
                proxy_set_header     Host            $host ;
                proxy_set_header     X-Real-IP       $remote_addr;
                proxy_set_header        X-Forwarded-Proto $scheme;
                add_header              Front-End-Https        on;
                proxy_redirect       off;
          }
 
          ##########################################################
          # Sonarr needs additional config regarding reverse proxy
          # Settings -> General -> URL Base: /sonarr
          ##########################################################
          location /sonarr/ {
                proxy_pass http://192.168.0.20:8989;
                proxy_set_header     Host            $host;
                proxy_set_header     X-Real-IP       $remote_addr;
                proxy_set_header     X-Forwarded-For      $proxy_add_x_forwarded_for;
          }
 
          location /sabnzbd/ {
                proxy_pass http://192.168.0.15:8080;
                proxy_set_header     Host            $host ;
                proxy_set_header     X-Real-IP       $remote_addr;
                proxy_set_header        X-Forwarded-Proto $scheme;
                add_header              Front-End-Https        on;
                proxy_redirect       off;
          }
     }
 
}

The config file above is complete, and it works just fine, but it's not the NGINX way to do stuff. As with the configurations of websites you can include the different locations from an external source.

To accomplish this I created the directories locations-available and locations-enabled;

sudo mkdir /etc/nginx/locations-available
sudo mkdir /etc/nginx/locations-enabled

In the directory locations-available I created the files representing the actual locations from the initial config file;

/etc/nginx/locations-available/domoticz.conf

location /domo/ {
    proxy_pass http://192.168.0.25:8080/;
    proxy_set_header Host $host ;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    add_header Front-End-Https on;
    proxy_redirect off;
    }

/etc/nginx/locations-available/sonarr.conf

location /sonarr/ {
    proxy_pass http://192.168.0.20:8989;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

/etc/nginx/locations-available/sabnzbd.conf

location /sabnzbd/ {
    proxy_pass http://192.168.0.15:8080;
    proxy_set_header Host $host ;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    add_header Front-End-Https on;
    proxy_redirect off;
    }

Next is to enable these available sites by creating symbolic links to the locations-enabled directory:

sudo ln -s /etc/nginx/locations-available/domoticz.conf /etc/nginx/locations-enabled/domoticz.conf
sudo ln -s /etc/nginx/locations-available/sabnzbd.conf /etc/nginx/locations-enabled/sabnzbd.conf
sudo ln -s /etc/nginx/locations-available/sonarr.conf /etc/nginx/locations-enabled/sonarr.conf

Now that the symbolic links are in place we can remove the actual location references in the original nginx config file and replace it with an include statement (include /etc/nginx/locations-enabled/*.conf). The thing to look out for is that this statement needs to be in the correct location in the config file. In this case, it needs to be nested under the server stanza. The final result is:

user www-data;
worker_processes auto;
pid /run/nginx.pid;
 
events {
    worker_connections 768;
    # multi_accept on;
}
 
http {
    ##
    # Basic Settings
    ##
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;
 
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;
 
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
 
    ##
    # Logging Settings
    ##
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
 
    server {
        listen 80 default_server;
        listen [::]:80 default_server;
 
        # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
        return 301 https://$host$request_uri;
     }
 
        server {
          ####################################################################
        # SSL Stuff
        # https://mozilla.github.io/server-side-tls/ssl-config-generator/
          ####################################################################
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
 
        # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
        ssl_certificate           /etc/nginx/cert.crt;
        ssl_certificate_key       /etc/nginx/cert.key;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;
 
 
        # modern configuration. tweak to your needs.
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
        ssl_prefer_server_ciphers on;
 
        # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
        add_header Strict-Transport-Security max-age=15768000;
 
        # OCSP Stapling ---
        # fetch OCSP records from URL in ssl_certificate and cache them
        ssl_stapling on;
        ssl_stapling_verify on;
         
        ## verify chain of trust of OCSP response using Root CA and Intermediate certs
#       ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

        ##########################################################
        # AUTHENTICATION
        ##########################################################
        # sudo apt-get install apache2-utils
        # sudo htpasswd -c /etc/nginx/auth.d/auth.pwd <username>
        ##########################################################
        auth_basic "Restricted Content";
        auth_basic_user_file /etc/nginx/auth.d/auth.pwd;
        # allow client from the subnet without the u/p requirement:
#       allow 192.168.0.0/24;
#       deny all;
#       satisfy any;

        ##########################################################
        # REVERSE PROXY LOCATION SETTINGS
        ##########################################################
        include /etc/nginx/locations-enabled/*.conf
     }
 
}

The ngynx configuration can be checked by the following command:

sudo nginx -t

If everything checks out, the service can be started (sudo service nginx start), or restarted (sudo service nginx restart), and the individual website should be accessible through the reverse proxy:

  • Domoticz: https://192.168.0.10/domo/
  • SabNZBd: https://192.168.0.10/sabnzbd/
  • sonarr: https://192.168.0.10/sonarr/

The first time you access one of these links you are presented with a login screen to identify yourself with a username and password (as created with the htpasswd tool. When these are entered correctly you should gain access to your internal services.

When everything is working you can enable the port-forwarding on your Internet modem by forwarding traffic destined for port 443 to the nginx server (also port 443).

Posted on January 29, 2017 and filed under Internet, Security, Tips'n Tricks.