A Guide to running a Reverse proxy for HTTP(S), SSH and MySQL/MariaDB using NGINX

This guide will walk you through the installation and configuration of NGINX to allow for the running of multiple physical servers, virtual machines or a combination of both behind a single public-facing IP Address. You may choose to run a number of web servers on virtual machines and administer them locally or you may be required to make use of remote access tools such as SSH to each of the hosts. For example, if local access will be unavailable outside of normal business hours. This guide can facilitate both scenarios.

The configurations shown here would be best suited to a home lab or small business network that has limitations on the available public IP Addresses. There would be little cause if any, to run a configuration such as this, when you have multiple servers or virtual machines rented from a hosting service, you will be assigned a public facing IP Address for each server or host anyway.

I will show you how to install NGINX and make configurations that will enable the server to act as a Reverse proxy for HTTP(S), SSH, FTP and MySQL/MariaDB. I presume that for the NGINX host server you have: local access, a fresh installation of Ubuntu 18.04 and that you opted to install the SSH server during the installation steps of the Ubuntu server.

This configuration works well for me, but please understand that I cannot make any guarantee that this will work for you. Of course, if you find something wrong, let me know so that it can be corrected. Please make sure that you read the entire guide before you begin, there is one part (streams) where I show two ways to manage it.

Getting Started

In this guide, I will be using the following hostnames and IP Addresses.

rproxy.example.com  192.168.1.1
web1.example.com    192.168.1.2
db1.exmple.com      192.168.1.3

You should have a non-root user account on the server for a standard Ubuntu 18.04 server installation which you created during installation. Begin by logging into the server where you will install NGINX with that user. As this is most likely a local server you may need to log in on the server directly the first time to configure the SSH server. You will, of course, need a keyboard and monitor attached to the server to do this.

Note: If like me you use virtualization software such as VMWare that includes a browser interface, then you should have a console within that system and can perform this step without "direct" access. You could attempt to perform this entire configuration within that console, however, I have found that some features such as copy and paste did not work in the browser-based console although this could be browser-specific so it may be worth trying to see if you can.

Preparing the host server

In your console shell (browser or directly connected)

sudo nano /etc/ssh/sshd_config

Un-comment the lines: Port change the Port number to something like 23456, ListenAddress and change that to 0.0.0.0. For those that may be unfamiliar with nano, Press CTRL + X, type y, and then press enter. This will save and close a file, if no changes were made to the file, CTRL + x will close the file without prompting to save. You will be returned to the command prompt.

I will not dig into the rest of the settings within this file because this is already a considerably lengthy guide and there are many guides that will show you which settings you should change for a number of things, such as using SSH keys and allowing root SSH login. You can also find those guides right here on the HowtoForge website.

With the changes complete you will need to restart the ssh server so that the changes will take effect. Your current login is unaffected by this restart.

systemctl restart ssh

Verify that you are able to log in using SSH from a terminal on another computer within your local network.

ssh [email protected] -p23456

Keep this terminal open after successfully logging in using SSH and log out of the console/server. You no longer need to use it for the remainder of this guide.

From this point forward you will be running root-level commands from your terminal. The next command will eliminate the need to prepend subsequent commands with sudo.

sudo -s

Update the Apt package database and upgrade Ubuntu to ensure that you have the most recent packages installed.

apt update && apt -y upgrade

Should you see anything during the upgrade that reports new kernels being installed then you should reboot once apt has finished upgrading to ensure that you are working on a fully updated system.

Setting the hostname of the reverse proxy server.

hostnamectl set-hostname rproxy.example.com

If you are running a virtual server you may have a file named cloud.cfg that needs modifying to preserve the hostname that is set here. The following command will either show a file with content or an empty page. If you see an empty page, you can simply CTRL + x and skip this step as there is nothing for you to do.

nano /etc/cloud/cloud.cfg

Change the preserve hostname line to true and close/save the file.

If your system is currently local only you will need to show this server where your other servers/virtual hosts are.
nano /etc/hosts

The hosts file will look something like this after you make the changes, the IP Addresses and Hosts should match your own infrastructure.

127.0.0.1 localhost
127.0.1.1  rproxy.example.com
192.168.1.2    web1.example.com
192.168.1.3    db1.example.com

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Installing NGINX

apt install -y nginx

