Converting a WordPress site to a static site using Wget

I recently made a YouTube tutorial on converting a WordPress site to a static HTML site. This blog post is a companion to the video.

First of all, why convert a WordPress site to a static HTML site? There are a number of reasons, but my primary concern is to reduce update fatigue. WordPress software, along with WordPress themes and plugins, have frequent security updates. Many sites have stable content after an initial editing phase, the need to apply never-ending security updates for a site that doesn’t change doesn’t make sense.

The example site I used in the tutorial is www.stress2012.com, a site for an academic conference/workshop that was held in 2012. It’s 2024: the site content is not going to change.

To mirror the site, I used Wget with the following command:

Continue reading Converting a WordPress site to a static site using Wget

WordPress 6.3 is incompatible with older versions of PHP

After installing WordPress 6.3, this site was broken because the new version of WordPress isn’t compatible with PHP 5.x.

I know WordPress has been complaining about this for a while, but PHP 5.x is the default version on CentOS 7, which is still supported until June 30, 2024.

I would expect that WordPress would, instead of encouraging the users on systems with old versions of PHP to apply the update, warn that applying the update will absolutely break the target website.

I’m exceedingly annoyed at WordPress. An absolutely terrible experience.

I currently have the site running on a temporary server that is a little fragile, it remains to be seen how stable it will be over the coming days.

Running WordPress on Docker

Similar to the previous post, Running Joomla on Docker, I was interested in spinning up a temporary WordPress installation so that I could target it with various scanning and reconnaissance tools. There is an official WordPress Docker image at https://hub.docker.com/_/wordpress/.

The steps were more-or-less the same. Note that if you followed the steps in the previous post, you will likely want to stop and remove the existing MySQL container before attempting to start a new one with the same name:

docker stop some-mysql
docker rm some-mysql

Start the MySQL Docker container:

docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=passW0rd -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wpP455 -d mysql:5

Start the WordPress Docker container:

docker run --name some-wordpress --link some-mysql:mysql -e WORDPRESS_DB_HOST=172.17.0.2 -e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=wpP455 -e WORDPRESS_DB_NAME=wordpress -p 8080:80 -d wordpress

I was then able to visit http://localhost:8080 and complete the web-based setup tasks.

Note that the MySQL container, as launched, does not have any shared volumes. Everything stored there is ephemeral and will be lost if the container is removed. To my surprise, however, the content survived stopping and restarting the container. The volumes for each container are located in the following directory:

/var/lib/docker/volumes/

Using docker inspect some-wordpress I could see that there was a mounted volume at:

/var/lib/docker/volumes/be3d54591da609e911a1ec3f0615a564990b37da184a67fab0ac0e75cc711c7f/_data

Indeed, the usual WordPress files, such as wp-config.php, were located there.

I did the same for the MySQL container and found the .frm and .ibd files for each of the tables in the WordPress database.

These files persist when the container is stopped, and persist even when the container is removed! In fact, when I removed all containers, I discovered there were still 22 volumes in /var/lib/docker/volumes from previous container projects and experiments.

The command to view these volumes is:

docker volume ls

To remove unused volumes, use:

docker volume prune

Container volumes are not as ephemeral as I originally thought!

Blocking WordPress scanners with fail2ban

My web logs are filled with requests for /wp-login.php and /xmlrpc.php, even on sites that aren’t running WordPress. Every one of these attempts is from a scanner trying to find, and possibly exploit, WordPress sites.

Why not put those scanners in a fail2ban jail and block them from further communication with your web server?
Continue reading Blocking WordPress scanners with fail2ban

WordPress Manual Update Instructions

One of the great things about WordPress is the one-click upgrade procedure. It’s particularly convenient, because WordPress has frequent upgrades and security updates. Without an easy way of upgrading, many users would complain of upgrade fatigue, or would continue running older versions with security flaws.

Of course, not everyone can use the one click upgrade: My host is not configured properly, so I need to upgrade manually. Fortunately, a manual upgrade is relatively painless. Although it is described in some detail at Upgrading WordPress: Manual Update, I’m listing my specific procedures (using the Bash shell) here.

  1. Download the latest version to your home directory:
    wget http://wordpress.org/latest.tar.gz
  2. Remove any old WordPress directory before unpacking the latest:
    rm -r ./wordpress
  3. Unpack the latest version:
    tar -xf wordpress-3.2.tar.gz
  4. Backup the database:
    mysqldump --add-drop-table -h localhost -u [username] -p [database name] | bzip2 -c > [site name].[dd-MMM-yyyy].bak.sql.bz2
  5. Disable plugins. You can do this via the admin interface, or the database:
    UPDATE wp_options SET option_value = 'a:0:{}' WHERE option_name = 'active_plugins';
  6. Remove the wp-admin and wp-includes directories:
    rm -r ./path/to/your/wordpress/wp-admin ./path/to/your/wordpress/wp-includes
  7. Copy only the updated files over to the WordPress install:
    cp -ru ./wordpress/* ./path/to/your/wordpress/
  8. Visit the admin page (which may prompt a database upgrade).
  9. Re-enable any plugins.

Step 6 may seem unnecessary in light of step 7, but in my experience merely updating the wp-admin and wp-includes directories is not enough: there may be old files that are not present in the latest version, but that will cause problems if they still exist.

I’ve found that my reCAPTCHA plugin doesn’t retain its API keys, but you can look them up again on the reCAPTCHA site. Other plugins may have similar issues.

Thanks to Perishable Press for the tip on disabling WordPress plugins via MySQL.

WordPress Security Tips

I attended the WordCamp Birmingham conference this past weekend to find out more about all things WordPress. WordPress is an open source blog engine/lightweight Content Management System (CMS) that has a huge community of users and developers, and an enormous repository of plugins to extend its functionality.

One of the presentations I attended, Mitch Canter‘s session on WordPress Security, had 6 good tips for making your WordPress-based site more secure:
Continue reading WordPress Security Tips