It’s 12 years ago that Jekyll burst onto the scene as a static site generator with their blog post Blog Like a Hacker.
For many this was the first time with a static site generator, and we were still in the time where PHP-Nuke was a thing. It was however an evolution of the static site generator, whose roots are in tools like Macromedia Contribute and, dare I say it, Frontpage.
The early CMSs were applications on your computer that ultimately generated HTML ready to be uploaded. Oh, how we have moved on with our JAMSTACKy world…
Over 6 years ago I wrote a post called Blog Like a Confused Hacker which introduced how solo developers and people running their own blogs could still benefit from WordPress but with static content. I updated later to talk about team management and how to build out Jekyll builds from within corporate firewalls.
These days the JAMSTACK world has changed a bit. It’s certainly become more complicated and while I was, back then, working with big organisations to bring WordPress into their network, today the JAMSTACK world is going very “enterprisey”.
More or less everything I wrote 6 years ago is still relevant today. However, today I have simplified my approach even more.
So I present you:
Blog like a confused hacker 2020 Edition.
The goal
To create a simple static site that is generated by a local copy of WordPress using minimal dependencies. By minimal dependencies I mean the only dependency is going to be WP-CLI and PHP CLI and modules you would need for WP. No Apache, Nginx or MySQL.
WAT!
Yep, we don’t need no MySQL for where we are going!
The goal is that with a single command we can launch our WordPress site, login and be ready to write, then when we hit publish our site is generated ready to go and, once we close down, it takes up no resources.
This is not an ideal scenario
Let’s be clear, we are making static sites, and there are a bunch of compromises. We are going to lose comments, forms, e-commerce and pretty much anything that does stuff. Now, there are ways we can mitigate this but lots of the plugins you are used to just won’t be available.
Also, this solution only works if one person is updating the site (not 100% true, but definitely beyond the scope of this article) so if you are a team, this is not going to work for you.
So why would we do this?
Performance. You want something to go fast? Then a static site is going to always be more performant than running a LAMP stack.
Security. I mean I’m not going to say you can’t hack this sort of stack, but it’s certainly more of a challenge.
Management. No servers, no faffy local development setup, just PHP and WP-CLI
This is the ideal setup for a small brochure site, personal blog or maybe a knowledge base/support page.
Ready?
Me too. Then let us begin.
I’m going to assume you have a working version of WP-CLI and a reasonably up-to-date version of PHP. To be honest you don’t NEED to use WP-CLI for this, you could install this using the normal WordPress methods and then just run a PHP server. But you be hackers now, so the command line you WILL use.
Seriously though, you don’t have to use WP-CLI.
You could also do this with Composer for extra hacker points, but I wanted to keep the dependencies as low as possible. However, doing it with Composer, if you have it installed, is a very smart choice.
Step 1 Download WordPress
Well I guess step 1 is really make a directory where your site is going to live, then get a copy of WordPress.
mkdir mysite
cd mysite
wp core download
Next we need to install SQLite db.php drop-in that lives in wp-content/.
Wait, what is SQLite?
SQLite is a single binary, incredibly small, simple SQL server, that is like MySQL but designed to be very small and with only very limited features. You will often find applications relying on SQLite because it creates a flat file DB structure, meaning it’s incredibly portable. It’s also cross-platform and most *nix-based operating systems (and probably Windows) have SQLite installed by default.
SQLite is not officially supported by WordPress anymore; fun fact, at one point there were WPDB drivers for SQLite, PostreSQL and MSSQL but over the years only MySQL is supported. It’s also worth saying that this means we MIGHT run into compatibility issues with plugins going off and doing their own thing. However that’s sort of OK in this use case because the number of plugins we are going to be using is minimal as we are making a static site.
Because there isn’t an official way to install SQLite the simplest option is to use one of the multiple forks. I recommend Aemonsttv version.
wget -o wp-content/db.php https://raw.githubusercontent.com/aaemnnosttv/wp-sqlite-db/master/src/db.php
Or you could download and move yourself, if you prefer. Once in your db drop-in you are ready to go. By default it will create a folder called database within wp-content, on a live site we would now be changing the location, as SQLite DB files are just plain text and encrypted but for our use case it’s fine.
Step 2 install WordPress
Normally we would use WP-CLI to do something like:
wp config create --prompt
If you want, for example, to add some extra PHP, or, for the sake of completeness you can go through and add random junk for the DB details. Alternatively you can copy this wp-config.php:
cat wp-config.php
<?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the
* installation. You don't have to use the web site, you can
* copy this file to "wp-config.php" and fill in the values.
*
* @link https://wordpress.org/support/article/editing-wp-config-php/
*
* @package WordPress
*/
/**
* Authentication Unique Keys and Salts.
*
* Change these to different unique phrases!
* You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
* You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
*
* @since 2.6.0
*/
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );
/**#@-*/
/**
* WordPress Database Table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix = 'wp_';
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://wordpress.org/support/article/debugging-in-wordpress/
*/
define( 'WP_DEBUG', false );
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
Don’t panic our DB.php drop-in automatically knows the location of our database and it doesn’t need any authentication. Again, this makes it great for this sort of thing, terrible for normal sites.
Finally let’s install WordPress
wp core install --prompt
This will ask you the usual questions, what’s your site called, what’s your email address, etc and then we are done.
You have a working WordPress site, congrats.
What? Why are you looking at me like that, oh you want to be able to “use” it, FINE
wp server
Happy now? In your browser you can visit http://localhost/:8080 and Hello World awaits.
Login, make yourself at home, it’s WordPress. You can do ctl-c to close the server, and it ceases to be.
What about Port 80
By default the PHP server binds to 8080 as it’s launched as your user on your machine rather than root. On Macs’ ports like 80 (anything below 1024) that would require enhanced user permissions to access (aka sudo) there are several ways we could get our server running just on http://localhost the simplest is:
sudo wp server –port=80 –allow-root
However, doing so will mean your WP site is now running as the root user of your machine, in the words of WP-CLI itself “If you REALLY mean to run this as root, we won’t stop you, but just bear in mind that any code on this site will then have full control of your server, making it quite DANGEROUS.”
The second option is to port forward, so direct all traffic from 80 to 8080 on MacOS Yosemite and higher (which I guess is probably most folks this is done through the pfctl tool).
echo "
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
" | sudo pfctl -ef -
We can then start the server with WP-CLI as before; wp server
but if we visit http://localhost it should now show.
The third option, just keep port 8080. If you do this make sure your home and siteurl are correctly set and include the port number.
Creating an auto-login
Let’s face it, login in via wp-admin is boring. We also are already on the command line, to “launch” the wp server, so we might as well, log straight in from the command line. The simplest solution is to use the wp-cli-login-command which is an additional WP-CLI package (so you need to install it)
wp package install aaemnnosttv/wp-cli-login-command
Which will then let you, once you set up its helper plugin.
wp login install --activate
Login via “magic link”, which is a randomly generated temporary URL, as we are the only user, we can do so with:
open $(wp login create 1 --url-only)
Disabling Comments
Out of the box, the biggest feature we just don’t need or can use is comments, so we might as well disable them. The simplest solution is the Disable Comments plugin. (https://wordpress.org/plugins/disable-comments/)
wp plugin install disable-comments --activate
Then in the admin panel, Settings -> Disable Comments, set it Everywhere. Done, comments are disabled.
But Tim, I want comments?
That is unfortunate. There are options, using third party tools is probably your best bet, the most popular is Disqus, but there are alternatives out there.
If you are planning to host on Github Pages then take a look at utterances which uses Github issues for comments.
Other options include Commento, which is not free for their hosted service but they do have a self hosted option that is built with Go and Postresql, which if you are hosting lots of small sites might work well for you.
#!/bin/bash
echo "
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
" | sudo pfctl -ef -
wp server &
sleep 3
open $(wp login create 1 --url-only)
Putting this all together
Lot’s going on here so to make it simple I have a “Launch script”. This is just a simple bash script that will sort out your port forwarding, launch server and, finally, automatically open and log into your admin area.
Saved as launch.sh you then, when you want to go into the server, just run sh launch.sh
We just made WordPress work without MySQL or Apache. High fives everyone!
Step 3 – make it static
OK, so you have a local site, and yes we have made it a bit simpler than normal, but really that’s it. Now we could just go and install Expose, which is an ngrox alternative written entirely in PHP, and serve your site to the big wide world. I mean what could go wrong?
Let’s just serve some HTML then, there are lots of static site generation plugins for WordPress. The one I recommend for projects like this is Leon Stafford’s Static Output plugin which has the balance between simplicity and it actually working. Now frustratingly, this is a Github only and normally this would be a red flag but just like the SQLite we can be a little (but only a little) more forgiving of using something on Github as it’s on our local machine.
Side note: I keep saying we can be a little more forgiving, it’s worth saying while the risks of someone abusing a vulnerability is lower because they would need to be able to reach your HTTP server which is unlikely to be achievable. However, if the plugin itself is compromised you are still running untrusted code on your machine.
We can grab the latest version from Github for me this was 6.6.23.
wp plugin install https://github.com/leonstafford/static-html-output/archive/6.6.23.zip --activate
Now one of the reasons to use the Static Output plugin is it does have a WP-CLI integration including setting options. High five! But unless you are building a script for deploying all this I would use the interface.
Initially let’s start by assuming we are going with a non-descript host i.e we are going to generate a zip file.
So within wp-admin -> Static HTML
Set Destination URL to be your final URL, so https://finalsite.com/
then hit Start Static Export and wait, it takes a bit of time. Alternatively, you can run:
wp statichtmloutput generate
This will generate your static HTML in wp-contents/uploads/static-html-output unless you have configured otherwise.
So if we want a zip version then we can do:
wp statichtmloutput deploy
Which will generate a .zip file of that folder.
It’s important to understand what this is doing, it’s crawling your site using CURL, but the site has to be up, so you need to be running wp server or it will just fail and, annoyingly, fail silently.
The result is, we now have a .zip file in the wp-contents/uploads/ folder which we can deploy and unzip anywhere we want.
Our next steps really depend on where we are going to deploy our code, which we haven’t looked at.
Where do we host the final option?
There are a few options, each with their pros/cons. You can put it just about anywhere that accepts HTML. That could be a traditional host, or more specialist setups. Personally, if we have gone to this trouble, I would be looking for a host that has a CDN network, to deploy our HTML pages as close to our visitors as we can. That provides free SSL and runs over HTTP/2. Here are a few suggested providers:
Github Pages
Github is predominantly known as a code host, but they also host static sites via Github Pages. Adding content is really only done by committing to a git repo and its features are really limited. But it’s free, which is a big thing, with no real limitations on bandwidth. Really though, I can only see this being an option for people on very limited budgets or where keeping the HTML with another project is important (documentation).
Deploying to Github Pages with Static HTML Output is one of the built-in options, so you simply select it from the drop-down menu. It will provide you with some fields to fill in, where you need to supply some keys, click the test settings and you’re done. Once set up, running wp statichtmloutput deploy will deploy it to Github.
Costs: Github Pages is free.
Netlify
Probably the best known of the static site hosting companies, Netlify has a very generous free offering, is pretty fast and very easy to set up. While its primary workflow is to use Git repos to deploy from, you can also drag and drop files to create deployments and push content via the Netlify CLI tools. Free SSL, access to lambda workers and some nice tools like a contact form submission end point. Netlify should probably be the thing you compare alternatives, such as Surge, Render, etc, against.
Deploying to Netlify with Static HTML Output is one of the built-in options, so you simply select it from the drop-down menu, it will provide you with some fields to fill in, where you need to supply some keys, click the test settings and you’re done. Once set up running wp statichtmloutput deploy will deploy it to github.
The Netlify options also give you 2 fields for generating your headers and redirects files which allow you to specify HTTP Headers and HTTP redirects respectively; it’s a nice touch.
Costs: Netlify has a free tier, and a $19/Month tier which, I will be honest, I have never used.
AWS/Google Cloud/Azure
Each has a S3-like bucket thing, that’s the technical term, but each offers a solution where you can upload static files and serve them. Each is as complicated as each other to set up and to gain maximum benefit you will need to use their various load balancing and CDN features.
The main reason I would go down this route is because it simplifies with your existing solutions, which means for the sort of project we are doing it’s unlikely you will want to go this way unless you have a lot of credits to burn.
S3 is an integration option, a little frustratingly it is hardcoded to use amazon.com for bucket URLs so it means S3 compatible services won’t work without some modification. But like the others mentioned, setting up S3 is just a case of filling in the boxes, though doing the bits AWS-side is a little more involved if you want to make use of CloudFront.
Costs: These vary but for small sites not making use of CloudFront you probably will be well within the S3 free tier. Other providers and costs vary.
CloudFlare
CloudFlare is normally used as a proxy you might put in front of a site, but it also supports static sites via its Lambda-like Workers. So you can deploy a site directly to CloudFlare CDN network. You will have to pay for this, but in many ways as the new kid on the block (when it comes to Workers, etc) the deployment process is fairly smooth done via their wrangler tool. $5+ (Minimum is $5, I would not expect almost anything but the most expensive, to exceed that)
CloudFlare is not supported out-of-the-box so you will have to use additional CloudFlare wrangler CLI which will need to be configured by adding a wrangler.toml file containing:
[site]
bucket = "./wp-content/uploads/static-html-output/"
entry-point = "workers-site"
zone_id = "...."
route = "example.com/*"
Your zone_id is the zone it’s being deployed from, the route is your site domain and then the * indicated paths will be routed accordingly.
You will also need to add your account credentials once in place you can deploy your site using:
wrangler publish
Update: Rumour has it that CloudFlare will be making all this less complicated with CloudFlare Pages in 2021.
Costs: Minimum $5 and it’s unlikely to cost you any more.
Where do I host my static sites?
My static sites are principally on Netlify and Google Cloud Storage. I would recommend Netlify as they have the balance of a great feature-set and good pricing. I haven’t needed to ever go beyond the free package, but should the need occur I would probably do it as they have been terrific for me, for the smaller HTML projects.
What’s it like Blogging like a confused hacker?
In 2014 I was talking about static sites powered by Markdown and near-seamless pipelines. The 2020 edition hasn’t changed all that much really. We simplified the technology some more though it might not seem like it. But we are missing something, where’s the Markdown?
Well, in 2014 the Markdown editor was built into my Sublime Editor and was, I will be honest, not the greatest experience. I bolted it on to make the post comparable to what Jekyll was offering. These days WordPress has an excellent Markdown editor in the form of Iceberg.
So we are now back at full feature parity with the old set-up. This I realise is a mammoth post and you may have read it and gone, gee none of this is simple. So here is the recap:
- Download WordPress
- Download the SQLite Database DB Drop-in
- Install WordPress
- Install Static HTML Output
- Done
Then for the extras we looked at:
- Turning comments off
- Turning them back on
- Creating instant login links on the command line
- Deploying to various hosts
So is it worth it?
You might now be looking at this site and thinking “hey Tim what about this site?”.
Yep, this site is built using the above stack, it sits on Netlify, uses Netlify forms for a contact form, the Newsletter is powered by Buttondown and the analytics are WithCabin.
So to answer the question, it’s certainly a much simpler way to generate a static site than a lot of the Jamstack options that are presented today. For the single person managing the site, it is fast, self-contained and really low maintenance.
It’s also incredibly portable, with the only dependency being PHP and WP-CLI. Even if you don’t create sites this way, putting WordPress behind a firewall and generating the content is still a really valid strategy.
Likewise you might find a use for running dev sites using SQLite and the WP-CLI server command.
So hopefully something of use for everyone. Go forth and blog like a confused hacker!