Migrating WordPress from Pagely

Pagely is an expensive and robust solution for WordPress hosting. With the right caching setup wordpress can be performant, so let’s slap it in a docker container behind nginx.

To get off of Pagely, the most critical thing you’ll need to do with your exported backups is to make sure to either remove or rename or remove the mu-plugins folder.

  1. Rename or delete mu-plugins/ from the folder export.
  2. Import the database and wp-content directories.

What’s easier than physically moving your wp-content directory is using the Updraft Plus plugin to create a backup of your wordpress install.

Redirect Hell

Setting the siteurl and home settings in the database shoots me into redirect hell.

 

Setting as localhost instead of localhost/blog yields a somewhat more functional site, but all the links are broken and nothing tends to work. I must need PHP 5. (This was definitely not the solution)

So it all works after disbabling the pagely plugins but this reverse proxying to a docker container from a subfolder thing, wordpress really doesn’t appear to like it very much. Everything worked for a second but now it doesnt again, just stuck in a 301 redirect loop to itself. The wp-admin functions fine as long as you remember to add the trailing slash on the URL.

Endless redirect loops later, we’re still stuck in a redirect loop.

Hopefully you won’t need to spend that much time dickerin with it.

wp-config.php

define('FORCE_SSL_ADMIN', true);

define('WP_HOME', 'http://example.com/newblog/');
define('WP_SITEURL', 'http://example/newblog/');

$_SERVER['REQUEST_URI'] = '/newblog' . $_SERVER['REQUEST_URI'];

This $_SERVER['REQUEST_URI'] line was the most helpful fix I came across. I had previously been using $_SERVER['REQUEST_URI'] = str_replace("/wp-admin/", "/blog/wp-admin/", $_SERVER['REQUEST_URI']); as recommended by several resources in place for the wp-admin but rewriting the whole URI with $_SERVER['REQUEST_URI'] = '/newblog' . $_SERVER['REQUEST_URI']; stopped redirect hell.

/etc/nginx/sites-enabled/default

We’ll add a section for nginx to proxy to the wordpress container.

    location /newblog/ {
        access_log off;
        auth_basic off;

        rewrite /newblog/wp-admin$ $scheme://$host$uri/ permanent; # This line redirects /newblog/wp-admin to /newblog/wp-admin/ which is nice to have. 

        proxy_set_header  Host               $host;
        proxy_set_header  X-Real-IP          $remote_addr;
        proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header  X-Forwarded-Proto  $http_x_forwarded_proto;

        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_read_timeout    90;
        proxy_connect_timeout 90;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Proxy "";

        proxy_redirect    off;
        proxy_pass        http://wordpress/;
    }

.htaccess

The .htaccess file that wordpress creates will attempt to make the RewriteBase and RewriteRule include the newblog/ part of the URL, which we don’t want, and will cause a redirect loop.

# BEGIN WordPress

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# END WordPress

The deploy

So it all works after destroying and rebuilding containers. What are the steps to do it live?

  1. ✅ Install Updraft Plus and create a backup, stuff it in dropbox.
  2. ✅ Deploy the containers, on the /newblog/ URL.
  3. ✅ Run our install script via wp_cli:
#!/bin/bash

docker-compose run wp_cli core install --url='http://example.com/newblog/' --title=temp --admin_user=temp --admin_email=wade@wadewilliams.com --admin_password="password"
docker-compose run wp_cli plugin install updraftplus --activate
  1. ✅ Connect dropbox in the updraft plus plugin. Click rescan remote storage, select the backup to install, and restore that backup.
  2. ✅ Reset the siteurl and home options in the wp_options table.
mysql -uroot -psecret -h 0.0.0.0 wp_database -e 'update wp_options set option_value = "http://example.com/newblog" where option_name = "siteurl" or option_name = "home"'
  1. 💣 Make sure it all works!
  2. ✅ Remove the old nginx redirect for the /blog url and replace it with the /newblog directive. Update the wp-config.php and the database to remove the references to /newblog.
  3. ✅ Shut down pagely. Keep your money.

