Optimise web rendering, ssl and security

As part of building a blog, I wanted to make sure that I at least test the performance of the blog against “the real world” rather than just myself, so I decided to use Google’s pagespeed insights tool available here. Another very important factor was that the blog was only available over HTTPS and with that, one usually introduces additional latency due to SSL session (re-)negotiation and general en/decryption.

Note: I’ve decided to give up the domain and hosting of that blog (https://ovni.io) and this is a copy & past from its content to medium.

results

The first couple results kept coming up with a few things to do :

  • Set a more appropriate Cache-Control header for things that will hardly change (like JS, CSS from third parties)
  • Ensure you set appropriate caching for images and the likes
  • Make sure you set your server to gzip as much as possible

results 2.0

After several tweaks to the Cache-Control header(s) and Nginx settings (serve static content directly, instead of passing it to the application), I finally got the desired 100/100 result!

nginx

So, couple things I added to my nginx config.

Tip: use nginx.org’s repositories, they’re way more up-to-date than your distribution(s).

http settings

keepalive_timeout  65;
gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";

I actually found the above in another blog but cannot remember which one at this moment, I’ll update with credits if I find it at some point.

cache settings

So, part of the crucial settings was to make Nginx serve all the static “stuff” and set the appropriate Cache-Control header(s). So I added the following to my config

location /static/ {
error_log /var/log/nginx/static-error.log;
access_log /var/log/nginx/static-access.log;
alias /path/to/static/;
add_header Pragma public;
add_header Cache-Control "max-age=604800, public, must-revalidate, proxy-revalidate";
autoindex off;
}
location = /favicon.ico {
log_not_found off;
expires 365d;
access_log off;
}

ssl settings

Even SSL can be somewhat insecure, with the wrong configuration or weak cipher usage (default confiugration), so I decided to use Qualy’s SSL Labs to try find weak configuration settings. Well, after several re-scans I fixed several issues, in particular the list of accepted ciphers and a new strong DH group as suggested by Qualys’ results and weakdh.org.

Create a strong DH group

openssl dhparam -out dhparams.pem 2048

Make Nginx use the new DH group set

To make use of the new DH group, you must instruct Nginx to use it. As part of the serverdefinition, add this line:

ssl_dhparam /path/to/dhparams.pem

Set strong cipher suites only

Set the good / strong ciphers and make the server enforce them.

ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;

Other HTTP security headers

You should also add some important security related headers in everyresponse, to prevent e.g. Click Jacking and other malicious attempts to trick your users:

add_header Strict-Transport-Security "max-age=31536000 always";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options nosniff always;

move css/js includes

This was relatively easy — PageSpeed literally tells you which of the CSS / JS you should move to after the content has been loaded, so I just did that quickly in the template and voila, that was it!