Tim's Blog

Information, Technology, Security, and other stuff.

Using Nginx as a Reverse Proxy for Multiple Sites

Published 2016-02-12

I'm running a few services now on my home network, including:

  • Plex
  • Sickbeard
  • CouchPotato
  • Headphones
  • Confluence (as my wiki)
  • Kolab (as my email server)

Instead of hitting the default URLs of these products, which often contain ports individual to each server (e.g. CouchPotato running on 5050, Plex on 32400), I wanted to have a single reverse proxy running that would serve up each site on port 443.

But instead of having each site as a directory under one site (e.g. site.example.com/plex, site.example.com/sickbeard), I wanted to have different DNS names for each service pointing to the same reverse proxy, but forwarded to the relevant service I'm trying to hit.

So I first created some CNAMEs in DNS (pointing to my nginx server), as follows:

  • plex.example.com
  • sickbeard.example.com
  • couchpotato.example.com
  • headphones.example.com
  • wiki.example.com (to be confluence)
  • mail.example.com (to be kolab)

Then, because kolab uses Apache by default, I just changed httpd to listen on port 4000 instead so I could install nginx. I installed the bog standard nginx from the EPEL repository (yum install epel-release -y && yum install nginx -y), so I haven't done anything special on my machine.

Then I set up the following config in /etc/nginx/conf.d/default.conf:

ssl_certificate /etc/pki/tls/certs/localhost.crt;
ssl_certificate_key /etc/pki/tls/private/localhost.key;

server {
    listen 80;
    server_name *.example.com;
    rewrite ^ https://$host$request_uri? permanent;
}

server {
    listen 443 ssl;
    server_name wiki.example.com;
    ssl on;

    location / {
        proxy_pass              http://server02.example.com:8090;
    }
}

server {
    listen 443 ssl;
    server_name sickbeard.example.com;
    ssl on;

    location / {
        proxy_pass              http://server01.example.com:8081;
    }
}

server {
    listen 443 ssl;
    server_name couchpotato.example.com;
    ssl on;

    location / {
        proxy_pass              http://server01.example.com:5050;
    }
}

server {
    listen 443 ssl;
    server_name plex.example.com;
    ssl on;

    location / {
        proxy_pass              http://server01.example.com:32400;
    }
}

server {
    listen 443 ssl;
    server_name mail.example.com;
    ssl on;

    location / {
        proxy_pass              http://server02.example.com:4000;
    }
}

server {
    listen 443 ssl;
    server_name headphones.example.com;
    ssl on;

    location / {
        proxy_pass              http://server01.example.com:8181;
    }
}

You might've noticed I've got services spread across server01 and server02. I'm planning to put them all on the same box soon to reduce the number of machines running in my network, so in that case all I need to do is update this config file to point to their new locations.

In addition, my reverse proxy is TLS enabled but the services beneath are not. There is a risk currently that someone could capture credentials from the communication between server01 (the nginx proxy) and server02. If you're going to implement connectivity to different servers in a production environment, don't even think about not using unencrypted communications between the nodes.

And if you're going to implement TLS in production, it's best to evaluate and specify exactly which protocols are able to be used to reduce the attack surface (which is easy to do in nginx, and there are tools out there to help you). I've tried to just illustrate the bare minimum needed to enable this capability, not provide a complete solution for a production environment.

Also to make things easier, and because I run my own Certificate Authority to trust internal services, I issued a *.example.com certificate for my nginx server, so it can purport to be any of the services it's presenting. If you're in an environment that doesn't do wildcard certs (and there are plenty of environments like that), then you can instead opt to have a different cert used for each server instance in the config, or just use a certificate with multiple Subject Alternative Names.