After installation you should check your NGINX version, it is paramount that you have a version 1.9 or higher to allow you to Reverse proxy for SSH and MySQL/MariaDB.

nginx -v

As you can see, I have NGINX version 1.14 installed which is the default in Ubuntu 18.04 (10th Oct 2019)

nginx version: nginx/1.14.0 (Ubuntu)

Preparing NGINX to function as a Reverse Proxy

With this configuration, you are not going to be serving any websites directly from the Reverse proxy host server. You will create a new directory structure under /etc/nginx/. This will preserve the default NGINX configurations should you want to revert these changes later or decide that you actually do also want to serve websites directly from this host. It is possible to run the default configuration along with these Reverse proxy configurations however if Apache2 will be on the same server, it will need alternate ports to listen on and you will still need to Reverse proxy the websites that this instance of Apache2 serves.

Build the Reverse proxy directory structure

cd /etc/nginx && mkdir rproxy && cd rproxy && mkdir http http/available http/enabled stream stream/available stream/enabled

Now that you have the structure in place, you can proceed with creating the configuration files. I use nano but you can use the editor you feel comfortable with. Nano will create/update the files on save.

Before you proceed, open an empty document on your computer or get a pen and paper to note down the ports you configure.

Configuring web server reverse proxies (http)

Create the http configuration file(s) for the website(s) adjusting accordingly

nano http/available/example.com.conf

Copy the server block into the page opened in the terminal with nano adjusting accordingly.

# Note down ports 80 and 443

server {
    server_name example.com www.example.com;
    listen 80;
    set $upstream 192.168.1.2;
    location / {
         proxy_pass_header Authorization;
         proxy_pass http://$upstream;
         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_http_version 1.1;
         proxy_set_header Connection "";
         proxy_buffering off;
         client_max_body_size 0;
         proxy_read_timeout 10000s;
         proxy_redirect off;
     }
}

Configuring SSH, MySQL/MariaDB reverse proxies (stream)

Before you proceed, decide which you want to utilize, per host or per service. Per host, you will create a configuration for each host which might be useful to quickly change a single hosts settings. Per service, you will have the service ports for all servers in a file for each, SSH, MySQL/MariaDB and FTP.

Using per service configurations

Add the SSH configurations.

nano stream/available/ssh.conf
# Note down the listen ports

upstream web1-ssh {
  server 192.168.1.2:22;
}

server {
  listen 22002;
  proxy_pass web1-ssh;
}

upstream db1-ssh {
  server 192.168.1.3:22;
}

server {
  listen 22003;
  proxy_pass db1-ssh;
}

# Add as many upstream and server block pairs as you will need for your remote accessed SSH servers.

Add the MySQL/MariaDB configurations.

nano stream/available/db.conf
# Note down the listen ports

upsteam db1-mysql {
  server 192.168.1.3:3306;
}

server {
  listen 33063;
  proxy_pass db1-mysql;
}

# Add as many upstream/server block pairs as you will need for your remote accessed MySQL/MariaDB servers to this file.

Now create the FTP Reverse proxy configurations.

nano stream/available/ftp.conf
upstream web1-ftp {
  server 192.168.1.3:21
}

server {
  listen 21002;
  proxy_pass web1-ftp;
}

# Add as many upstream/server block pairs as you will need for your remote accessed FTP servers.

Using Per host configuration files

nano /etc/nginx/rproxy/stream/available/web1.example.com.conf
# Note down the listen ports

upstream web1-ssh {
  server 192.168.1.3:22;
}

server {
  listen 22002;
  proxy_pass web-ssh;
}

Creating the host file for db1.example.com

nano /etc/nginx/rproxy/stream/available/db1.example.com.conf
# Note down the listen ports

upsteam db1-mysql {
  server 192.168.1.3:3306;
}

server {
  listen 33063;
  proxy_pass db1-mysql;
}

upstream db1-ssh {
  server 192.168.1.3:22;
}

server {
  listen 22003;
  proxy_pass db1-ssh;
}

As you can see, its a little unorthodox. You are using public ports in a non-standard way, choosing the ports that you need and then pointing them to NGINX. This would be normal except that you are now using a different port for each service on each server that you want to access remotely. This means, using SSH as an example, a different port number for each SSH enabled host 22 222 2222 22222, for example, would point to port 22 on four different servers or virtual machines.

