It’s happened - Wordpress is gone, and Hugo is now powering my blog

What is it

Hugo is a free open-source Static Site Generator (SSG). I’ve used it before for other projects, and decided to use it for my blog. It takes simple markdown files, and generates the raw HTML needed to display it.

Why did you switch

Wordpress, as awesome as it is for a typical user to create content, is always targetted for hackers. It runs on PHP, which allows executing code based on requests from a remote user. This isn’t good.

I want simple. I want to have my website look the same across all posts, and be light. My Wordpress had been transitioned a few times anyways - from a home server, to Digital Ocean, and then to two Digital Ocean droplets (one for MySQL, one for Nginx and PHP). This helped speed it up significantly, but it could still be faster.


Only getting a C grade sucks. It’s taking almost a full second to deliver less than 200 KB of data? That is unacceptable!


Getting a B is much better! This is running on the same server that was running Nginx and PHP for my Wordpress blog! 870 KB in almost a quarter of a second is much better! And yes, I know that it is currently only HTTP, but my server is going to be rebuilt soon to ONLY contain this site. Then, everything will be HTTPS.

Security reasons

First off, let’s make an assumption - if you’re in IT, you probably care about security. Whether it’s encryption, authorization, accountability, accessibility, secrecy, or find it plain FUN to secure your assets. I like security, as long as I understand what it is.

There are numerous reports of PHP being insecure. Unfortunately, it also runs on many millions of web servers around the world - it’s not going away soon. If I can close that door for potential attackers, then I will have less worry going on.

Because of how popular Wordpress is, especially for bugs, I had scripts that would run daily to get updates to themes, plugins, and the Wordpress Engine itself. It’s great for a set-it-and-forget, but what about those

Source Control

It’s no secret that I like automation. One big factor with automation is having proper Source Control, such as GitHub or GitLab or BitBucket, for examples. Using plain ol’ git, I now have this website in source control to the point I can roll back changes if I don’t like it. Let’s see that with Wordpress without extensive plugins!

Everything I need is in plain text, except for images. This is the perfect candidate for source control. Currently, my entire website can be rebuilt from its source in under 1 second! This is thanks, largely, due to automation.

What is your new workflow now?

  1. Come up with an idea to write about (no change from the old process)
  2. Run hugo new post. This will create my template file that I can rename, then edit with plain old Markdown on whatever plain-text editor or IDE I want (big change from Wordpress). I do not need to be on a browser to complete this step.
  3. When I’m ready, I test it. I run hugo server from my dev computer, and make sure the site still looks great with the new post. (different process, same step for Wordpress)
  4. When I’m happy, I change draft: true to draft: false, and commit to source control (same effect, extra benefits)
  5. When I run git push, my webserver gets the post and rebuilds the site.

This is all able to be done via the command line or your favourite plain-text editor. Programs that include Git compatibility are a plus!

What about testing?

From within Visual Studio Code (where I write my posts now), I have a terminal window available. I run hugo server, then navigate to the address specified - The site has a reload cookie to automatically reload if it detects a change. The server component listens to the directories for a change. As soon as I hit save, I see the results in my browser.

If I’m working on a draft, I can preview the Markdown files in Visual Studio Code (right-click the tab, choose Preview), or tell Hugo to render drafts as well (hugo --buildDrafts server). Obviously, this would not happen in production, but for my “dev” box, of course!

How did you do it?

I tried searching online for tutorials, but none fitted my needs. Most tutorials brought up information about using Github Pages - but this isn’t what I wanted. I had a domain, and paid for a hosted server - I wanted to use that!

The basic procedure I followed was:

  1. Ensure my Hugo files were in a Git repo on my “dev” computer. Ensure there is a .gitignore file at the root of the repo, and it has public/ in it to prevent the output from being added to source control (ideally, you’d only want the sources saved).

  2. Ensure my “dev” computer could SSH to my web server, preferrably with an SSH key. Obviously, do not commit this key to source control.

  3. Create the folder where the files would reside after Hugo processed them

  4. Ensure Nginx was installed, and pointed to the public/ folder that Hugo eventually generates. I used /var/www/talk-about-it/public for my site.

  5. Ensure the same version of Hugo is installed on both my Dev computer and my web server. In my case, I installed it from the binary offered on Hugo’s website.

  6. Copy my Hugo site folder to a temporary folder on my web host.

  7. Create a bare git repo (git init --bare) pointing to the folder I just copied. I called this folder website.git to indicate it is a bare Git repo, and placed it in my home folder.

  8. Edit the hooks/post-receive script in the bare Git repo. I put the following in this script:

    set -e
    rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
    trap "error 'A problem occured. Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT
    git clone --recurse-submodules $GIT_REPO $WORKING_DIRECTORY
    rm -rf $PUBLIC_WWW/*
    /usr/local/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}" -v
    trap - EXIT
  9. Save and exit your text editor. Run chmod +x hooks/post-receive to allow it to execute.

  10. Add the web server as a remote repository from my dev machine. You can do this from your dev machine by editing the .git/config file, and adding the following (replacing USERNAME and SERVERNAME with your actual values):

[remote "prod"]
    url = [email protected]:website.git
    fetch = +refs/heads/*:refs/remotes/prod/*

That’s it! You can test it now from your Dev machine by running git push prod master (Push the master repo to the remote target prod). In your terminal, you should see some output from Hugo. If you see that it created all the pages successfully, then you should be able to browse to it. If not, check for errors.


It’s no secret that automation is not perfect until it’s setup. Let’s work out the moving parts and figure out where the errors happen.

  1. Can you SSH to your Prod (web server) from your Dev machine using your SSH key? If not, fix this. Git won’t be able to push to the server unless it has access to push over SSH.

  2. Is the same version of Hugo installed? I ran into issues where files were not rendering at all, and it’s because I used Debian’s repo, which was many versions behind.

    Check the Hugo website for the latest version. You can also run hugo version on both machines to verify what version you’re running. If they are the same version, then you will see exactly what prod will look like on dev, and vice versa.

  3. Are your themes cloned as submodules? This allows you to get the latest versions of themes as well by running a git pull --recurse-submodules on the base of the repository. I added it into the script above, as this was another one of my issues.

  4. Is the web server running? On Debian, I can run systemctl status nginx.service to see how it’s doing. Ideally, it should be a set-and-forget, especially for serving out only static HTML files. This would be a good candidate to run Nagios checks, or similar.

  5. Can the www-data user access the public/ folder that Hugo creates? If not, you may need to alter the script to allow all users access to read files, and read-execute on folders in your webroot.

  6. Basic web checking - Does your DNS server point to the correct IP address? Does your web host have a firewall blocking incoming requests on port 80 and/or 443? Does your web host show it’s listening on those ports (check by running ss -altn, and check the outputs). Are you using a proxy that is caching the website for you, such as Cloudflare? Have you tried an Incognito or InPrivate Window in your web browser? What about curl or wget?


This challenge made me understand a lot more about Git and the actions I can have it take on demand, or automatically. I do not want to AUTOMATICALLY push to production every time I make a change, so it only re-renders on my web server (in under a second) when I push to it directly.

Using Markdown files will make my writing more universal. Plain text files will survive forever, and with Git as my version control, I only need to clone it to a private Github repo to keep it alive.

You can switch out themes pretty easily, and test them in dev before pushing them to prod. Because we’re in source control, I could choose any other point in my Master branch, and revert to that change.

Because we’re using static HTML files now for the output, my server will be able to handle more requests per second than it could beforehand.