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:
Configure Keter for managing deployments and as a public-facing reverse proxy. This is simple and straightforward.
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:
Preparing a ready-to-deploy “bundle”,
Copying the said bundle over to some pre-configured server,
Unpacking and sanity-checking the said bundle at the server,
Launching or rolling back the application contained in the said bundle based on how things proceed,
Assigning the said application a port number,
Managing several such applications,
Reverse proxying for the said applications, and even
Managing PostgreSQL connections for the said applications.
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:
Configure protocol support, such as limiting supported protocols to TLS v1, v1.1 and v1.2.
Configure cipher suites to just the known best ones, or just the right ones for better backward compatibility. This is useful if you have people using the dreaded Windows XP/Internet Explorer 6 combination.
Strict Transport Security, so that one could worry about things like what sslstrip demonstrates a little less.
Make plenty of search engine juice available.
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.csrThe 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.crtConfigure Keter
My keter-config.yaml looks like this at the moment:
root: ..
host: 127.0.0.1
port: 8000Configure the application
In addition to the usual stuff, I had to add this line to
application’s config/keter.yaml:
ssl: trueThis 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.pemFor pretending to support old-ass versions of Java:
openssl dhparam 1024 -out /path/to/dhparam.pemGet the setup rated
All these efforts were rewarded with this:

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.