This is not the case for NGINX to Reverse proxy for websites, as long as NGINX has a server configuration defined for a website it will work correctly with just ports 80 and 443 forwarded to it.

At this point you've probably realized that you could simply use the HTTP steps and skip the stream steps, and instead forwarding multiple ports for the multiple services to the appropriate server/IP Address. Indeed, this can be done. However, it would add another layer of complexity and become difficult to maintain as the number of servers increase because you may need to change the default ports on each server for ssh, mysql and ftp. This configuration is already complex, still it could be done if you wanted to.

Using a reverse proxy for these services in the way that I have shown you decrease the complexity significantly by providing a single place to make these configuration changes and you would not need to make changes to the ports across your entire infrastructure.

As an added bonus to this configuration, the other servers in your infrastructure only need to listen on local interfaces and default ports if thats what you prefer. Should you be locally managing, you can use the local IP Addresses and default service ports to access the required services so you won't need to reference your notes to remember the correct ports, you only need to know the IP Address and login credentials.

Bringing it all together

To begin using the NGINX Reverse proxy configurations you will need to make some edits to the main configuration file. Comment out the current include line in the http block (if you are not serving websites directly from NGINX also).

cd /etc/nginx && nano nginx.conf

Note the highlighted parts below to determine what to needs changing.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

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;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascri$

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
#       include /etc/nginx/sites-enabled/*;

        # Reverse proxy http configuration files.
        include /etc/nginx/rproxy/http/enabled/*.conf;
}

stream {

    # Reverse proxy stream configuration files.
    include /etc/nginx/rproxy/streams/enabled/*.conf;
}


#mail {
#       # See sample authentication script at:
#       # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#       # auth_http localhost/auth.php;
#       # pop3_capabilities "TOP" "USER";
#       # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#       server {
#               listen     localhost:110;
#               protocol   pop3;
#               proxy      on;
#       }
# 
#       server {
#               listen     localhost:143;
#               protocol   imap;
#               proxy      on;
#       }
#}
    

Activate the reverse proxy configurations.

First, enable all of the http configurations

ln -s /etc/nginx/rproxy/http/available/*.conf /etc/nginx/rproxy/http/enabled

Enable all of the stream configurations

ln -s /etc/nginx/rproxy/stream/available/*.conf /etc/nginx/rproxy/stream/enabled

Perform a test to check that the configuration of NGINX as a Reverse proxy is correct.

nginx -T

In the output, you should see a success message along with all of the custom configurations that you have made previously.

Restart NGINX to put the Reverse proxy configurations into action.

systemctl restart nginx

Check to ensure that NGINX is listening on all of the configured ports. verify against your notes that all of the ports are shown in the results.

netstat -tulpn | grep nginx

The output should look something like this

tcp        0      0 0.0.0.0:22           0.0.0.0:*               LISTEN      4964/nginx: master  
tcp        0      0 0.0.0.0:22002           0.0.0.0:*               LISTEN      4964/nginx: master  
tcp        0      0 0.0.0.0:22003           0.0.0.0:*               LISTEN      4964/nginx: master
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4964/nginx: master  
tcp        0      0 0.0.0.0:33062           0.0.0.0:*               LISTEN      4964/nginx: master  
tcp        0      0 0.0.0.0:33063           0.0.0.0:*               LISTEN      4964/nginx: master  
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      4964/nginx: master  
    

Opening the server for traffic

Now that NGINX is listening and ready to act as Reverse proxy for all of the connections we want to allow traffic in, temporarily disable UFW while going through the next steps. It will make troubleshooting easier if things are not working properly.

ufw disable

Forwarding Ports

Unfortunately, I am unable to give you guidance here. You will need to look to your router's manual or look up your router online to learn how to do this. In summary, however, you want to forward each of the ports that NGINX is listening on to the NGINX host machine. In my specific router, I am able to set up custom "applications".

This allows me to assign any number of unassigned ports or port ranges to the custom application which will then be assigned to either a LAN IP Address or to a specific connected device identified by its hostname. In any case, this means that I am able to switch all of the ports assigned to that custom application to any host on my network without having to delete all of the ports and reassign them. This is an excellent option to have and I highly recommend that you choose a router that supports it.

This small but incredibly useful feature in my routers firewall prompted me to install a second Reverse proxy mirroring the configurations of my active Reverse proxy. I have it powered off and I'm able to boot up and have the ports switched to it in under 3 minutes of discovery. I imagine that there are professional hosting companies that would struggle to meet that turnaround time for disaster recovery and all of this came about because of a desire to run multiple servers on a home network.

Free Letsencrypt SSL for the Reverse proxy Server

Install Certbot and Certbot NGINX plugin. This step is performed here because you cannot create a certificate until you have functional DNS for all of the (sub.)domain names listed in a certificate and are able to make a connection to the domain over HTTP. By completing this after you have forwarded the ports to the reverse proxy it also acts as an extra test to your configurations. If the cert fails because the domain cannot be reached you will see a meaningful error notice in the output.

To ensure that you are getting the latest certbot version add the certbot repository before installing. Ubuntu repositories are often a version or more behind a software version and you really want the latest stavle versions of all your software if you can get them.

add-apt-repository ppa:certbot/certbot
apt install -y certbot python-certbot-plugin

With certbot and the certbot nginx plugin installed you can now create the certificates for NGINX.

This command should be repeated for all of the domains and subdomains for which you want to provide SSL. If this is the first time running certbot, you will need to accept the terms and conditions.

certbot --nginx -d example.com -d www.example.com

Note that if you have a working server setup with SSL on upstream host, you will need to ensure that the options selected here match those on the host server, specifically, if you redirect to https on the upstream server, you should so that here too.

For clarity, example.com is existing on another server, and I selected to redirect http to https within ISPConfig, so for that reason, I select to do that here. The configuration file will be updated and I now see that Certbot has added some configurations of its own.

Checking that things work.

Now that you have traffic able to flow to your Reverse proxy, you should verify that everything is working as intended. Check that websites are functioning properly, perform SSH, FTP and MySQL/MariaDB connections and tasks. Once you are satisfied that everything is operating as it should be then you will enable UFW and add rules to allow each of the ports.

Securing the Reverse Proxy server

ufw enable

You will want to allow, 80 and 443 from anywhere. and likely restrict SSH, FTP and MySQL/MariaDB to an IP Address or hostname. You can comment the rules to quickly identify which service/server you have assigned the port to.

ufw allow 80
ufw allow 443
ufw allow from 1.2.3.4 to any port 22002 comment 'web1 SSH'
ufw allow from somehost.domain.com to any port 33061 comment 'db1 MySQL/MariaDB'

ufw reload
ufw status numbered

Updating Apache2

When running behind a reverse proxy, Apache2 log files will record the IP Address of the Reverse proxy server instead of the IP Address of the website visitor. To reinstate the normal IP Address logging to Apache2 a module is available to correct this behavior.

Complete the following steps on each web server with an Apache2 instance installed.

sudo apt install -y libapache2-mod-rpaf

To ensure that Apache2 is now going to record the correct IP Addresses make a small modification to the rpaf.conf file. Ubuntu 18.04 has already created the file for us, we just need to edit it changing the highlighted IP Address to that of the NGINX Reverse proxy host.

nano /etc/apache2/mods-available/rpaf.conf
<ifmodule rpaf_module="">
    RPAFenable On

    # When enabled, take the incoming X-Host header and
    # update the virtualhost settings accordingly:
    RPAFsethostname On

    # Define which IP's are your frontend proxies that sends
    # the correct X-Forwarded-For headers:
    RPAFproxy_ips 127.0.0.1 ::1

    # Change the header name to parse from the default
    # X-Forwarded-For to something of your choice:
#   RPAFheader X-Real-IP
</ifmodule>

Final Notes

NGINX and Apache2 in the same host

No two services can listen on the same port within a server or virtual machine. If NGINX is installed on the same server or virtual machine as an Apache2 web server, you will need to change the port that Apache2 listens on. NGINX requires ports 80 and 443 to perform its HTTP(S) functions as they are the default ports for HTTP and HTTPS.

Refer back to the http section of this guide and add Reverse proxy configurations for websites served by this Apache2 instance in the same way as that of other servers in the network.

Changing the Apache2 listen port

If you have a server management system installed such as ISPConfig, this system handles the Apache2 vhost files so you should research how to change the ports that Apache2 is listening on. Search the ISPConfig forums and then make those adjustments as necessary. Otherwise, you should look to the Ubuntu forums or Apache2 website for how to perform these changes.

Note: Apache2 ports do not need to be altered when it is the only web server installed on server or virtual machine.
Share this page:

7 Comment(s)