To the cloud and back again

Several years in the past, I decided to move this website from a small Linux machine that was collecting dust in my house to an AWS EC2 instance. After several years in the cloud, I still didn’t see what the hype was about, and my monthly cloud bill continued to climb, so I decided to go on-premises again.

Almost five hours of work later, and the site is back online, running on nothing more than a small Raspberry Pi Zero W. I had underestimated the moving parts, so I’m writing this post to reflect, and to hopefully keep me from making a decision to go back to a cloud provider in the future.

In total, I moved one VM back to on premises and migrated the following:

  • Apache Web Server with multiple Virtual Hosts
  • PHP
  • MariaDB (Fork of MySQL, used for WordPress)
  • Let’s Encrypt certificates
  • Postfix

Step 1 – Source the hardware

Sourcing the hardware when corporate purchasing isn’t involved is quick and easy. This translated to me digging through a shoebox until I found the Raspberry Pi Zero that had gathered the least amount of dust. Alongside it, I found a 32 GB SD card that would serve as storage for the OS. Yes, larger capacity may have been better, but this was the first one that I grabbed, and this is Linux that we’re talking about, not Bloat-crosoft.

Step 2 – Install the OS

The last time I played with a Raspberry Pi, we used tools like Balena Etcher, Rufus, or the Linux dd command to get the job done. These felt clunky at times, but that’s no longer necessary in 2025.

The Raspberry Pi Foundation has been hard at work since those days with the Raspberry Pi Imager tool. You pop in an SD card, tell it to install Raspberry Pi OS, and have the opportunity to pre-configure hostname, SSH, and credentials all as a part of the initial imaging. I was skeptical at best. After letting it run for half an hour or so, I came back to the “Success” screen and I prepared to start my headless installation.

Step 3 – Boot the OS

In the older days of Raspberry Pi, you booted up and finished installing the OS, setting the hostname, and connecting to the network. This required you to attach peripherals like your monitor, keyboard, and mouse. I avoided that with my headless install. I plugged the Pi in and ran ping -t <hostname> from another PC for five minutes or so until I gave up after it kept timing out. Since I’m an amateur network administrator at best, I jumped into my Wireless Access Point’s management interface and waited to see the Pi connect. About ten minutes in, it was able to connect and was available on my LAN.

Step 4 – Getting connected

This was easy. I was able to SSH to the Pi using the credentials that I provided in Raspberry Pi Imager. I also took the time to configure Tailscale so that I could easily SSH to the Pi without having to open that port to the internet.

Step 5 – Take a database backup

In my day job, I’m a database administrator, so I already had a cron job in EC2 to take regular full and logical dump-based backups. I just re-ran the shell script for my logical backup and redirected the output to a dumpfile, which I could then scp over to my Raspberry Pi.

Step 6 – Restore the database backup on the new machine

Run apt to install MariaDB (whatever version since we’re just doing a logical restore), then run mysql_secure_installation to harden up security. Create the WordPress database and database user, then grant whichever privileges that they need. Finally, restore the dumpfile and validate there were no errors.

scp backup.dmp raspberrypi:/tmp/backup.dmp
ssh raspberrypi
cd /tmp

sudo mysql
create database wordpress;
grant all privileges on wordpress.* to 'wordpress'@'localhost' identified by '<password>';
use wordpress;
source /tmp/backup.dmp

Step 7 – Application server installation

I muddled through the other parts of the LAMP stack and installed Apache and PHP on the Pi. I copied over my website data from /var/www from EC2 and placed it in the corresponding directories on the Pi. I also copied my Apache site configuration from /etc/apache2/sites-available and changed ownership to match what was on EC2.

Step 8 – Start serving the sites on the Pi.

First, I enabled the sites on the new server.

cd /etc/apache2/sites-available
scp ec2:/etc/apache2/sites-available/*.conf .
sudo a2ensite dustinredmond.com
sudo systemctl restart apache2

Step 9 – Test site functionality over HTTP

I thought that I was nearly done at this step when everything seemed to work perfectly over HTTP. I halfway tested each of my websites, fairly happy with the results.

Step 10 – Switch DNS to point to the Pi’s IP address instead of EC2

I was able to do this config through my domain registrar. For each DNS record, I simply replaced the EC2 IP address with that of the internet line for my Raspberry Pi. Quick and painless. I gave some time for DNS to work its magic, then came back after a short break.

Step 11 – Enable HTTPS (and break everything in the process)

I shutdown my EC2 instance in hopes that I’d never need to start it again. Here’s where everything started going South. For SSL certs, you can’t beat certbot. I installed that, and requested certs for each Virtual Host. No issues there, until it was time for post-deployment validation.

Step 12 – Validation

For the sites using WordPress, nothing worked. Well, not nothing, technically I could load a homepage for one, which made the matter even more confusing. At this point, my site was down, and I didn’t have a clue what needed to change to get it back.

Step 13 – Diagnosis

Steps one through thirteen in this post-mortem took less than an hour if we don’t count watching progress bars. For the next three hours, I combed through logs, examined my .bash_history file to find where I did something wrong and tweaked every setting in Apache that I was familiar with. In researching the issue, I had realized that I didn’t enable mod_rewrite for Apache, which perfectly explained the issue that I was seeing. I enabled that since I knew that it would be needed, but to no avail.

After I had nearly exhausted hope, and considered a fresh installation of WordPress, it dawned on me to start running diff against site configuration files. That did the trick.

In EC2, in the SSL site configuration files generated by certbot, it had:

<IfModule mod_ssl.c>
<VirtualHost *:443>
...
    <Directory /var/www/dustinredmond.com/>
      AllowOverride All
    </Directory>
...
</VirtualHost>
</IfModule>

But, my Pi had:

<IfModule mod_ssl.c>
<VirtualHost *:443>
...
    <Directory /var/www/dustinredmond.com/>
      AllowOverride
    </Directory>
...
</VirtualHost>
</IfModule>

This one-word difference was the reason for all of the madness. A quick sed command to replace this line in a few site config files, a restart of Apache, and poof, headache gone. I know that his AllowOverride All directive is used by WordPress for how it handles perma-linking, but if certbot could have copied over the same config for the SSL files, this would have been much simpler.

Have you ever experienced a single line of configuration that caused so many issues? Let me know in the comments.

Hope this helps,

Dustin

Leave a Reply

Your email address will not be published. Required fields are marked *