Last month, I launched the new site for a client's online resource for books. While only a beta (client just announced it to friends this week), it is a taste of things to come as I am about halfway through the development for version 2. When we launched, I put a note in my calendar to set up HTTPS once the Let's Encrypt certificate authority hit beta. Free, open, and automated certificates for websites? No brainer, especially given how important it is that we as developers protect both our and our users' data.
Naturally, with any new tool, I played around with it on my own personal site before using it for a client. Also, I discovered a couple weeks back that nginx was configured on my server without any default caching settings for images and other media files. No gzip compression set up either. Proverbial two (three?) birds with one stone time.
You can read along and then at the bottom I will provide links to my nginx.conf and an example site configuration file.
I am using Homestead for my local development and the godsend that is Laravel Forge for deployment on Digital Ocean. After restarting the server to insure all packages were up to date, I used Forge's built in tool for setting up Let's Encrypt and creating the certificate. Once completed, I clicked Activate and my site was magically running with HTTPS. Golden.
Well. Almost. I went to SSL Labs' Analyzer and only got a B. Seems the default Diffie-Hellman parameters on nginx are vulnerable to attack. The fix is relatively simple and if you are familiar with the command line and editing via vi, you should be done in no time. However, I wanted to insure that all of my SSL configuration options in nginx were as up to date as possible based on recommended practices too.
One of the most important configuration options you can edit is the list of ciphers your server will use and in what priority. Mozilla has a fantastic page that explains the ciphers and has recommendations based on your compatibility needs. Not only that, they have a SSL Configuration Generator where you specify your web server, currently installed version, and compatibility needs and it spits out the SSL settings it recommends all ready top copy and past. Very helpful.
Pro Tip: You can find your nginx and openssl versions by running nginx -v
and openssl version
If you're like me, you are probably running more than one site on Forge. Most SSL configuration options do not need to duplicated for every site configuration file, obviously, and can be all put in the global nginx.conf file. Which brings us to the gzip and caching portion of our entry.
Matt Stauffer wrote a solid article on how to enable gzip for your nginx server by adding a couple dozen lines to your global nginx.conf. I followed it exactly and highly recommend you read it first.
Next, caching headers. This is where it got a bit tricky for me. First, it has to go in your site configuration as you are matching files based off their file type as specified by the file name. Second, since I am running two different applications–Laravel for the main site and Craft CMS for the blog– that makes the configuration a little tricky as there are already two different location blocks matching incoming requests. You cannot simply set up a third location for just the media files as nginx will only match one location block and thus it will break things like the CSS displaying in the Craft CMS control panel.
You can get around this in two ways. First, by having additional, more specific grep matching for your location blocks or simply add conditionals to your existing location blocks. I went with the latter. Here is a short example of what I have in my site configuration for my combo Laravel and Craft CMS site:
location / { try_files $uri $uri/ /index.php?$query_string; if ($uri ~* "\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv)" ) { expires 1M; access_log off; add_header Cache-Control "public"; } if ($uri ~* "\.(?:css|js)" ) { expires 1y; access_log off; add_header Cache-Control "public"; } } location /blog { try_files $uri $uri/ /blog/index.php?p=$query_string; }
Pretty simple and it works. It probably is worth emphasizing that I do not consider myself a DevOps expert and while this works for me, I am in no way putting this solution up on high as The Solution (TM, patent pending).
For everyone who read this far, here are the two example files as promised. May they be a light to you in dark places, when all other lights go out. Oh, and remember to first test with nginx -t
before restarting with service nginx restart
.