You should be using a reverse proxy both internally and externally. Why? It keeps things simple (for example, you know that every DNS entry has to point to a single IP now), it keeps things secure (a reverse proxy only passes what’s required), and it keeps SSL termination easy. Especially if you use Let’s Encrypt and NGINX, it makes it really easy! Let’s jump in!
What is a reverse proxy?
First, let’s understand what a reverse proxy is, what it does, and why we’d want one. I’m assuming you’ve heard of a regular proxy before. It’s a device that makes requests on your behalf – perhaps bypassing your school or work Internet filter (I won’t tell!) – and shows you the results. Or maybe you’ve heard the term “vote by proxy” where you send someone to vote in a board meeting on your behalf. So what’s the opposite of that? A reverse proxy! This is a service that makes inbound requests on behalf of someone externally. Internal and external are of course objective because I’m going to show you how to use an internal reverse proxy as well.
How does it work?
Now that we know what it is, let’s talk about how it works. Now I don’t know about you, but I’m leased a small number of IPs from my different hosting providers and even my home ISP restricts me. And typing in port numbers is a pain – was my personal site on port 9080 or is that NextCloud? Shoot! Reverse proxies simplify this by allowing you to use a single IP address and ports 80/443 for your requests.
Let’s say for example I have a service on my home network that uses port 8080, another service that uses port 8443, another service that uses 2280, and another that uses 5544. Typically, to access these services, I might have to open my browser and type in https://10.10.10.20:8080, http://10.10.10.30:5544, etc. I could simplify this by adding a DNS record to those IPs so I could use https://server1.mydomain.com:8080, but I still have to type the port number. I might also have SSL certificate errors and I want to avoid that.
Instead, I set up a reverse proxy. I port forward 80/443 to this one server. I update my DNS to point to this server. And on this server, I run NGINX and I tell NGINX that www.mysite.com is at https://10.10.10.20:8080, but from your side, all you see is https://www.mysite.com. And maybe I have NextCloud setup as https://nextcloud.mysite.com and on the back end, NGINX is really going to https://10.10.10.10:5544.
Setting up a Reverse Proxy
Prerequisites
Before we get into things, you’re going to need to have a few things first.
- REQUIRED: A real domain name you own (real meaning you’ve leased a .com, .net, .org, .info, .ninja, etc. and it exists on the Internet and you own meaning it can’t be google.com, apple.com, etc. because you don’t have access to those. Invalid examples: .loc, .local, .main, .lan, etc. These will not work!)
- REQUIRED: A hypervisor you can install a Linux server on (Proxmox, Hyper-V, VMware, etc.)
- REQUIRED: DNS server (internal or external)
- OPTIONAL: External inbound access through port 80/443
Process
I’m going to do this with Ubuntu Server 20.04. You can do this with any flavor of Linux you’d like.
Install a fresh copy of Ubuntu Server and update and reboot it. Get it fully patched up!
Now we just need to install NGINX:
sudo apt install nginx
Ubuntu will automatically start and enable the service, however on other versions and other distros, it may not. You can check with systemctl status nginx.
We need to get rid of the default site. Run the following command to unlink the conf file:
sudo unlink /etc/nginx/sites-enabled/default
And then run this to delete it altogether. We just need a blank slate.
sudo rm /etc/nginx/sites-available/default
Change directory into the sites-available directory:
cd /etc/nginx/sites-available/
And we’re going to create a new config file called proxy.conf.
sudo nano proxy.conf
And then we’re going to enable it.
sudo ln -s /etc/nginx/sites-available/proxy.conf /etc/nginx/sites-enabled/proxy.conf
In this file, copy and paste and modify the following simple http site. You can point this to an internal service you already have. For me, I’m pointing this to the web server that serves up my family’s intranet site. Even though this is internal, the only difference between this and an external reverse proxy is that external DNS doesn’t point to this server and the firewall it’s behind doesn’t forward 80/443 traffic.
server { listen 80; server_name www.yourdomain.com yourdomain.com; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://10.200.2.39; } }
Things to change are yourdomain.com to the domain you own or a subdomain of the domain you own and the proxy_pass location. This is where you put the location of the service – for example, http://10.200.34.14:8090.
The “proxy_set_header” are HTTP headers NGINX sets and forwards on to the web server. You’ll notice that some appear to be duplicated. That’s because the receiving server may ignore one header and accept another. You’ll have to play around with the headers and check the software documentation. These are the most common ones.
Save and exit nano (CTRL + X and then Y) and restart nginx
sudo systemctl restart nginx
Let’s test your reverse proxy. Open your local HOSTS file and add an entry for your domain and the IP address of your reverse proxy. This is just to locally test as your HOSTS file will bypass DNS. Follow the examples in your HOST file. For example:
10.10.10.10 mysite.com
10.10.10.10 www.mysite.com
Now if I go to www.mysite.com, I’ll be directed to my reverse proxy. If you see what you expect to see, awesome! Your reverse proxy is working!
Securing requests with HTTPS
Today, security is not optional and it’s changed a lot. We used to require dedicated IPs for every SSL site (wonder why we have an IP address shortage?), and SSL certificates used to last anywhere from 1-10 years. In 2020, the maximum validity an SSL cert can be is 1 year. In 2015, Let’s Encrypt came in and turned the world on it’s head by providing free SSL certificates. The only thing? These certs are valid for a maximum of 90 days.
You might be wondering, why 90 days? Answer: security. You’re rotating your private keys at most every 90 days reducing the chance of someone stealing your SSL traffic – if they do manage to get your private keys, they won’t be valid for long.
You might also be saying, “But if I have to change certs every 90 days, that’s going to take forever!” And you’d be right. But Let’s Encrypt wants you to automate certs – everything from requesting and installing should be automated.
We’re going to do this on our reverse proxy server by using a free and open-source tool from the EFF called certbot. Certbot will handle generating, requesting, and installing the certificates, and then it will also handle the renewal, including installing the updated certificates. It’s a nice “set it and forget it” solution.
Installing certbot is also extremely easy. Just head to https://certbot.eff.org/ and then select your web server type and OS you’re installing it on and follow the instructions. Since I’m generating a wildcard certificate that is going to cover all my sites, I run the following:
sudo snap install --beta --classic certbot
Since I’m generating a wildcard certificate, I need to generate the certificate manually using a DNS challenge. Also, since this is an internal webserver, I have to do a DNS challenge regardless since Let’s Encrypt can’t get into the server to verify.
sudo certbot certonly --manual --prefered-challenges dns -d *.yourdomain.com
Replace yourdomain.com with your actual domain.
Certbot is going to ask for an email. This is where Let’s Encrypt will send notices about your SSL expiring or issues renewing to. Agree to register with the ACME server. Decide if you’d like to join the EFF’s mailing list. You can say no here if you don’t want to join yet another email newsletter.
Certbot will now attempt to get a certificate. Since we’re doing a DNS challenge, it’s going to ask us to create a DNS TXT record under the name _acme-challenge.mydomain.com and it’ll spit out a random string. We need to copy that string and go to our DNS provider and create a new text record with that value. Once it’s added, go back to your terminal and press enter. It’ll verify and issue a cert.
You’re going to see some IMPORTANT NOTES. The thing to remember is where the certificate and private key are as well as the certificate expiration date.
Add SSL to NGINX
Now go back to your proxy.conf file.
Remove the “listen 80;” line and under the closing brace “}” for the location, add the following:
listen 443 ssl; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
Your site will now be served with your wildcard SSL. As you add more services, just add a new server block with that cert and every time you renew it, it’ll update all sites on this server.
Catch-All HTTPS Redirect
One last thing I like to do is add a catch-all redirect. This redirects port 80 traffic to 443. All you need to do is copy and paste the following block into your proxy.conf file.
#################################################### # !!! DEFAULT CATCH-ALL HTTPS REDIRECT !!! # #################################################### server { listen 80 default_server; server_name _; return 301 https://$host$request_uri; } # //////////////////////////////////////////////////
Restart NGINX and update your DNS and that’s it!
Internal-Only Reverse Proxy
I mentioned in this article that I have an internal-only reverse proxy. My internal domain name externally only has MX records and my _acme-challenge records. If you’re external, you cannot reach my reverse proxy. Everything is closed up. However, if you’re inside my network, my internal DNS points to this reverse proxy for everything.
Services that I want to expose internally will route through this proxy which also eases up on security just a bit more.
External Reverse Proxy
First, not everything you do needs to be opened to the Internet! If you use pfSense, you can have an OpenVPN server that will give you access to your internal network. I colo a dedicated server in a data center. This server has a virtual pfSense firewall that has an IPSec tunnel to my house. If I’m not home and need private internet or access to some of my internal services, I simply VPN in.
Now for the items that can be exposed, like public websites, I port forward 80 and 443 on a virtual IP on my pfSense firewall. This goes to my reverse proxy. My external DNS will in turn point to this virtual IP. This allows me to safely expose internal services like a public website. First, my pfSense firewall is doing filtering. It’s only going to allow traffic on 80 and 443. That means SSH, RDP on Windows servers, SQL, etc. isn’t exposed externally, and I can easily open it up internally. So if I need to SSH to a server, I just VPN in.