Recently, I decided to update my personal site/blog using Jekyll. As a developer, I like to have some control over what happens on the server, setting up a vps therefore seemed like a good option, despite other hosting options such as Github pages.

Another reason for this approach is that it allows you to use Letsencrypt, a free certificate authority for SSL. Setting up a vps and configuration does require basic knowledge of the command line and text editors like vim / nano. However, if you don’t have that experience, this is a perfect opportunity to learn. This guide also assumes you’re using Mac/Linux. Let’s get started!

1. Install Jekyll

First, install Jekyll, a simple, blog-aware, static site generator. Check out the Jekyll docs for the installation requirements(Ruby).

gem install jekyll bundler

jeykyll new your_site_name

cd your_site_name

bundle exec jekyll serve

2. Create a new droplet with Ubuntu 16.04 on Digitalocean

We’ll use DigitalOcean to create our Ubuntu VPS. Create an account and head over to your dashboard to create a new droplet.

  • create a droplet
  • select ubuntu 16.04 (latest)
  • Choose a size. Because this is a simple static site, the cheapest option ($5) will do.
  • Choose a region
  • Add your ssh key (we will use this to log in to our server with ssh)
  • Name your droplet to something recognizable (yourblog)
  • create droplet!

3. Setting up server access

Your droplet is now ready, the next step is to setup ssh access and create a new user for deploying our site. Because we added ssh keys to our droplet, we can login as a root user.

ssh root@YOUR_SERVER_IP

The next step is to create a new (deploy) user with the appropriate permissons

sudo adduser deploy *this will prompt a password change

Use enter to skip the user information for deploy. Next, add the deploy user to sudo group permissions

sudo adduser deploy sudo

to switch to the deploy user run su deploy

Close the connection to the server

Now we will add ssh keys for the deploy user as well. An easy way to do this is using a tool called ssh-copy-id. Run the following on your local machine to copy your ssh keys to the server.

ssh-copy-id deploy@YOUR_SERVER_IP

This will prompt for the password you set earlier.

Test your ssh login with ssh deploy@YOUR_SERVER_IP

4. Install Nginx

On our droplet, we will use Nginx to serve our pages. On your server, run the following to install Nginx.

sudo apt-get update
sudo apt-get install nginx
sudo service nginx start

To check if Nginx is running, enter your ip address in the browser. This should display the “Welcome to Nginx” page. Alternatively, run

sudo service nginx status

5. Getting your static site on the server

  • If you would rather use Capistrano, you can skip this and read 8. Deploying with Capistrano instead.

Now we want to get our static site files to the server. The default directory where nginx finds the html you’re seeing right now is the default index.html in /var/www/html.

We’re going to change that by adjusting the default nginx configuration (using vim or nano).

sudo vim /etc/nginx/sites-enabled/default

find the line with root /var/www/html;

and change it to

/home/deploy/your_blog_name/_site

There are several ways to transfer static site files to a server. One of the simpler methods is using rsync. On your local machine, in your blog directory, run the following command to generate the _site build folder that we will transfer to the server.

bundle exec jekll build

To transfer the _site directory to our server, run

rsync -a _site deploy@YOUR_SERVER_IP:/home/deploy/your_site_name

If we go back to the server we should find our files in /home/deploy/your_site_name/_site

Run sudo nginx -s reload to see if your site is up and running!

This is a (very) basic method of getting files to the server. Later I explain how you can setup Capistrano to deploy your site.

6. Add a custom domain name

Go to your dns provider and add an A record with your server ip to your domain name.

Next, ssh into the server and open the Nginx configuration file;

sudo vim /etc/nginx/sites-enables/default

update the server_name with your domain name;

server_name your_domain.com;

Reload nginx configuration with sudo nginx -s reload, your site should now be accessible via your custom domain.

7. Free SSL with Letsencrypt

Let’s Encrypt is a free, automated, and open certificate authority. To install letsencrypt run the following commands on your server

sudo apt-get update
sudo apt-get install letsencrypt

Run the following to create the certificates, make sure you replace the path, email and domain with yours.

  • Also make sure you set the correct webroot path, when deploying with Capistrano for example the _site folder should be replaced with current.
sudo letsencrypt certonly --webroot --webroot-path /home/deploy/your_site/_site --renew-by-default --email youremail@example.com --text --agree-tos -d example.com

To get an A+ rating for our SSL security, run the following command to generate a Diffie-Hellman group. This is an extra measure to protect our site from the Logjam Attack.

cd ~
openssl dhparam -out dhparams.pem 2048

The next step is to set up our Nginx configuration for SSL

sudo vim /etc/nginx/sites-enabled/default

We divide the configuration in two blocks, one for HTTP traffic (which we redirect to HTTPS) and the other for our ssl configuration, including several best practices such as including the protocols, ciphers and the path to the dhparams.

server {
        listen 80;
        server_name example.com;
        return 301 https://$host$request_uri;
}

server {
        listen 443 ssl;
        server_name example.com;

        ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;

        ssl_session_timeout 5m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        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;
        ssl_session_cache shared:SSL:10m;
        ssl_dhparam /home/deploy/dhparams.pem;


        root /home/deploy/your_blog_name/_site;

        index index.html index.htm index.nginx-debian.html;
        
        location / {
                try_files $uri $uri/ =404;
        }
        
}

After adjusting the Nginx configuration, reload with sudo nginx -s reload

Letsencrypt certificates are short-lived, and need to be renewed regularly. We’ll add a Cron job to automatically run the renewal script.

Open the crontab with

sudo crontab -e

add the following line to the bottom.

30 2 * * 1 /home/deploy/.local/share/letsencrypt/bin/letsencrypt renew

8. Deploying with Capistrano

Our previous deploy method (rsync) is ofcourse very limited. For some more control over our deployment process we will set up Capistrano in our project. Capistrano uses Github to deploy, so start with submitting your project to Github.

IMPORTANT: Jekyll adds the ‘_site’ directory to the .gitignore file by default. We do need this in our repository, so remove this from your gitignore file.

First, make sure that Capistrano is installed by adding it to your gemfile

gem capistrano

In your site directory run the following to generate the Capistrano configuration files.

cap install

Update/add the following to config/deploy.rb

set :application, 'your_site_name'

set :repo_url, '[email protected]:your_account/your_site.git'

set :deploy_to, '/home/deploy/your_site_name'

set :ssh_options, { :forward_agent => true }

set :repo_tree, '_site'

Change the contents of the file deploy/production.rb to the following

set :stage, :production

server 'your_server_ip', user: 'deploy', roles: %w{web app}

When you deploy, Capistrano will create a /current directory with your files. The last thing we will have to do is to update Nginx to serve files from this directory.

So on your server, open the Nginx configuration

sudo vim /etc/nginx/sites-enabled/default

change the site root to the following

root /home/deploy/your_site_name/current;

We’ve setup Capistrano to use ssh forwarding, so we can deploy using our ssh key. For this to work, our ssh key has to be loaded into memory by ssh-agent. To check if your key is available, run ssh-add -L(locally). You can manually add your key with ssh-add ~/.ssh/id_rsa. To permanently add the key, update your system’s ssh_config to allow agent forwarding. Check out the Github developer guides for more info about using ssh agent forwarding.

Now to deploy your site

bundle exec jekyll build
git commit -am "message"
git push
cap production deploy

Because the Nginx configuration was changed an extra sudo nginx -s reload on your server is necessary to reload changes.

That’s it!