Area Man Configures Nginx

These are notes to my future self, because these are the results of much DuckDuckGo-ing, and I'm going to forget all this before tomorrow afternoon. Please do not treat this as some sort of canonical reference! That would be a really bad idea.

(Also! You don't have to read further if you don't care what Yesod, Keter, Nginx, and the such are. If you're that hard-pressed for reading material, consider reading Dostoyevsky or something.)

So! This is the scenario: I'm deploying a Yesod web application to serve a https site. All http requests are redirected to the https site without any regard of just what the request is.

These are the approaches I considered:

  1. Configure Keter for managing deployments and as a public-facing reverse proxy. This is simple and straightforward.
  2. Keep Keter for managing deployments and as a not-public-facing reverse proxy, and place Nginx before it as the public-facing reverse proxy. It's probably not as ridiculous as it sounds, or maybe it is. Let's investigate.

Keter is pretty cool. For example, I can do a 'yesod keter' and quickly deploy the app. Yesod and Keter will take care of the boring details such as:

So I certainly want to keep Keter in the picture.

The second approach (of placing Nginx reverse proxy before Keter reverse proxy) is certainly less than optimal because it adds another seemingly pointless layer, but that is what I chose, for the moment anyway. Reason is, there are a few things Nginx can do but Keter (evidently) can't, at the moment:

It happens that Keter is still relatively young and obscure, and Nginx is older and way more popular, so it is a matter of time before Keter catches up on compliance with the fancy security stuff.

(I do have a soft spot for Keter because (1) it's so useful; (2) Michael Snoyman readily answers questions about Yesod and Keter; and (3) Michael even merged my tiny documentation patch today. More on that later.)

Below are the steps I took to scaffold a (Nginx ↔ Keter ↔ Yesod app) setup:

Generate a SSL key and certificate signing request

This is how:

openssl req -nodes -newkey rsa:2048 \
-keyout example.net.key \
-out example.net.csr

The example.net.csr file was given to Gandi.net for an actual certificate, in exchange for real cash money. Gandi verified that I do, in fact, control the domain I'm claiming (options: being able to answer email to admin@example.net, or create a DNS CNAME record, or hosting a text file in the server root), and then gave me a certificate example.net.crt.

Import Gandi intermediate certificate

The example.net.crt that Gandi gave me would not please mobile web browsers (for example, Firefox and Chrome on Android). They complained about not being able to verify the server certificate. This was solved by downloading Gandi intermediate certificate (the one in PEM format) and appending it to example.net.crt, with a newline in between.

echo '\n' >> example.net.crt
cat GandiStandardSSLCA.pem >> example.net.crt

Configure Keter

My keter-config.yaml looks like this at the moment:

root: ..
host: 127.0.0.1
port: 8000

Configure the application

In addition to the usual stuff, I had to add this line to application's config/keter.yaml:

ssl: true

This line is important: without it, the application will generate http URLs instead of https URLs; consquently, no browser would load any of the CSS/JS assets, and all links would refer to http things.

Configure Nginx

Here's (an approximation of) my Nginx configuration:

# Don't advertise that we're nginx, or nginx version
# in error pages and the such.
server_tokens off ; 

server {
    listen 80 ;
    server_name example.net www.example.net ;

    # Make things https-only.
    rewrite ^ https://$server_name$request_uri? permanent ;
}

server {
    listen 443 ssl default deferred ;
    server_name example.net www.example.net ;

    ssl_certificate /path/to/example.net.crt ;
    ssl_certificate_key /path/to/example.net.key ;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 ;
    ssl_prefer_server_ciphers on ;

    # Recommended for proven strength.
    ssl_ciphers 'AES256+EECDH:AES256+EDH:!aNULL:!eNULL' ;

    # Performance stuff.
    ssl_session_cache builtin:1000 shared:SSL:10m ;

    # Perfect Forward Secrecy stuff.  
    ssl_dhparam /path/to/dhparam.pem ;

    # OCSP stapling stuff.
    ssl_stapling on ;
    ssl_stapling_verify on ;
    resolver 8.8.4.4 8.8.8.8 valid=300s ; 
    resolver_timeout 5s ; 

    # HSTS stuff.  
    add_header Strict-Transport-Security max-age=31536000 ;

    # Don't frame us.
    add_header X-Frame-Options DENY ;

    # Avoid MIME type troubles.  
    add_header X-Content-Type-Options nosniff ;

    access_log /var/log/nginx/example.net.access.log ;
    error_log /var/log/nginx/example.net.error.log info ;

    location / {
        # Reverse proxy for Keter.
        proxy_pass http://127.0.0.1:8000 ;
    }
}

The dhparam file is generated like so:

openssl dhparam 2048 -out /path/to/dhparam.pem

For pretending to support old-ass versions of Java:

openssl dhparam 1024 -out /path/to/dhparam.pem

Get the setup rated

All these efforts were rewarded with this:

aplus.png

That is an A+ rating in Qualys SSL server test. That is pretty cool. That rating might lull us into a false sense of security (after all, setting up SSL is just one part of securing web-facing software), but now no one can blame us for not trying at all.