Unfortunately for various reasons some of the websites I host no longer have their humans on hand to maintain them and update their content etc
To save on cost and to minimise risk from unmaintained websites I’ve migrated several of the Wordpress sites I’ve been hosting in to static ones.
Background#
The sites I’m migrating have a standard Wordpress setup which typically have the following components:
- Base Operating System ( Linux )
- Web server ( Apache in this case )
- PHP
- Wordpress installation
- Database
Left alone most of this would work fine however as with anything connected to the internet they will require regular maintenance and updates to avoid security issues.
Baring the Operating system and a cut down version of the web server all the other items could be removed from scope if the site were static and not dynamically generated.
Dynamic vs Static#
Dynamic#
A dynamically generated website, such as a Wordpress site, typically generates the pages you see on the fly based on information stored in its database.
This info may include templates, images and text. In its default configuration this would be generated on every visit to the page.
This process would need to read the source files from disk, execute the code, access the database, perform tasks, generate the page and then send it to the user.
In the case of Wordpress its developers are very skilled and have created some very efficient code to do this which typically completes in way under a second.
Static#
A Static site generates its pages once and saves the resulting pages to disk where the web server serves them directly from there. ( Just like this site )
Here the web server just needs to read the source file from disk and send it on to the user accessing the page.
The Process#
The websites I want to convert are live websites and although are low traffic I’d rather not interfere with a running site.
Backup#
I’ll need to backup the live site first. Mistakes can happen so I’ll need something to rollback on to should it go wrong. The backup is also a useful copy of the site I can play with
Mysql#
A standard mysqldump
of the site will do. I’m also going to compress the resulting file for quicker transfer.
mysqldump mj-wp |gzip > mj-wp-sql_Backup_202503.gz
WordPress installation#
A straight copy of the working directory will be OK here:
cd web/mj-wp/htdocs
tar -zcf mj-wp_Backup_202503.tar.gz .
Replicating the setup#
Container setup#
I’m going to use docker for ease of explanation here but podman would work too, just don’t forget to expose some ports!
I’ve not created a specific container image for this process and will handball the installation of all the bits needed to make it go.
docker run -it --name jeremy -p 80:80 -p 8000:8000 debian:latest
add --rm
to your docker command for an extra element of risk!
You don’t have to use jeremy
as a container name. If you prefer something else specify it here.
If no name option is provided here then docker will make one up for you. Use docker ps
to discover it.
Copy the backup files from the live server to the docker host machine and then in to the container:
docker cp mj-wp_Backup_202503.gz jeremy:/
docker cp mj-wp_Backup_202503.tar.gz jeremy:/
Install supporting software#
In the container I’ll need:
- Tools to help me do things
- Apache server with PHP installed
- Database server
- A copy of the backup
PHP also needs to be able to talk to the database so extra PHP modules will need to be installed. ( making a note of this because I always forget )
apt-get update -yy
apt-get install -yy vim wget curl tmux tree zip
apt-get install -yy apache2 libapache2-mod-php php-mysql php-gd php-bz2 php-zip php-intl php-imagick php-xml php-curl php-mbstring php-readline python3
apt-get install -yy mariadb-server mariadb-client proftpd
note: proftpd may not be needed in your setup but was included here for completeness
Starting and stopping services#
This container doesn’t have systemd by default so you’ll have to rely on the old initrd style service control.
To start and stop services run the following commands:
/etc/init.d/<service> <action>
Where
<service>
can be either mariadb
or apache2
and <action>
can be start
, stop
, restart
Configuring apache#
Apache will need to closely match the config of the live site so remove the default site and add in a new one.
Ensure that the ServerName
and ServerAlias
settings of the site are set the same as the live site
The document root can be anywhere within the container so long as apache can read it. I chose the default location of /var/www/html
Note: Don’t forget to restart/reload apache when you make changes to the config
Domains and DNS#
As there’s still a live site in play I’ll need to make sure the machine I’m doing this work on is always looking to the container version of the site and not the live site.
I can do this by either editing /etc/hosts
or by altering the DNS server I’m using.
Spoiler: I had to do both!
Assuming the ports have been forwarded from the container the following host file entry should suffice:
127.0.0.1 mj-wp.co.uk www.mj-wp.co.uk
To test this is working correctly I can run host mj-wp.co.uk
on the machine I’m running this from ( not within the container ).
There are additional protections on the /wp-admin
URL on the live server so accessing this URL can also give me a hint of if I’m accessing the correct site or not.
Extract the backup#
The files will need restoring to the DocumentRoot
so:
cd /var/www/html
tar -zxf /mj-wp_Backup_202503.tar.gz
At this point I’m also removing any specific access rights etc as defined in the .htaccess
files dotted around the place.
The site should work as is without any file permissions changes at this point.
Setup Mysql#
First create a database.
Type mysql
and press enter
Then run the following command:
create database mj-wp;
Now the database has been created; create a mysql user for the site. Check in the wp-config.php
file for the correct information
Example command:
GRANT ALL PRIVILEGES on mj-wp.* to mj-wp@localhost IDENTIFIED BY 'password';
Don’t forget to do FLUSH PRIVILEGES
after a successful run of the grant command.
Restore the database#
Restore the backup to the new server:
zcat /mj-wp_Backup-202503.sql.gz | mysql mj-wp
If all has gone well you should now be able to access the website via your browser
Preparing WordPress#
WordPress should be working normally at this point 🎉
If however its been left alone for a while it will require some updates before you can proceed
Login to the control panel at /wp-admin
and head over to the updates section.
Follow the prompts
File permissions and updates#
WordPress can either update the files itself directly or via FTP if available ( which is why I included proftpd in the setup :) )
If WordPress is asking for ftp details you may have to make the following changes:
Edit the wp-config.php
file and add the following:
define('FS_METHOD', 'direct');
define('FS_CHMOD_DIR', 0755);
define('FS_CHMOD_FILE', 0644);
Also ensure your file system permissions are correct. In this setup apache runs as the www-data
user.
Make use this user has appropriate permissions on the /var/www/html
directory.
Follow the update process through. Plugin updates may also be needed if you change WordPress versions or if they are badly out of date. Ensure you check the site for functionality after each update. … Especially themes!
Simply Static#
Use the Plugins section of the admin interface to install a new plugin named Simply Static by someone called Patrick Posner
. At the time of writing the plugin version is 3.2.7.1
Once installed activate the plugin.
A new Admin menu will appear named Simply Static
. Clicking on it will take you to its setting page.
At this point clicking Generate
will create a zip file containing all the needed static assets for the site.
Configuration#
There is very little to configure here for a basic site. Other options are available for more complex sites and needs however you may need the pro version of the plugin for this.
The only option I changed here was under the deploy
section.
Here I changed the deployment method to be Local Directory
and below this set the path
option to be /export
.
Click Save to save settings.
note: You’ll need to allow www-data
to write to this directory too
When ready click the Generate
button. Status of the output can be seen in the Activity Log
section.
Testing#
In the container head over to /export
and run the following command:
python3 -mhttp.server
This will spin up a web server in that directory on port 8000.
Browse to http://localhost:8000 to view your newly generated static site.
Ensure you test it well before replacing the dynamic site on the live server.
If anything doesn’t look right make changes in WordPress as needed then re-generate the static files.
Deployment#
If you’re happy with the new static files Copy them to the server.
First tar them up:
cd /export
tar -cf /export.tar *
Copy this file out of the container to the container host system:
## On the host system
docker cp jeremy:/export.tar .
Then transfer the file to the live server.
🎉
Troubleshooting#
This process wasn’t as smooth as this post would appear.
File Permissions#
This was quite a headache initially before letting WordPress look after itself with the config file changes. Doing this on a live server does make me apprehensive however.
During testing the classic chmod 777 * -R
was thrown around a fair bit
DNS#
Its always DNS!
The last time I performed a conversion on a site this wasn’t an issue but it seems there may have been some changes to the WordPress cron system. Any jobs activated on the contained version of the site wouldn’t work at all, this also included the generate option from simply static. Simply Static would simply mention in its debug log that the job had been entered but there was no mention of it being started as shown in the doc’s and other examples.
After many hours debugging I noticed errors appearing in the logs on the live site. They were errors because the live site didn’t have Simply Static installed so didn’t know what to do with the call. 🤦
This is down to something I had forgotten…
Unless otherwise specified at the command line docker doesn’t pay attention to the host’s host file ( /etc/hosts
) or any custom entries in it even if its added in before hand.
You must specify any custom host names and IP’s on the command line when the container is first run:
docker run -it --add-host=example:1.1.1.1 debian:latest
As I had not done so the fix was to modify the DNS server the docker host machine was talking to to return the 127.0.0.1
IP address for the site in question.
When this was done the generation process worked correctly as expected.
Simply Static#
Because of the DNS issues above the plugin would get stuck in the Generation phase and there would be no info sent to the activity log page.
Instead, enabling the debug log in the plugin options and looking in to wp-content/uploads/simply-static
at the xxxxx-debug.txt
files gave some hints about what was going on.