I’ve had codeheir.com hosted using WordPress for the past 6 years, and it costs a disproportionate amount when compared to my other websites. I’ve been a bit lazy and just paid the bill, but now, it’s time for a change.
I took a backup of the website using a WordPress plugin WPVivid, I stored this on disk with the plan of uploading it to my new self-hosted server.
Okay, so I’ve got myself an Ubuntu server, now what? Well, what do we know? My website uses the LAMP stack, which is Linux, Apache, MySQL and PHP.
Let’s get an Apache web server running and go from there: sudo apt install apache2
And start it at system boot: sudo systemct enable apache2
. That’s enough to get the classic Apache server page:

Now, I’m going to need a database: sudo apt install mysql-server
and the same as Apache sudo systemctl enable mysql
. I’ll also run sudo mysql_secure_installation
, which helps to quickly secure a fresh db installation.
Now I need PHP and some extra modules: sudo apt install php libapache2-mod-php php-mysql
to allow it to work with MySQL and Apache. And a few more extensions: sudo apt install php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip
.
Next, I need to install WordPress. In the root directory for the web app /var/www/html
I’ll run sudo wget -c http://wordpress.org/latest.tar.gz
and extract these files sudo tar -xzvf latest.tar.gz
.
Now I can go to http://codeheir.com/wordpress
and see the website.
Time to create the database: sudo mysql
to get in, and CREATE DATABASE codeheir
and then create a user for it: CREATE USER demo_user@localhost IDENTIFIED BY 'somepass';
Followed by FLUSH PRIVELEGES
and exit
to scadaddle.
Set up and configure WordPress
I don’t like having to go to codeheir.com/wordpress
so I pulled all the files up a level to /var/www/codeheir
I’ve installed everything I need; it’s time to point my WordPress config to the database. In the /var/www/codeheir
folder there’s a wp-config.php
file that needed updating. I went to the WordPress admin panel and added the plugin for backups and clicked the import button.
I now have my blog being served, but many things are wrong. Firstly, the theme I use isn’t free and requires WordPress to host the site. I’ll change it.
Okay, now I’ve got a theme, but I’m not able to click into any of the links to the blogs. I need to add a rewrite rule to my .htaccess
:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
This performs a URL rewrite, e.g, if you go to /about-us
This file doesn’t actually exist, so it’s rewritten to /index.php
, which does exist.
Missing images
I can now navigate the website, but I’m getting a load of failed requests for images that no longer exist. That doesn’t really make sense. I’ve exported and imported my entire website; how can I be missing images? Well, it turns out themes create thumbnails for different image sizes.
E.g. the following image fails: /wp-content/uploads/2024/12/image-2-488x488.png
. When looking at the folder on the server, the only image that exists is image-2.png
not the specific one with the size appended.
I tried lots of different solutions to this. I tried manually using the WordPress CLI to do find-and-replace, and I tried updating the database directly. They all work, but there are just too many broken images to fix manually. Luckily, there’s a plugin for this exact problem: regenerate-thumbnails. Sadly, the author of this plugin died in 2019, preventing headaches from beyond the grave. Rest in peace, mate!
Certificates
Now that I’m hosting myself, I’ll need to create a TLS certificate. I’ll use Let’s Encrypt, the open-source Certificate Authority that issues SSL/TLS certificates. Let’s add Certbot sudo apt install certbot python3-certbot-apache
.
sudo certbot --apache -d codeheir.com -d www.codeheir.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for codeheir.com and www.codeheir.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/codeheir.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/codeheir.com/privkey.pem
This certificate expires on 2025-12-22.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for codeheir.com to /etc/apache2/sites-available/codeheir-le-ssl.conf
The Certificate should auto-renew in 90 days, that was simple enough.
Broken links
This blog has always been riddled with a number of broken links. From people commenting and attaching a website that no longer exists, or from old articles I’ve referenced that have since been removed. I’ve generally been quite lazy at resolving them. I added the Broken Link Checker plugin and got to work:

I manually fixed all the links; the plugin had an “unlink” button, but that didn’t work. You can set this to run every day, as I have, so you can handle broken links one at a time.
Security headers
A quick Security Header scan gave me a very poor score:

I added the following to /etc/apache2/sites-available-codeheir-le-ssl.conf
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
That’s better:

I’ll work on the Content-Security Policy (CSP) a little later. There are CSP tools you can use to scan your website and whitelist the various sites you need.
Backups
Using the same tool as I used to export WordPress, I’ve configured a weekly backup to be sent to my Google Drive:

Caching
The WordPress admin dashboard gives you a very useful Site Health Status:

There were about 8 things that needed doing on this, such as deleting/updating plugins, deleting themes, etc. There are 2 remaining:

Both of these can be handled by Total Cache. I installed the plugin, and it gave me a plethora of lines to add to .htaccess
I manually added those rather than opening FTP.
Next, I’ll need to install Redis: sudo apt install redis-server php-redis -y

And select Redis in Page/Object cache.
Site health is looking a bit better now!

I think that’ll do for now, she’s all up and running with hosting costing ~£2 a month rather than £30+.
Here’s to another 6 years! ?