Reading Time: 8 minutes

As you may have noticed, I’ve kept the default first entry just because. Here’s a true description of how my first run went.

Why Jekyll?

  1. I wanted something KISS, just place lean markup somewhere and have it converted to static html files, no more. In fact, my main page already does this with my own PHP code, but this time I didn’t want to waste time to write something that cares about time and date of posts etc etc.
    Plus, as part of the KISS principle the resulting HTML code should not be a cluster of eye-defiling div soup.
  2. Being able to easily git my page
  3. I already saw a blog using Jekyll so I figured I’d give it a go.

A short look revealed:

  1. No funky daemon* with yet-another-fancy-application-behind™ tearing possible additional holes into the web server host
  2. No super-fancy DSL requiring you to learn Vendor® Software™’s own language/dialect, tying you to the product
  3. The above: place files, render, go home

* yes, I am aware that jekyll can be run as a web-app daemon - but as they themselves state this is only for development purposes. I for myself will occasionally use jekyll build –watch in a systemd service while I do lots of changes, and periodical execution of build without –watch the rest of the time. How to ease that approach: see “Publish the Site” later in this post.

Let’s go, then!


Pretty straightforward. Many how-tos of course suggest manual gem pulling and stuff, but I rather keep my things updated through repositories. In case of Debian (which this first installation runs on), this is a simple

sudo apt install jekyll{,-theme-minima}

Point out a directory where jekyll should “live”. Remember that many systems like Jekyll live off a base and publish the results to a different directory, typically a sub-directory, so in my case I went to my web-app base directory, and started with:

sudo mkdir /web/app/base/blog && \
sudo chown editing_user:www-data "$_" && \
sudo chmod 02750 "$_" &&\
cd /web/app/base/ &&\
jekyll new blog # the directory blog/ needs to be empty at this point

You can then switch to the directory and a

jekyll build

creates the static HTML pages.

Finally, point your web server to the resulting _site subdirectory and that’s it.

Jekyll lets you customize source and target directories among a lot of other stuff through parameters or configuration files – but for that, there’s manuals.


As an example, let’s add a TOC (Table of Contents) plugin.

sudo apt install ruby-jekyll-toc

Then, we need to include this in the jekyll source directory’s Gemfile (/web/app/base/Gemfile):

# If you have any plugins, put them here!
group :jekyll_plugins do
  gem "jekyll-feed", "~> 0.6"
  gem "jekyll-toc"


Now, you certainly have noticed we installed the minima theme. Without further ado, the blog comes in a neatly styled theme that does the trick.

I wanted to integrate this into my main site, however, which comes down to:

  • a custom <nav> at the site’s top
  • a custom <header>
  • styling the main content like the rest of the page (like I did with cgit)

After a bit of tinkering about, I eliminated minima’s CSS (i.e. removed it from the <head>) and went on to adjust the templates and my own site CSS. To give an overview into the first insights:


There’s only one base concept how template files work: there are defaults all over the place and user custom files. That is, if you have a folder _includes with two files header.html and footer.html, and the user creates only folder/header.html in their customization structure, footer.html should still come from the default folder and header.html should be the user’s.

Sounds straight-forward, not all CMS or ticket systems or or or do that or at least did that to a point. Jekyll works this straight-forward way.

Since minima is a rubygem and we used a repository package to acquire that, the theme defaults are located in:


With that knowledge, we may tweak single files. I replaced the content of header.html entirely, moved parts of it to the footer replacing all email address and twitter hullaballoo there, and that’s most of the structure.

mkdir /web/app/blog/_includes && cp -p <rubygems-minima-folder>/header.html "$_"
vim /web/app/blog/_includes/header.html
# and so on

Also, head.html (not header.html) is where my new CSS went, to answer the question for “where’s the site’s own CSS?”.

Configuration and template example: dates

There’s two approaches here, and I used them both :-)

Goal: I wanted to change the dates to roughly resemble ISO8601/RFC3339.

The standardisation approach: _config.yml

Variables! Grepping around for HTML elements of the main page’s blog entry dates and the posts’ header dates I found _layouts/post.html and _layouts/home.html. For the post listing in the blog’s home page, I chose a shorter format and for the single post details a longer one. I chose the shorter format for my default.

Within home.html we find this:

{%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}

date_format is then used in any post, so we may want to adjust that. This brings us to variables. Configuration is done through said _config.yml. It is created on jekyll new with rudimentary content, and for further options you may want to check the documentation. One insight, however, is this:

If you want to adjust the above site.minima.date_format, it’s important to know that site is a dict that is populated with the contents of _config.yml. Took me a few minutes to figure, I’ll save you that hassle. As this is YAML, I then put this into the file:

  date_format: "%a, %F"

I still wanted to have the long format, so I just defined an individual variable for that (the name is made up by me):

