DigitalOcean ❤ Let’s Encrypt

DigitalOcean + Let’s Encrypt

Bad news, My beloved DigitalOcean’s droplets just get hijack. Good news is it’s nothing there just random POC Meteor, And after spiked traffic which somehow break DigitalOcean’s limit, my poor droplets has been shut down completely. (sigh)

That moment when you get hijack

At that time I didn’t care much about security because it’s just development droplet for POC. But this hijack kick me to get serious and harden my security!

Speaking of which, Wasin Thonkaew just share link which very nice tutorials there but it’s not wrote for my DigitalOcean.

Also today Let’s Encrypt just leaving beta which mean it’s a perfect time to mess with it. (1.7 million certificates can’t we wrong!)

1.7 million certificates

I decide to rewrite my own version to suite my need for DigitalOcean and my new deomain name which I just register at NameCheap.

cheapest price for .io and super nice UI/UX there!

If you on the same path, I wish this’ll save you sometime like always. :)

Infrastructure setup

New Droplets and better use SSH this time ;)

Add SSH to DigitalOcean’s droplets
# Lists the files in your .ssh directory, if they exist
$ ls -al ~/.ssh
# Copies the contents of the file to your clipboard
$ pbcopy < ~/.ssh/

Add a Domain

Create Record

Add www

Add DigitalOcean's nameservers to NameCheap’s domain registrar.
Setup DigitalOcean’s nameservers on NameCheap

Get in

$ ssh
locale warning

Not again, Just set it and exit.

export LANGUAGE="en_US.UTF-8" 
echo 'LANGUAGE="en_US.UTF-8"' >> /etc/default/locale
echo 'LC_ALL="en_US.UTF-8"' >> /etc/default/locale

Get back in

$ ssh

Basic security hardening of your server

Setup UFW (Uncomplicated Firewall) rules

sudo ufw allow out 22/tcp
sudo ufw allow out 80/tcp
sudo ufw allow out 443/tcp

Get update to ensure all software is up to date and reboot.

sudo apt-get update && sudo apt-get dist-upgrade -y && sudo reboot

Enable automatic security updates

sudo dpkg-reconfigure --priority=low unattended-upgrades
Automatic security updates

Install fail2ban to prevent brute force SSH attacks

sudo apt-get install -y fail2ban


# Sadly the key is being served over http, so you need to check it's sha256 hash to ensure that it hasn't been somehow compromised
wget --quiet -O nginx_signing.key && sha256sum nginx_signing.key
# At the time of writing the sha256sum is "dcc2ed613d67b277a7e7c87b12907485652286e199c1061fb4b3af91f201be39"
# Please ensure that you get the same result before proceeding further
sudo apt-key add nginx_signing.key
echo "deb trusty nginx" | sudo tee --append /etc/apt/sources.list.d/nginx_org_packages_mainline_ubuntu.list
sudo apt-get update && sudo apt-get install -y nginx

Create the website root folder with demo

sudo mkdir /var/www/
# download our demo website
sudo tar zxf demo.tar.gz -C /var/www
sudo chown -R root:www-data /var/www/

Remove the default Nginx configuration and start with a fresh blank file

sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.orig
sudo nano /etc/nginx/conf.d/default.conf

Copy and Paste this and cmd+o and enter to save then cmd+x to exit

server {
listen 80;
server_name default_server;
root /var/www/demo;

Reload Nginx to apply our configuration

sudo nginx -t && sudo nginx -s reload

Now when you enter your site you should see this.

Let’s Encrypt Default Page

Setup Let’s Encrypt

sudo apt-get install -y git
sudo git clone /opt/letsencrypt

SSL certificates

Replace with your domain
export DOMAINS=","
export DIR=/var/www/demo
/opt/letsencrypt/letsencrypt-auto certonly --server -a webroot --webroot-path=$DIR -d $DOMAINS

Enter email and agree term then you should see

Nginx HTTPS config

Open default.conf for add our SSL Certificate.

sudo nano /etc/nginx/conf.d/default.conf


Replace with your domain
server {
listen 443 ssl;
root /var/www/demo;
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;


Save, Exit and then restart Nginx

sudo nginx -t &&  sudo nginx -s reload

Now we got up and running! But we not done yet!

Automatic Renew

sudo nano /home/

Then add below content then Save, Exit.

# This script renews all the Let's Encrypt certificates with a validity < 30 days
if ! /opt/letsencrypt/letsencrypt-auto renew > /var/log/letsencrypt/renew.log 2>&1 ; then
echo Automated renewal failed:
cat /var/log/letsencrypt/renew.log
exit 1
nginx -t && nginx -s reload

Add daily cron job.

sudo crontab -e

Then add below content then Save, Exit.

@daily /home/

Make it executable.

chmod +x /home/

Now it become zombies yeah! But if you test with SSL analyser, You’ll grade only B grade which is bummer! Gimme A!

Nginx SSL/TLS hardening

Edit config all over again.

Do you think why we’ve to do this again and again? Me too! But I’ll keep continue with the original flow for now. ;p
server {
listen 80;
listen 443 ssl http2;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;
ssl_session_cache shared:SSL:128m;
add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
ssl_stapling on;
ssl_stapling_verify on;
# Your favorite resolver may be used instead of the Google one below
root /var/www/demo;
index index.html;
    location '/.well-known/acme-challenge' {
root /var/www/demo;
    location / {
if ($scheme = http) {
return 301 https://$server_name$request_uri;

Save, Exit and then restart Nginx

sudo nginx -t &&  sudo nginx -s reload

Now you should get A+ SSL and HTTP/2 nice!

To proof you get HTTP/2 protocol you need to add Protocol column via Dev tools and it’ll shown as “h2”

Are we done yet? Not really this analyser still give us E Noooooo.

Security headers hardening

Open default.conf for add our SSL Certificate. (Yes again!, below step is copy and paste from original site)

sudo nano /etc/nginx/conf.d/default.conf

The X-Content-Type-Options header stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type.

add_header X-Frame-Options "SAMEORIGIN" always;

The X-Frame-Options header tells the browser whether you want to allow your site to be framed or not. By preventing a browser from framing your site you can defend against attacks like clickjacking.

add_header X-Xss-Protection "1";

The X-Xss-Protection header sets the configuration for the cross-site scripting filter built into most browsers.

add_header Content-Security-Policy "default-src 'self'";

Then enable report mode , You can read more about this here.

Content-Security-Policy-Report-Only instead of Content-Security-Policy

Final config will look like this… (Gist look better huh?)

Save, Exit and then restart Nginx

sudo nginx -t &&  sudo nginx -s reload

Now you should finally get A from , Yeah!

About Public Key Pinning, please read more here.

Addition Steps

Backup letsencrypt from remote to local by run this via local.

$ scp -r letsencrypt

Keep your private key secure by set the right permission so only you can read it at remote.

# chmod 400 -R /etc/letsencrypt/archive

Conclusion really help me understand what going on along the way. Next step for me is try to squeeze HTTP/2 speed to it limit. This should be fun! :)

Note (ref)

You can get free $10 for DigitalOcean (which is 2 months free hosting).
You can get cheapest .io and nice UI/UX at NameCheap.

Happy Securing!

Show your support

Clapping shows how much you appreciated katopz’s story.