HTTPS Support.

We didn’t test SSL support locally before deployment, and it’s easy to bang your head against the wall chasing down a fix for a problem that’s the result of something else.

Turns out that Jeff’s Application Load Balancers handle SSL termination and thus pass http_x_forwarded_proto. We set that right in the nginx config and suddenly there is no more redirect hell, and everything’s running on https.

The line in the nginx config proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; must use x_forwarded_proto instead of $scheme since we’re behind an ALB on AWS. This is only needed if you’re using nginx behind an application load balancer on aws, otherwise using $scheme should allegedly work.


Resources

github ↗ a5hleyrich/wordpress-nginx
docs.j7k6.org ↗ Basically looks to be exactly what we are looking for, but not quite.
stackoverflow ↗ nginx subpath to redirect to wordpress docker container

serverfault ↗ hosting a wordpress blog using nginx as a sub directory
stackoverflow ↗ wordpress nginx proxy and subdirectory wp login php redirects to domain

stackexchange ↗ wordpress wp admin https redirect loop

stackexchange ↗ https leads to sorry you are not allowed to access this page
stackexchange ↗ setting server https on prevents access to wp-admin

codex.wordpress.org ↗ administration over ssl

serverfault ↗ x forward proto custom header

Beats

Generating ERD for Django Applications

Generating an ERD (Entity Relationship Diagram) can be helpful to understand what your database looks like, and is also really easy to generate if you’re running a Python/Django Application.

Step 1: Install django-extensions and graphviz

 pip install django-extensions && pip install graphviz

Step 2: Add django_extensions to settings.py

INSTALLED_APPS = [
    ...
    'django_extensions',
    ...
]

Step 3 Create the .dot output and convert to .png

Run manage.py graph_models from the environment you may normally use:

python manage.py graph_models -a > output.dot

From your mac, change the format to png using graphviz

brew install graphviz
dot -Tpng output.dot -o output.png

References
Django Extensions graph_models docs
Django Extensions Commands Unavailable?
Opening .dot files on mac

Monthly Goals

Work takes time. Time is money. All of these things should be obvious by now.

But how do you decide how to spend your time or money?

Spend time identifying the most high value opportunities that you can spend your time with. Then divide and conquer.

“Deadlines are vitamins for creativity”

Set monthly objectives. Everyone gets all hot and ornery about new years resolutions, but every turning of the calendar month should bring the same contemplation. This gives you 12 opportunities a year to assess where you’re at, where you’re going, and where you want to get to. Make it a regular practice.

The Best Hatchet

I once worked for a zombie startup*.

At one point, the “leadership” team decided they needed to just get away and do some real soul searching. A candid mandate was issued that everyone read Creativity Inc in the next three days.

They travelled to an old west dude ranch in Arizona, the team met to discuss the changes that had already taken place: The removal of the CIO and COO, as the company tightened its belt in a desperate attempt to “get organized” and “make a plan”.

Someone was ordered to pick up a dozen hatchets from the general store for the meeting. On each hatchet, each “leader” was to name their hatchet with sharpie marker. Later on the team would proceed out into the desert where they would burry all of their hatchets. (A team building exercise that had clearly been recommended by the business coach).

As I was the most senior technology person on staff following the dismissal of the CIO, I wasn’t invited to the meeting. But the hatchets showed up on Monday at the office. Intrigued, I picked up the first one I came across.

Written across the face of the hatchet in the CEO’s handwriting, “The Best”.

Either the team couldn’t figure out how to dig a hole in the desert (possible) or they were too lazy to try (likely).

Either way, The Best™ hatchet is still doing fine work for me.

*Disclaimer: Nothing in this article should be construed as a recommendation to bring a hatchet to a zombie fight.