date_to_rfc3339_long: "%a, %F %H:%M:%S (%Z, %z)"

Why an individual variable? None of the time formats in the docs satisfy my needs, however, we can use any formatting from strftime(3) as I do above.

Example _config.yml
title: "  of linux, sysadminism and luls"
email: ""
description: >- # this means to ignore newlines until "baseurl:"
  This is my personal blog. As I'm a Linux and automation based sysadmin,
  most of the content is about topics from that field. The key descriptor,
  however, is personal - it's the part of a notepad that you can present
  to the public and which I do, here.
baseurl: "" # the subpath of your site, e.g. /blog
url: "" # the base hostname & protocol for your site, e.g.
languageCode: "en-dk"
timezone: "Europe/Busingen"
encoding: "utf-8"
quiet: True
date_to_rfc3339_long: "%a, %F %H:%M:%S (%Z, %z)"
  date_format: "%a, %F"
highlighter: "rouge"
markdown: "kramdown"
theme: "minima"
  - "jekyll-feed"
  - "jekyll-toc"
  min_level: 2
  max_level: 4
  list_class: "toc"
  list_class: "toc__list"
  sublist_class: "toc__sublist"
  ordered_list: True
  - "public"
  - "nocontent"
  - "nav.html"
  - ".git"
  - ".gitignore"

The template customization approach: post.html

We can then proceed to copy the original post.html into a _layouts subdirectory of our Jekyll source and do this:

{% comment %}{{ | date: date_format }}{% endcomment %}
{{ | date: site.date_to_rfc3339_long }}

I’ve commented out the original line, used my variable from _config.yml as site.variablename, and that’s the whole magic. This post’s header already shows our new format.

Any template language like Liquid (which Jekyll uses) or Jinja (Python, Ansible) work like this:

  1. You basically just write down the content of the resulting file.
  2. Variables known to the surrounding code and configuration are addressed as {{ variable_name }}
  3. Programming logic like if, loops, … lives inside {% these %} (similar to <?php code ?>) – anything else is not interpreted

And that’s it. Take a look at the default first entry and adapt using the documentation to enrich the document header and markdown for the content.

The Markdown

After this wall of text, this is a rather short remark: Jekyll’s default renderer is kramdown which already supports GFM (Github Flavoured Markdown) which suits all my needs.

Syntax highlighting

Jekyll uses Rouge by default. I’m fine with that and the default appearance (the latter at least for now). To dig a bit deeper into styling that, you may want to use this blog post as a kickoff.

Publish the Site

Now you know basics about the templates and where to customize, and maybe you’ve already done your first post inside _posts/. Apache looks inside _site/, but alas, no change.

There’s no more to do than a

cd "$JEKYLL_SOURCEDIR" && jekyll build

Been there, done that. Now all we need to do is make this git revisioned! You may have already noticed that jekyll places any file inside the source directory into the target directory save for special locations which are converted (yes, like _layouts and _posts). You can tell Jekyll to ignore certain locations in _config.yml:

  - .git
  - .gitignore
  - nekkidfolder/

With that in mind, we now can safely make this a general process.

Option 1: Live publishing

jekyll build itself will already have told you that it has a –watch parameter for live processing. We can use that for a systemd service:

# /etc/systemd/system/blog-livegen.service
[Unit] live generation

ExecStart=/usr/bin/jekyll build --watch


Disadvantage: there’s a ruby process running all the time. So why not just periodically fire that up?

Option 2: Periodic publishing

This still frequently regenerates the site without a process idling around in the meantime that offers more attack surface.

Rename the /etc/systemd/system/blog-livegen.service from above to /etc/systemd/system/blog-gen.service , remove the word “live” from the Description, remove the --watch parameter from ExecStart and remove any line below Group=www-data. Now the service can’t be enabled anymore but still be triggered manually or by a timer. Here we go:

# /etc/systemd/system/blog-gen.timer
# the timer file MUST have the same name as the service file it triggers
[Unit] generation

OnCalendar=*-*-* *:0/30:*


This starts the site generation every 30 minutes with a random delay of up to 5 minutes for less predictability (people shouldn’t be able to exactly predict the runtime, the logs still will show what has happened when in the past).

Now, we can

sudo systemctl enable --now blog-gen.timer

and within up to 35 minutes tops any change is live.

The next step would be to automate pulling the git source so you can push from no matter where, but that is not in scope for a jekyll how-to anymore (in fact, we already went a bit astray with the systemd explanation ;-)).


It took me longer to write this blog post than to set up Jekyll. (Also, CSS adjustments were more labourous as well.) So yes, KISS is fulfilled, and I’d say the resulting HTML code is clean enough. No div soup, no funky Softwarename™ code garbage, mostly just converted Markdown. GGWP!

Also, we already gained a few pointers towards theming and templating.