Apache Reverse Proxy with LetsEncrypt SSL

Configuring an Apache reverse proxy server with LetsEncrypt SSL certificates

Andrew Cabey - June 8, 2017

What is a Reverse Proxy?

Like many Homelabbers on a residential connection, I only have a single public IP to work with, and with this comes the challenge of hosting several services behind this single IP. While on my internal network I have several VMs (internal wiki, Plex, Gitlab, website, etc.) accessible to my LAN, by conventional hosting methods I could only make one of these available through my public IP at a time.

In comes reverse proxying, where you expose a single server to the external network (public IP), and it will then redirect that traffic to where it needs to be depending on the desired host.

For example, someone on the internet may request blog.acabey.xyz, the request will reach my proxy server, then based on the desired host (blog.acabey.xyz), the server will pull internally from my blog host (blog.eden.localdomain on my internal domain) and feed that content right back out to the internet client. Cool stuff!

While there are a ton of tutorials on how to configure a reverse proxy with nginx, I fancy myself an apache man and took it to use my webserver of choice instead.

You can use reverse proxying to host multiple services, redirect ports, and whole bunch of other magic. I will cover setting up a basic web service behind a proxy and enabling SSL with LetsEncrypt.

What you will need

  • A public IP through which you are allowed you to forward ports 80 and 443
  • A domain / subdomain name for each internal service
  • Two servers/ VMs/ containers each running apache, an unconfigured reverseproxy and a webservice serving content to your internal network on port 80

Setting up your domain

In order to reach your service through your domain/subdomain, set up an A record pointing towards your public IP. This is entirely dependent on your DNS service, so I’ll leave this as an exercise to the reader.

Setting up your NAT

The first step on your end is to forward your incoming web traffic to your reverseproxy server. Again, this step is entirely dependent on your network / firewall configuration, but should be simple enough. When you are done, you should see the apache landing page (being served by your reverse proxy server) when you access any of your domains.

Basic Proxying

Whether or not you have any experience configuring apache virtualhosts, setting up a basic proxy is a piece of cake. You’ll have to enable a couple modules on your server

a2enmod proxy proxy_http proxy_ajp rewrite deflate headers proxy_balancer proxy_connect proxy_html

Disable the default site

a2dissite 000-default

Now you can make the choice, you can either create a virtual site for each service you proxy, or put them all in a single apache site. I prefer one for each internal service, so I will create a new site configuration

vim /etc/apache2/sites-available/webservice-eden-localdomain.conf

Inside this site configuration, create a new virtualhost listening on port 80. For more information on configuring a virtualhost, see the apache docs. There are a ton of different configuration options

<Virtualhost *:80>

Give this virtualhost a ServerName matching your target domain name

ServerName 'webservice.acabey.xyz'

Now the actual proxying (if you don’t use an internal DNS, just use the static IP of your server)

ProxyPreserveHost On
ProxyPass / 'http://webservice.eden.localdomain/'
ProxyPassReverse / ''http://webservice.eden.localdomain''

Your basic, insecure virtualhost should look something like this

< VirtualHost *:80 >
ServerName 'webservice.acabey.xyz'
ProxyPreserveHost On
ProxyPass / 'http://webservice.eden.localdomain/'
ProxyPassReverse / ''http://webservice.eden.localdomain''
< /VirtualHost >

Internal SSL (Optional)

Of course, you notice that there is absolutely no SSL/TLS going on here, which is no bueno. Solid security in a reverse proxy system should look like this

Internet ←→ Reverse Proxy (LetsEncrypt SSL) ←→ Internal Service (self-signed SSL)

Ideally, you should set up an internal, self-signed CA that you add to all the machines on your internal network, and use a certificate signed by this CA on your internal webserver. From there, the only things you have to change are the ProxyPass directives in your virtualhost to https rather than http.

Configuring LetsEncrypt

To really step up your security game, we will use LetsEncrypt to establish SSL with our internet users.

There are countless guides on how to use LetsEncrypt, but to keep it brief (assuming a Debian/ Ubuntu system)

apt install python-letsencrypt-apache
sudo letsencrypt --apache -d webservice.acabey.xyz

In the interactive window, be sure to Allow both HTTP and HTTPS connections, we will fix this in a second. Go back into your apache site config and notice that you should now have a virtualhost listening on port 443.

Before we continue with that, you should first redirect http to https. Go to the virtualhost listening on port 80 and cut your proxying directives down to the new 443 virtualhost. In place of this, just add a permanent redirect to the https enabled site

Redirect Permanent / https://webservice.acabey.xyz/
Remove the Alias directive

Proxying SSL

Although you are very, very close, there is some magic when it comes to proxying SSL traffic. In the 443 virtualhost, add a global location section, which we will use to apply some headers

< Location "/" >
RequestHeader set X-Forwarded-Proto https
RequestHeader set X-Forwarded-Ssl on
RequestHeader set X-Url-Scheme https
< /Location >

Make sure that your original proxy settings are in the 443 virtualhost. By the end, your site apache site configuration should look something like this:

Complete configuration

Try reaching your domain!

A Note on Internal DNS

In the event that your site does not load from the external domain name, this is likely caused by lack of support for NAT reflection on your router. Try manually overriding your external domain name (eg. webservice.acabey.xyz) to the IP of your reverse proxy server within your internal DNS settings. If you are on pfSense, you can either try enabling NAT reflection or you can implement Split DNS