Amateur radio and embedded systems

Migrating to Pelican

Returning readers: please update your feed reader with the new RSS or Atom URL.

Last updated 06 Aug 2023.

I decided to migrate my blog from Wordpress to a static site generator. My main motivation, apart from just trying something new for the sake of it, was the endless stream of Wordpress updates and security issues. Every time I logged into the admin panel there was something asking me for an update. I still feel quite fortunate not to have anything break after an update and not having my site hacked or distributing malware. I do not need any dynamic content, cookies, tracking and spyware, I do not need a fancy WYSIWYG editor so a static site makes perfect sense. My hosting account provides a limited number of PHP interpreters so from time to time I have seen an occasional HTTP 500 error when Wordpress scripts were taking too long to execute.

After a quick research I settled on Pelican. I was already familiar with Python so found it easy to comprehend and work with. Here are my migration notes and quirks.

Import from Wordpress

I imported my old posts from Wordpress to Markdown using official Pelican documentation. There were no erros but I had to rearrange the files, attachments and tweak the text a little bit. I made a directory for each year and placed articles from that year in the directory. When an article had attachments I also made a directory with an identical name to the article slug and placed the attachments there (slug is specified in every article file. The slug does not have to follow the filename but it is least confusing when it does).

Of course I had to fix attachment links and article-to-article links. Imported code blocks also had some HTML entities that were not necessary and had to be removed. Overall I would say that the import went quite okay but still required lots of manual work and proofreading.


I reduced the number of categories so that the menu fits one row. There were not that many posts in each of them. I had to install a Pelican plugin to support articles being members of multiple categories (pip install pelican-more-categories) and enable it in the PLUGINS config variable by adding 'more_categories'.

I also had to add USE_FOLDER_AS_CATEGORY = False to pelicanconf.py so that the folder structure does not automatically generate new categories. Without it I would have for example categories for every year. Right now the "All posts" page takes care of displaying the complete archive.

Theme & CSS

I picked the minimalistic Peli-Kiera as the theme for my blog. I only needed support for Atom & RSS feeds, Github link and... that is it. I like focusing on the actual content rather than eye candy. This is also the moment I discovered that Pelican comes with "no batteries included". You have to work out all the settings that work for you yourself. However, small changes are pretty easy due to the overall simplicity. There is no magic and no multiple layers of abstraction as in Wordpress.

Line numbers in code blocks

To enable line numbers in code blocks I added this piece to pelicanconf.py:

  'extension_configs': {
    'markdown.extensions.codehilite': {'css_class': 'highlight', 'linenums': True},
    'markdown.extensions.extra': {},
    'markdown.extensions.meta': {},
  'output_format': 'html5',


Reducing image width

The theme has a single main.css where everything is stored. By default the images were enlarged to fit the width of the container. It did not look well for smaller images so from the .image section I removed width 100%; and added max-width: 800px; instead.

Categories menu

This element needed the most adjustments. First of all I did not want the currently selected category to be highlighted by moving its text upwards so I commented out the #container nav ul li a.active section.

Second, I wanted the category menu to wrap when the screen is narrow (like on a phone). #container nav ul got flex-wrap: wrap; justify-content: center; and #container nav ul li got float: left; nth-child(4) {clear: left;}.

Main container width

I found the default max width of the blocks of text too small so I increased them in #container using width: 900px;.

You can download the css file from my site and diff with the default one from the theme.


I picked a chip icon from Icons8. I put it in extra directory (alongside robots.txt) and added to Pelican static files using:

STATIC_PATHS = [ 'extra' ]
    'extra/favicon.ico': {'path': 'favicon.ico'},
    'extra/robots.txt': {'path': 'robots.txt'},

and added the link required by the free icon license Chip favicon by <a href="https://icons8.com/">Icons8</a> at the bottom of base.html of the theme.


To play nice with search engines I installed the sitemap plugin (pip install pelican-sitemap) and added to pelicanconf.py:

PLUGINS = ['sitemap', 'more_categories']
    'format': 'xml',
    'priorities': {
        'articles': 0.6,
        'indexes': 0.5,
        'pages': 0.4
    'changefreqs': {
        'articles': 'weekly',
        'indexes': 'weekly',
        'pages': 'monthly'

Then, I created robots.txt and placed it in extras directory (alongside the favicon):

User-agent: *

Sitemap: https://m0agx.eu/sitemap.xml

This tells robots to only fetch the XML sitemap instead of crawling the whole site. This is only a sitemap for robots. List of all articles is described in the next paragraph.

Page with a list of all articles

This feature in Pelican is called an archive. To have a page with the list of all posts I used:

ARCHIVES_SAVE_AS = 'pages/archives.html'
MENUITEMS = [ ('All posts', '/pages/archives.html') ]

in pelicanconf.py. This is the equivalent of WP Sitemap Page plugin.

HTTP redirect for feeds

As a heavy RSS user I did not want to leave my readers behind with broken feed URLs so I set up a HTTP redirection. A slight complication is that Wordpress has many feeds. Like: feed per site, feed per category, feed per article, feed for comments etc. Pelican has only a single feed so I decided to redirect all possible feed URLs to the single one from Pelican. But first, how to find which feed URLs are actually in use?

I searched my access logs with grep feed /path/to/access/logs/* | awk '{print $7}' | sort | uniq. This one-liner searches for all lines that contain "feed", extracts the 7th column (which is the URL in my web server logs), sorts them, and removes duplicate entries. The pattern I spotted is that the URL always contains at least /feed/ so a simple match rule can capture all combinations. I redirected them by putting RedirectMatch ".*/feed/.*" "/feeds/all.rss.xml" to .htaccess. This page was very useful. .* means any number of any characters and /feed/ is a literal text. Using this trick any URL, like /anything/feed/more/of/anything, will be redirected to the new feed.


The equivalent of Wordpress' <!--more--> is <!-- PELICAN_END_SUMMARY --> with the minchin.pelican.plugins.summary plugin.

Scheduling posts

Pelican does not have any facility for scheduling posts but this can easily be done with a small shell script.

Missing features

E-mail subscriptions

Finally: I had e-mail subscription feature on my old site. Unfortunately I can't find any easy solution for Pelican (since it is a static site). I think I will have to roll out my own solution that will be executed on the "build machine" and maybe a trivial PHP frontend to handle subscribes and unsubscribes.