One of the oft-touted benefits of the beloved static site/blog generator Jekyll is the ability to host websites generated using the tool on free platforms such as Github Pages and Netlify. So why would you want to host Jekyll-generated websites on your own server? Well, there are many reasons. For one, hosting Jekyll websites on Github Pages requires either that the site’s repository be public (meaning that you cannot check in unpublished/draft articles without them becoming browsable in the repository) or that you have a “Pro” account (which is great if it’s something you need, but with private repository hosting included with free/standard accounts many people do not). Even disregarding these points, many of us have a strong desire to retain as much ownership of our data as we can, and if you share that outlook then you too may prefer to host Jekyll on your own server.

Hosting a Jekyll website on Ubuntu Server is very straightforward, yet I struggled to find any comprehensive guide on the subject when I was figuring it out for myself this past week, so I decided to document the process for anybody hoping to do the same.

NGINX installation and user setup

As Jekyll is a static site generator, hosting it is as simple as hosting any other static site. Namely just an NGINX install, plus a vhost and webroot configuration for your domain.

For the NGINX install (on ubuntu, you can run):

$ apt install nginx

You will then want to go ahead and create a deploy user (this is not strictly necessary but it is a good practice if you are going to be scripting your deployment such as I describe below) and add this user to the www-data group (the designated group for the NGINX server user, which is also called www-date).

$ adduser deploy
$ usermod -aG www-data deploy

You will then want to create the site root directory from which you are going to serve your site’s pages and assets:

$ mkdir /var/www/yourdomain.com

And finally you will want to grant the deploy user and www-data group (the group of the NGINX user) ownership of the directory:

$ chown -R deploy:www-data

NGINX vhost configuration

In order for NGINX to serve your site from the site root directory that you just created, you will need to write a vhost configuration to tell it how it should serve requests to yourdomain.com. Below is a simple but sufficient vhost configuration to serve your Jekyll-generated website:

server {
  listen 80;

  server_name yourdomain.com www.yourdomain.com;

  root /var/www/yourdomain.com;
  index index.html

  # Cache expiry for HTTP/browser-caching (dictates what 
  # Cache-Control header value is sent). Set this to
  # a value you are comfortable with. 1 day is a good default.
  expires 1d;
}

You can save this vhost configuration to /etc/nginx/sites-available/yourdomain.com. You then will need to symlink it into /etc/sites-enabled in order to enable the site:

ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/

And then reload NGINX:

service nginx reload

Finally, point the A records for yourdomain.com and www.yourdomain.com to the server’s IP. The non-https version of the site will now be reachable at this server via those domains.

Adding SSL (via Certbot)

Since the advent of Certbot and the Certbot NGINX plugin, setting up NGINX with LetEncrypt has become even more simple than before. You no longer even need to manually create vhost files or entries for the SSL version of your site. To make your site now available via https/SSL, all you need to do is first install Certbot and the Certbot NGINX plugin:

apt install certbot python3-certbot-nginx

With these two packages installed, getting your website serving via SSL is as simple as invoking Certbot with the requisite info to verify the domain and obtain the SSL certs from the LetsEncrypt CA:

certbot -n --nginx --agree-tos -m admin@yourdomain.com -d yourdomain.com -d www.yourdomain.com

In addition to obtaining your SSL certs from the LetsEncrypt CA, running the above will update your vhost configuration file with the appropriate rules to use the certs to serve your site over SSL. Furthermore the Certbot package on Ubuntu bundles a systemd service for certificate renewal as well as a systemd timer configuration to ensure that your certificates are regularly renewed, without any manual intervention or additional scripting necessary.

One-step deployment with rsync

Even though the official Jekyll website notes in passing that you can deploy a Jekyll website using rsync, it had not initially been obvious to me that this is actually probably the best deployment solution for most people hosting a personal Jekyll website on their own server.

Below is the simple bash script that I wrote to generate your website using jekyll build and then deploy it via rsync, all in one command. You can save this to a file within your Jekyll root directory (i.e. as deploy.sh - just make sure to also add it under the excludes: list in the Jekyll config.yml so that it doesn’t get copied into the site build directory).

#!/usr/bin/env bash

for cmd in clean build
do
  echo -e "Running jekyll $cmd"
  bundle exec jekyll $cmd
done

echo -e "Deploying via rsync"

rsync -avz _site/ deploy@your-server:/var/www/yourdomain.com --delete

echo "Done."

Once you make it executable (i.e. $ chmod +x deploy.sh) you can run your deployment with a single command, with:

$ ./deploy.sh

This will run the build, and push the generated content to your server. The --delete flag ensures that any locally deleted files/unpublished posts are also removed on the server (because otherwise if you were to unpublish a post, the static files generated by previous builds would actually still linger on the server).

Automating the entire thing with Ansible

Anything I do with my servers I prefer to do via Ansible, in case I ever need to migrate and perform the same provisioning steps on a new machine. The thing I love about Ansible is how transparent it is, however I found that many guides on the subject of setting up NGINX and LetsEncrypt with Ansible were out-of-date or gratuitously complicated (because they were overly-generalized) for Ubuntu Server.

Therefore for anybody who wishes to automate all of the above provisioning steps using Ansible you can check out my example playbook that outlines how to do this.