CentOS: Using NGinx as an SSL Reverse Proxy for Apache

A little while ago, I published a guide to configuring NGinx to act as a Reverse Proxy for Apache. Although it was briefly mentioned, we never went through the steps necessary to configure NGinx to work in this manner for SSL connections - we simply left Apache listening on port 443.

This documentation details how to go about configuring NGinx to handle your SSL stuff as well. It assumes you've already generated CSR's and got the SSL certificate to use (presumably because they're already being used by Apache).

 

Introduction

The steps involved are actually almost identical, but there are two ways you can go about it

  • End-to-End Encryption 
  • NGinx to Client Encryption

The difference essentially boils down to whether we tell NGinx to use SSL when talking to Apache. As the connection is taking place over the loopback interface, it's more a matter of practicality than it is security. My preference is to use end-to-end as it means anything running on Apache's end can see that a SSL connection is being used (so scripts configured to force a SSL connection won't try and redirect for example).

It also means that if (for some reason) you're serving different content over SSL, the correct content will be displayed.

There is a slight performance overhead in this, but it's pretty negligible on modern hardware.

We'll be using End-To-End in this documentation, and you'll need to be root to complete the steps detailed below.

 

Installing the Certificate

Depending on how you generated them, your certificate and private key may already be in the correct location, but in case they aren't we want to make sure that both are within the pki config structure in /etc.

mv server.crt /etc/pki/tls/certs
mv server.key /etc/pki/tls/private/

Done!

 

Configuring NGinx

I like to play it safe, so we're going to start by configuring NGinx to accept SSL connections on a non-standard port (4431). Essentially we're just creating a new server block

So in your favourite text editor, open /etc/nginx/conf.d/virtual.conf (if you prefer, you can put it in ssl.conf - and probably should - but I like to have all my server blocks in a single file where possible). We're going to start by copying the HTTP block for the site we want to SSL

So assuming our site is www.example.com and the docroot is /var/www/vhosts/example.com/public_html

server {
listen 80;

root /var/www/vhosts/example.com/public_html;
index index.php index.html index.htm;

server_name www.example.com;
location / {
try_files $uri $uri/ /index.php;
}

location ~ \.php$ {

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
proxy_cache my-cache;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
}

location ~ /\.ht {
deny all;
}

}

Now, of course, we want to adapt it so that it uses SSL, so that block becomes 

server {
listen 4431;

root /var/www/vhosts/example.com/public_html;
index index.php index.html index.htm;

server_name www.example.com;
ssl on;
ssl_certificate /etc/pki/tls/certs/server.crt;
ssl_certificate_key /etc/pki/tls/private/server.key;

ssl_session_timeout 5m;

ssl_protocols SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+EXP;
ssl_prefer_server_ciphers on;
location / {
try_files $uri $uri/ /index.php;
}

location ~ \.php$ {

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass https://127.0.0.1:443;
proxy_cache my-cache;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
}

location ~ /\.ht {
deny all;
}

}

So, to summarise our changes;

  1. Listen port changed (once we're ready, this will become 443)
  2. New SSL section, enabling SSL, providing key and certificate locations and defining which protocols and ciphers can be used (note the lack of SSLv2 from the protocols list)
  3. Proxy_pass changed to pass to Apache via SSL connection.

You should now be able to restart NGinx and connect on port 4431 (you can do so in a web-browser but we'll do it with OpenSSL below).

service nginx restart 
openssl s_client -connect www.example.com:4431 < /dev/null

The exact output of OpenSSL will depend entirely upon your certificate, but what you're looking for is that it manages to connect and handshake successfully. If you get socket: Connection refused then something is preventing you from talking to NGinx on port 4431 - either NGinx didn't restart, or you've firewalled it off.

 

Reconfiguring Apache

We can't yet tell NGinx to listen on port 443 as Apache is still doing so. The next step, then, is to tell Apache to listen on a different port. We're going to go with port 8081. Just as we did in the previous guide, we're going to edit httpd.conf.

nano /etc/httpd/conf/httpd.conf
# Find the following

NameVirtualHost (someIP):443
# Change the port to
Listen 127.0.0.1:8081

# Now at the bottom of the file, you'll find your virtualhost directives,
# Change all port definitions of 443 to 8081

However, unlike the previous guide, we also now need to edit the SSL configuration file

nano /etc/httpd/conf.d/ssl.conf
# Find Listen 443 and change to

Listen 127.0.0.1:8081
# Further down the file, you'll find the default SSL Vhost
# <virtualhost _default_:443> becomes <virtualhost _default_:8081>

Now we just need to reconfigure NGinx then restart both services;

Open your NGinx server config file again (I used /etc/nginx/conf.d/virtual.conf above). We have two lines we need to change

# listen 4431; becomes
listen 443;

# proxy_pass https://127.0.0.1:443; becomes
proxy_pass https://127.0.0.1:8081;

Now we just need to restart the services (in the correct order) and we should be good to go

service httpd restart
service nginx restart

We should now be able to browse to https://www.example.com and view our content. Static content should be served by NGinx whilst dynamic content will be proxied to Apache. As in our previous guide, we've bound Apache to the loopback adaptor, so that no-one can browse to port 8081.

 

Extra: Including CA Bundles

If you need to include your CA's certificates for some browsers to recognise yours as valid, things can get a little more complicated as you'll need to chain in the intermediary certificate. At this point it's worth creating a NGinx specific copy of the certificate to make sure we don't upset Apache!

cp /etc/pki/tls/certs/server.crt /etc/pki/tls/certs/server-nginx.crt

Now we want to append our Intermediary bundles onto the end (assuming yours is in roots home directory)

cat ~/ca-bundle.crt >> server-nginx.crt

Edit the server block to point to the new certficate

nano /etc/nginx/conf.d/virtual.conf
# find ssl_certificate /etc/pki/tls/certs/server.crt;
ssl_certificate /etc/pki/tls/certs/server-nginx.crt;

#Exit and save

service nginx restart

You should now find that the affected browsers now accept the certificate, you'll certainly see a lot more output when you run

openssl s_client -connect www.example.com:443 < /dev/null