Jekyll-built static sites are public by default. However, have you ever wanted to create a private area where you can upload articles for review and keep them from the public eye until they’re ready? That was my use case recently. Here’s how I solved this particular puzzle.
My blog is a static site built with Jekyll and the Minimal Mistakes theme. The site is hosted in a Hetzner webspace, which means that the pages are served via Apache.1 These are the assumptions I made when writing up this post. So, if you use a provider which doesn’t use Apache, or the files are different because of a different Jekyll theme, then this solution likely won’t work for you. Nevertheless, I hope it gives someone a helping hand when trying to create their own private area of a Jekyll-built site!
The two main ingredients we need are Jekyll collections and Apache Authentication and Authorization. Let’s see how we combine them to achieve our goal.
Create a “subscribers” area
The first step is to create a new path within which to put articles for reviewers or subscribers. If you’re using Jekyll in its standard configuration, posts will always have their path based at the root domain. For example, if you host your blog on https://example.com,2 and /:title/ as your permalink setting (as I do), articles will appear under their own path. Hopefully, a concrete example will clear what I mean. Imagine you have an article called “Reading books for fun and profit”. Jekyll will make the article available under the URL https://example.com/reading-books-for-fun-and-profit/. The path I’m talking about is the reading-books-for-fun-and-profit/ bit.
Jekyll has lots of flexibility in how to set up permalinks for your posts. If you’ve used the default option, then you’ll see a URL with category, date and post title. Continuing with the example just mentioned, you might see something like this: https://example.com/books/2028/02/29/reading-books-for-fun-and-profit.html. In this case, the path will be books/2028/02/29/.
Anyway, I hope you get my point about the URL paths that Jekyll creates for blog posts. What we want to do here is to create the path subscribers/ at the domain root. Then, our “subscribers” can access preview articles under https://example.com/subscribers/, instead of whatever structure Jekyll uses for posts. To achieve this goal, we’ll use Jekyll collections.
Collections are a great way to group related content like members of a team or talks at a conference.
That’s basically what we want to do; the only difference being we don’t want our collection to be publicly available. URL path protection is a separate step, so we’ll get to that later.
To create a collection, we use the collections setting in the _config.yml file:
collections:
subscribers:
output: true
With this configuration in place, Jekyll will know to look for Markdown files within a _subscribers/ directory located in the base directory (i.e. where _config.yml and friends live). Note that the leading underscore in the directory name is important: the configuration value is subscribers, but the directory name is called _subscribers/.
The output: true option tells Jekyll to process all files in the _subscribers/ directory and produce a rendered page for each file. Since each file we want to process in this directory is going to be an article for our “subscribers”, this is the behaviour we want.
Now, to start creating some content. Create the _subscribers/ directory in the base project directory:
$ mkdir _subscribers/
Then, create an article within that directory by opening a file called _subscribers/reading-books-for-fun-and-profit.md in your favourite editor. Fill it with this text:
---
title: Reading books for fun and profit
categories:
- Books
---
Books rock! You should read more of them. Did you know that libraries are quite simply *packed* with them? Mind = Blown. :exploding_head:
Building your site in development mode, you should now see your article appear under your new, shiny subscribers/ URL path:
http://localhost:4000/subscribers/reading-books-for-fun-and-profit/
That’s cool! But it could be better. You’ll likely find that there isn’t any styling, so you’ll see only plain text at the top of the page. This is not something you’d like to show off to your mates! To get the styling right, we need to extend the defaults: setting in the Jekyll config. Open your _config.yml file and append the following code to your defaults: configuration:
# Defaults
defaults:
<snip>
# subscribers' area page styling
- scope:
path: ""
type: subscribers
values:
layout: single
author_profile: true
read_time: true
Now, if you rebuild your site, you should see the article at http://localhost:4000/subscribers/reading-books-for-fun-and-profit/ styled nicely with a better layout and styling. This setup also shows your author bio information (author_profile: true) and how long it will take to read the article (read_time: true). How much those settings are only related to the Minimal Mistakes theme, I don’t know, so YMMV. In my case, this made the “subscribers” area consistent with the rest of my site.
Creating a “subscribers” landing page
With the subscribers/ path created and an article in it, it’d also be nice for “subscribers” to see an overview of the available articles in the “Subscribers’ Area”. I.e. if someone navigates to https://example.com/subscribers/, then they won’t be presented with a 404 page or similar; they’ll see a list of articles to read at their leisure.3
To make such a landing page, we need to create a file not in the _subscribers/ directory, but in the base project directory. This was something that surprised me, so hopefully you’ll now be spared that confusion. I’d expected everything associated with the new collection to be kept within that directory, but alas, I was wrong. Oh well.
Jekyll expects this file to consist of the collection name followed by a .html extension, i.e. in our case that is, subscribers.html. This file’s content is a mixture of Jekyll frontmatter, Markdown and HTML, which is a bit weird, but that’s the way this particular cookie crumbles, I guess.
Open your favourite editor and create a file called subscribers.html with this content:
---
layout: archive
title: Subscribers' Area
author_profile: true
---
{% assign entries_layout = page.entries_layout | default: 'list' %}
<div class="entries-{{ entries_layout }}">
{% for post in site.subscribers %}
{% include archive-single.html type=entries_layout %}
{% endfor %}
</div>
I gleaned this code from perusing the Minimal Mistakes theme code, so hopefully this also works for you. 🤞
I’m fairly sure I understand what this code is doing, so here’s my explanation.
We base our layout on the archive layout as shown in the frontmatter at the beginning of the file. This seems to be sensible as it is what’s used for the main “Recent Posts” page as well. Why it’s called “archive”, I don’t know, but it works, so I’m not complaining. The title key provides the page’s title, and the author_profile key puts the blog author’s bio information on the same page to keep the same look and feel as the rest of the site. Note that the styling configuration change we made to the defaults: setting above doesn’t apply to this page. This is because it’s not a file within the _subscribers/ directory, hence we have to mention author_profile in its frontmatter.
The assign statement sets the entries_layout variable either to the value of page.entries_layout or to list if page.entries_layout isn’t set or doesn’t exist. We pass this variable as the type argument to the archive-single template used later within the include statement. It also helps with the styling of the entries appearing within the <div> block, via the <div>’s class attribute. Within the <div>, we loop over all posts in the list of files that Jekyll found in the _subscribers/ directory. In other words, we iterate over all elements in the site.subscribers list. Note that it’s necessary to use the variable name post as the loop variable here because the article-single template uses this variable name internally. I probably could have written something myself which used a more appropriate variable name, but it would have duplicated a lot of code for little reason.
The loop creates an overview of the available articles, showing their titles as well as the “teaser” text for that article.4
Let’s add another article to see what the list can look like with more than one article.
Open a new file called _subscribers/books-in-modern-society.md and fill it with this content:
---
title: Books in modern society
categories:
- Books
---
Are parchment scrolls a thing of the past? Are books the future or just a
fad? We asked Pliny the Elder his opinion: "I think there is a world market
for maybe five books". Others have chimed in on this new trend with "I love
the smell of books in the morning". It seems that expert opinion is divided
on this contentious issue.
Rebuilding the development site and navigating to http://localhost:4000/subscribers/, you should see something like this:
We’ve thus created an area for a certain group of privileged people to read our articles. Great! The only problem is: anyone can read it. I.e. it’s public and not private. How do we fix that? By getting Apache to control access for us.
Make the “subscribers” area private
Now that we have a directory to protect, let’s protect it.
The Hetzner directory protection docs describe how to do this if you’re a Hetzner user. However, once you’ve gone through that process, you realise that this is just an automated way to create .htaccess and .htpasswd files in the directory you want to protect. It’s easier to create these files ourselves, rather than clicking through a GUI to achieve the same goal.
The way to do this is to create two files, a .htaccess file and a .htpasswd file, and upload them to the subscribers/ directory where our blog is hosted. The .htaccess file controls the access configuration, and the .htpasswd file contains the usernames and passwords of our “subscribers” so that only they can access this directory.
The .htaccess file looks like this:
AuthUserFile "/usr/www/users/<username>/subscribers/.htpasswd"
AuthName "Subscribers area"
AuthType Basic
require valid-user
which is a standard Apache .htaccess configuration allowing basic HTTP authentication.
The AuthUserFile option specifies the password file to use for authentication. In this case, the path is specific to Hetzner; thus, you will likely need to change the /usr/www/users component to some other value for your setup. The main point is that we specify the path to the .htpasswd file within the subscribers/ directory on our hosting provider’s infrastructure. We’ll copy the file there when deploying our Jekyll site to our hosting provider.
The AuthName value
sets the name of the authorization realm for a directory.
This value is just a name, so it’s easiest here to use the same name as the title we gave our subscribers’ area landing page.
As mentioned above, we use basic HTTP authentication; hence, the config option AuthType is set to Basic.
And of course, we require a valid user for login, hence we set require valid-user. We don’t want any old riff-raff looking at our nice preview articles!
With the configuration all set up, we now need to create a .htpasswd file. This file contains colon-separated usernames and hashed passwords for those who are allowed access to our “subscribers” area. The password hashing uses the bcrypt encryption option of the Apache htpasswd command. But first, what password should we use?
In my use case, I thought it would be best if I chose a long, random password and informed each user individually what their username and password are. It’s clear that this doesn’t scale to hundreds of users. But then, I only want to show some articles to friends for review, so this solution is ok for me. Also, if someone finds out the password for a user, it’s not that bad. I mean, whoever happens to nab this information only gets read-only access to a couple of articles. Not a major security incident. Of course, if you have hundreds of subscribers, you’ll need to come up with a much better solution!
To create a good random password, my tool of preference is pwgen. If you’re on Debian, you can install it in the usual manner:
$ sudo apt install pwgen
To create a single 20-character password, call the program like so:
$ pwgen 20 1
We then feed this output into the htpasswd program (which is part of the apache2-utils Debian package):
$ htpasswd -nbB alice password-generated-from-pwgen
alice:$2y$05$iFwBDdjNbE3sef7siS2/dOu.DqAkVhiw8zVcMLiPfE.y9tw.i8ypO
where the -n option displays the output on the terminal, -b uses the password specified on the command line, and -B forces bcrypt encryption. The two arguments to htpasswd are the username (in this case, alice) and the password generated by pwgen.
You then paste the output from htpasswd into the .htpasswd file which is located in the _subscribers/ directory. If you don’t want to do that by hand, then you can get htpasswd to update the file directly by removing the -n option and specifying the .htpasswd file explicitly.
First, create the file with touch (htpasswd requires the password file to exist beforehand):
$ touch _subscribers/.htpasswd
and set the password:
$ htpasswd -bB _subscribers/.htpasswd alice password-generated-from-pwgen
Adding password for user alice
You can add further users in the same manner, e.g. for the user bob:
$ htpasswd -bB _subscribers/.htpasswd bob password-generated-from-pwgen
Adding password for user bob
You don’t want everyone in the world to be able to read this file, so set its file permissions to remove group and world read permissions:
$ chmod 0600 _subscribers/.htpasswd
The last step in this process is to upload the .htaccess and .htpasswd files to the subscribers/ subdirectory where your blog is hosted. This process depends upon your hosting provider, so I’m not going to describe it here. I’m guessing that there’s either an FTP or rsync connection that you can use to copy your blog’s _site contents to the appropriate location on your hosting provider’s servers. You’ll have to work that bit out on your own, sorry. :-/
One thing I will note: if you want to get Jekyll to copy your .htaccess and .htpasswd files into the _site/subscribers directory when building your site, you’ll need to tell Jekyll to include them. You do this by adding these filenames to the include: directive in your _config.yml file:
include:
<snip>
- .htaccess
- .htpasswd
Now, when you copy your site to your hosting provider, the .htaccess and .htpasswd files will be copied to the correct location automatically.
Wrapping up
And that’s it! Now you have most of the pieces to the puzzle of how to password-protect a subdirectory of your Jekyll-built site. Although I can’t flesh out a complete solution,5 I hope you’ve been able to see enough of the shape of the solution to be able to fill in the gaps on your own.
I hope that helps, and I wish you luck!
I deduced this detail from the Hetzner docs, where configuration of web server extensions uses
mod_http2and redirecting a domain to a subdomain requires usingmod_rewriteand setting up a.htaccessfile. I.e. classic Apache config. ↩Look, I know it’s not hosted there, but we’ll make the assumption for now and use that as a placeholder for the actual domain. ↩
That is, of course if they’re not already reading a book, which is always difficult to avoid. ↩
The “teaser text” is the article’s first paragraph. ↩
This is because I don’t know what theme you might be using and how that impacts things nor what hosting provider you have. If you want help with this, give me a yell! ↩

Top comments (0)