Using an SSL Terminating Reverse Proxy with Passenger Standalone

An SSL terminating reverse proxy is simply a web server that is configured to accept encrypted https requests from clients, and to forward them as unencrypted http requests to another backend process, and to relay the unencrypted results from the backend process back to the client via the encrypted channel. In our case the backend process will be Passenger Standalone running on the same host. Setting up an SSL terminating reverse proxy is super easy, and this article will walk you through how to do it.

Which reverse proxy are you using?

Prepare the system

Follow these instructions, before continuing with this guide.

Assumptions: you are using the bash shell, and have admin rights to your computer, and are using a modern browser. Ensure that OpenSSL is installed.

Debian, Ubuntu
$ sudo apt-get update
$ sudo apt-get upgrade -y
$ sudo apt-get install -y openssl
$ sudo a2enmod ssl
Red Hat, CentOS, Fedora, Amazon Linux, Scientific Linux
$ sudo yum update -y
$ sudo yum install -y openssl
$ sudo yum install -y mod24_ssl # only if using httpd24
$ sudo yum install -y mod_ssl # only if using httpd
macOS
$ sudo softwareupdate -i -a
$ brew update
$ brew upgrade
$ brew install openssl
$ sudo sed -E -i.bak -e 's|#(LoadModule ssl_)|\1|' /etc/apache2/httpd.conf

Software versions used in this article:

To check the versions of the packages you have installed you can use the following commands:

Package Version Version Command
OpenSSL 1.0.2h
$ openssl version
Passenger 5.0.29
$ passenger -v
Nginx 1.10.1
$ nginx -v
Apache Httpd 2.4.18
$ httpd -v || apache2 -v

Obtain your certificate / key pair

You can read up on self signing a certificate in this article or if this is for production use you can use Let's Encrypt or another certificate authority to get a valid certificate.

Configure Web Server

Replace example.com with your domain:

Move your http/port-80 virtual host configuration to port 443, and setup a redirect, then add the SSL options to your port 443 virtualhost. The headers and server signature options are more useful in production. The end result should look similar to the provided configuration. If you have multiple https virtualhosts on the same box you must add SSLStrictSNIVHostCheck off to your Apache configuration, you can set it just above your virtualhost blocks.

SSLStrictSNIVHostCheck off
<VirtualHost *:80>
    Redirect permanent / https://www.example.com/
    ServerName example.com
    ServerAlias www.example.com
</VirtualHost>

<VirtualHost *:443>
    ServerName www.example.com

    ProxyPreserveHost On
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    SSLEngine on
    SSLProtocol all -SSLv2 -SSLv3
    SSLHonorCipherOrder on
    SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
    SSLCertificateFile "/private/etc/apache2/server.crt"
    SSLCertificateKeyFile "/private/etc/apache2/server.key"
    SSLCompression off # not always present

    Header always edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

    ServerSignature Off
</VirtualHost>

Change your listen options from port 80 to the SSL versions below. Create a redirect from http to https as shown. Add the SSL options as described below. You must generate a dhparam file, this can be done with $ openssl dhparam -out /usr/local/etc/nginx/ssl/dhparam.pem 2048

server_tokens off;
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /usr/local/etc/nginx/ssl/server.crt;
    ssl_certificate_key /usr/local/etc/nginx/ssl/server.key;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    ssl_dhparam /usr/local/etc/nginx/ssl/dhparam.pem;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

    add_header Strict-Transport-Security max-age=31536000;
    location / {
        proxy_pass http://localhost:3000;
    }
}

Start Passenger

The instructions for starting passenger are the same for an SSL terminating reverse proxy as for a regular reverse proxy.

Done

Now if you restart your web server, SSL should be working and Passenger should be serving your app.