linkedin twitter github linklocationarrow

Moving to DigitalOcean

Last weekend I've moved my website and blog to DigitalOcean. At the time of building this website 2015 I choosed Heroku as the platform to host my application, because I didn't wanted to deal with server provisioning and maintenance.

Heroku is probably the easiest way to ship your application into production πŸš€. In my use case, the GitHub repository that hosts the code for my website, was connected to Heroku and magically I've managed to ship my application using continuous deployment through Travis CI and GitHub.

However I knew that I would be switching at some point to an IaaS considering I would need more control over the infrastructure of my application.

The problem

My website is a Node.js application built with Express, Pug and SCSS. The blog runs on a self-hosted Ghost.

Since the start of using Heroku, I wanted to use a single Dyno to keep it simple. But every dyno is attatched to a process, so I managed to start Ghost as a module from the Express application. This workaround wasn't the best solution, but it worked more than a year and a half.

Recently, Ghost went out of beta and released the 1.0.0 with a ton of breaking changes. Since then it was nearly impossible with my needs to keep using Heroku.

Switching to DigitalOcean

I decided to make the move considering it an opportunity to learn and improve my infrastructure.

If you don't have a DigitalOcean account, use this link to register and get $10 for free!

Requirements

  • Single droplet.
  • Basic security.
  • SSL certificate using LetsEncrypt & Certbot with auto-renew.
  • Independent blog and web applications through Nginx.

Basic security

After spinning up a 5$ droplet, the first thing I did was disabling root login and password authentication. That means only SSH can be used to connect to the server.

UFW

$ sudo ufw allow 'Nginx Full' && sudo ufw allow OpenSSH
$ sudo ufw enable && sudo ufw status

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

LetsEncrypt

When I was on Heroku, I used Cloudflare to obtain SSL πŸ”’. But LetsEncrypt is way better. Just because you get end to end encryption.

To get your SSL certificate, first, you need to install certbot.

$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update && sudo apt-get install python-certbot-nginx

Then, open your Nginx configuration file find server_name directive and set your domain.

server_name example.com www.example.com;

Verify the configuration and if you have no errors, reload it.

$ sudo nginx -t
$ sudo systemctl reload nginx

Now it's time to obtain our SSL certificates for the domain specified at the Nginx config file.

$ sudo certbot --nginx -d example.com -d www.example.com

⚠️ Before obtaining the certs, you'll have to point the domain to your DigitalOcean IP. That's the way Certbot verifies that you control the domain you're requesting a certificate for.

If that's successful, certbot will ask how you'd like to configure your HTTPS. Finally the certificates will be downloaded, installed, and loaded.

Auto renewal

LetsEncrypt certificates are only valid for ninety days, however the Certbot cli includes an option to renew our SSL certificates and we can automate this process with a crontab.

$ sudo crontab -e

Add a new line inside the crontab file and save it. Basically you're asking to your server to run the certbot renew --quiet command every day at 04:00.

0 4 * * * /usr/bin/certbot renew --quiet

Apps management

Both applications are started as a daemon on the server. So even though the server is restarted, automatically both apps will go up.

carloscuesta.me: Uses PM2 – production process manager for Node.js.

carloscuesta.me/blog: Ghost uses ghost-cli to update and run the blog.

Nginx

I use Nginx as a reverse proxy against the applications that are running on localhost.

The first block of my configuration file, redirects all the requests to https.

server {
  listen         80;
  listen    [::]:80;
  server_name    carloscuesta.me www.carloscuesta.me;
  return         301 https://$server_name$request_uri;
}

After enforcing HTTPs, we use another server block to set our locations. Those locations will define how Nginx should handle the requests to specific resources.

As an example, if your make a request to carloscuesta.me, Nginx will match our / location and is going to proxy_pass the request to my http://localhost:PORT where the Node.js application is started.

Also, we're enabling HTTP2 and SSL for our server, providing the certificates and keys needed.

server {
  listen 443 ssl http2 default_server;
  listen [::]:443 ssl http2 default_server;
  
  ssl_certificate ...;
  ssl_certificate_key ...;
  ssl_dhparam ...;
  
  server_name carloscuesta.me www.carloscuesta.me;
  
  location / {
    proxy_pass http://localhost:PORT;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-NginX-Proxy true;
    proxy_set_header X-Real-IP $remote_addr;
  }
  
  location ^~ /blog {
     # Same as / with another port
  }
}