Moving from WordPress to Hugo plus Caddy

I have never been very happy about using WordPress for my blog. Granted, that is mostly because I’ve been running it on my home server. Which means keeping Apache, WordPress and PHP up to date. Not to mention configuring that stack of software. Especially doing things like adding rewrite rules to Apache to minimize the risk that I would be hacked by someone trying to exploit WordPress/PHP vulnerabilities. And over the years I’ve had several breakages when I’ve updated my OS or any of the aforementioned packages. Perhaps I’d have a better opinion of the WordPress platform if I outsourced all that work to or another commercial provider. But I don’t like to yield that control to third parties and definitely did not want to pay for it.

I grew tired of all the work to keep WordPress running on my personal server. Especially the effort to minimize the risk of my web server being hacked and used to distribute malicious content or be part of a bot army. The answer was obvious: switch to a static site. The only question was how to manage the content. I looked at several options including Jekyll. I eventually decided on Hugo.

Thus I recently bit the bullet and migrated my content from WordPess to Hugo. I initially did a full XML export via the WordPress admin interface and used the CLI tool exitwp-for-hugo to convert the content to a form that Hugo could use. The results were disappointing. So I tried the WordPress wordpress-to-hugo-exporter plugin with much better results.

After exporting my WordPress content I needed to do a couple of transformations. One was to add an “aliases” metadata entry to each document to map the “/yyyy/mm/doc_name” URI that was the canonical URI under my WordPress config (and which was indexed by web crawlers like Google) to the canonical “/posts/yyy/mm/doc_name” URI under Hugo. I also needed to remove the “url:” metadata entry from each document. Finally, I needed to move each document to the appropriate pathname for Hugo. I did this via a straightfoward script:

for f in *.md
    set x (string split -m3 -- - $f)
    mkdir -p ~/blog/content/posts/$x[1]/$x[2]
    awk '
    /^url: / { print "aliases:"; print "  - '$x[1]/$x[2]/$x[4]'"; next }
    { print }
    ' $f | sed -e 's,,,g' \
        -e 's,,,g' \
    > ~/blog/content/posts/$x[1]/$x[2]/$x[4]

Sorry about the use of fish rather than, say, bash but I’ve been using fish for such things for a few years.

I then started the Hugo builtin web server:

hugo server --port=8081 --baseURL=

Using my web browser I verifed that I could fetch my blog posts. Once I had done that it was time to pick a theme for my new Hugo based site. There are a huge number of Hugo themes. I’ve tentatively settled on “whiteplain”. Mostly because I like its simple, minimalist, style that also provides URLs compatible with my old WordPress based site. In particular /page/..., /category/... and /tags/... style URLs. This means that existing web crawlers can continue to fetch those URLs.

Once I was happy with the migration of my blog posts from WordPress to Hugo it was time to decide how to make the new content visible to the world. I originally intended to use the Hugo builtin web server. But it was quickly obvious that is not a viable option because it does zero logging of HTTP requests. So I looked at Caddy. OMFG! All I had to do was create a config file with a single line,, and start the web server: caddy -conf ~/etc/caddy.conf. A few seconds later I had a web site with SSL/TLS/HTTPS support plus HTTP/2 support. Caddy automatically took care of setting up my SSL site certificate with the Let’s Enscrypt project.

Yes, I’ve glossed over some details. Such as how to convert the raw Hugo content to set of files servable by Caddy. With my raw content in ~/blog and my public web site in ~/www it was a simple matter of doing cd ~/blog then hugo -d ../www. And, obviously, telling Caddy to serve the content in ~/www by making that the PWD when starting the Caddy server.

I still have some work to do. Such as providing a way to search for content. I also haven’t decided if I want to provide a commenting facility. I had comments enabled under WordPress and it mostly resulted in trolls and butthurt Xtians posting unconstructive comments. If I do decide to enable a way to make comments on posts I’ll probably use Disqus.

Still, I’m really happy with this change from WordPress to Hugo+Caddy. I no longer have to worry about being hacked due to PHP or WordPress security vulnerabilities. I can now use the same text editor I use for modifying source code (I’m a software engineer) for writing blog posts. The CPU and memory load on my home server from hosting a blog is approximately two orders of magnitude less based on a few quick tests. Using Apache, PHP, and WordPress to serve what is essentially a lot of text (i.e., blog posts) is incredibly inefficient.