Quaintous Published:
(tags)
(category)
Updated:
Migrating managed Wordpress instances to NGINX + PHP-FPM

tl;dr This post documents how I migrated my managed wordpress instances to my own VPS using PHP-FPM and NGINX in case I need to do it again!

Introduction

Some years ago, I got an offer for managed wordpress that I could not refuse: unlimited instances, disk space, traffic, databases, etc. I was fairly happy until I wanted to share a MySQL database outside the service provider’s private network. Well, I couldn’t, because I didn’t have access rights to configure the firewall. So I ordered the cheapest SSD VPS by contabo as an alternative. I chose PHP-FPM for security reasons, e.g., containing script execution to a single user.

NOTE: WP provides guidance on how to migrate a WP instance. Here, following assumptions are made:

  • A single WP site is being migrated
  • Domain name stays the same
  • SSH access to server is given

Prepare new server

For a debian 10, that I’m using, at least the following packages need to be installed:

apt install \
  php7.3-fpm \
  php7.3-mysql \
  nginx \
  mariadb-common

Backing up old instance

Although there’s an official WP guide on how to backup data, I wanted to have it automated and uncomplicated. The result was a single bash script wp-backup.sh:

#!/bin/bash

set -euo pipefail

err() {
	>&2 echo "$1"
	exit 1
}

main() {
	if [[ ! -d "$1" ]]; then
		err "Given path ($1) not found or not a directory!"
	fi

	local readonly WPCONFIG="$1/wp-config.php"

	if [[ ! -r "${WPCONFIG}" ]]; then
		err "WP config file (${WPCONFIG}) not found or not readable!"
	fi

	local readonly DEFINERE="^define\('(.*)'[[:blank:]]*,[[:blank:]]*'(.*)'\)"

	local line
	declare -A configValues
	while read -r line; do
		if [[ "${line}" =~ ${DEFINERE} ]]; then
			local key="${BASH_REMATCH[1]}"
			local value="${BASH_REMATCH[2]}"
			configValues[${key}]="${value}"
		fi
	done < "${WPCONFIG}"

	# Serialize config (for restore script)
	local configTarget="config.sh"
	echo "Dumping WP config to '${configTarget}'"
	declare -p configValues > "config.sh"

	# Backup DB
	local sqlTarget="${configValues[DB_NAME]}.sql"
	echo "Dumping sql data to '${sqlTarget}'"
	mysqldump --host="${configValues[DB_HOST]}" --user="${configValues[DB_USER]}" --password="${configValues[DB_PASSWORD]}" "${configValues[DB_NAME]}" > "${sqlTarget}"

	# Backup WP files
	local wpTarget="${configValues[DB_NAME]}.gz"
	echo "Dumping WP files to '${wpTarget}'"
	tar -czf "${wpTarget}" "$1"
}

main "$@" || exit 1

Now you can backup as follows:

# Log on to your server
# copy wp-script.sh over
mkdir backup && cd backup
./PATH/TO/wp-script.sh PATH/TO/WP-DIR

Restoring

Log on to your new server and scp the backup data from previous step over into the home directory of created user:

scp -r OLD_SERVER:/PATH/TO/backup /home/example_com

To quickly restore DB and WP files as well as configuring NGINX and PHP-FPM, wp-restore.sh can be used:

#!/bin/bash

set -eo pipefail

err() {
	>&2 echo "$1"
	exit 1
}

main() {
	if [[ -z "$1" ]]; then
		err "Host name not given or empty!"
	fi

	if [[ ! -d "$2" ]]; then
		err "Backup directory '$2' is invalid!"
	fi

	local readonly SITENAME="$1"
	local readonly SITESAFE=$(echo "${SITENAME}" | tr '.' '_')
	local readonly WPBACKUP=$(find "$2" -type f -name "*.gz" | head -n 1)
	local readonly DBBACKUP=$(find "$2" -type f -name "*.sql" | head -n 1)
	local readonly TARGETBASE="/home/${SITESAFE}"
	local readonly WPTARGET="${TARGETBASE}/${SITENAME}"

	# Load WP configs
	source "$2/config.sh"

	# Create user
	if ! id -u "${SITESAFE}" > /dev/null; then
		adduser --system --group "${SITESAFE}"
	fi

	# Extract WP files
	mkdir -p "${WPTARGET}"
	tar -xf "${WPBACKUP}" --directory "${WPTARGET}" --strip-components 1
	chown -R "${SITESAFE}":"${SITESAFE}" "${WPTARGET}"

	# Setup and restore DB
	echo "Setting up database"
	mysql -u root -p <<- SQL
		DROP USER IF EXISTS '${configValues[DB_USER]}'@'localhost';
		CREATE USER '${configValues[DB_USER]}'@'localhost' IDENTIFIED BY '${configValues[DB_PASSWORD]}';
		DROP DATABASE IF EXISTS ${configValues[DB_NAME]};
		CREATE DATABASE ${configValues[DB_NAME]};
		GRANT ALL ON ${configValues[DB_NAME]}.* TO '${configValues[DB_USER]}'@'localhost';
		USE ${configValues[DB_NAME]};
		SOURCE ${DBBACKUP};
	SQL

	# Setup PHP-FPM
	echo "Setting up PHP FPM"
	cat <<- CONF > /etc/php/7.3/fpm/pool.d/${SITESAFE}.conf
		[${SITESAFE}]
		user = ${SITESAFE}
		group = ${SITESAFE}
		listen = /run/php/php7.3-fpm-${SITESAFE}.sock
		listen.owner = www-data
		listen.group = www-data
		php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,dl,setenv
		php_admin_flag[allow_url_fopen] = off
		; Choose how the process manager will control the number of child processes.
		pm = dynamic
		pm.max_children = 75
		pm.start_servers = 10
		pm.min_spare_servers = 5
		pm.max_spare_servers = 20
		pm.process_idle_timeout = 10s
		env[HOSTNAME] = $HOSTNAME
		env[TMP] = /tmp
	CONF
	if php-fpm7.3 -t; then
	       	service php7.3-fpm restart
	else
		err "Something went wrong with PHP FPM!"
	fi

	# Setting up NGINX
	echo "Setting up NGINX"
	local readonly NGINXCONF="/etc/nginx/sites-available/${SITENAME}"
	cat <<- CONF > "${NGINXCONF}"
		server {
                        listen 80;
                        listen [::]:80;
		        listen 443 ssl;
		        listen [::]:443 ssl;
		
		        server_name ${SITENAME} www.${SITENAME};
		
		        include snippets/wordpress.conf;
		
		        root ${WPTARGET};
		
		        access_log /var/log/nginx/${SITENAME}-access.log;
		        error_log /var/log/nginx/${SITENAME}-error.log error;
		
		        index index.php;
		
		        location / {
		                try_files \$uri \$uri/ /index.php?\$args;
		        }
		
		        location ~ \.php$ {
		                include snippets/fastcgi-php.conf;
		                fastcgi_pass unix:/run/php/php7.3-fpm-${SITESAFE}.sock ;
		        }
		
		        ssl_protocols TLSv1.2 TLSv1.3;
		
		        ssl_stapling on;
		        ssl_stapling_verify on;
		}
	CONF
	ln -s "${NGINXCONF}" /etc/nginx/sites-enabled
	if nginx -t; then
		service nginx restart
	else
		err "Something went wrong while configuring NGINX"
	fi
		
}

main "$@" || exit 1

Restoring is as easy as:

./PATH/TO/wp-restore.sh example.com PATH/TO/BACKUP

This script changes the system as follows:

  1. A system user corresponding to given domain name is generated, e.g., example_com.
  2. Compressed WP files are extracted to a directory under home directory of the newly created user, e.g., /home/example_com/example.com
  3. A new SQL user is added and is filled with the previously created dump
  4. A PHP-FPM configuration file is generated and activated
  5. A new NGINX block is generated and activated

Finishing touches

You’re not done before you get some fresh new certificates for your wordpress instance. Go ahead and install certbot and run the following:

certbot --nginx\
  -d example.com -d www.example.com\
  --server 'https://api.buypass.com/acme/directory'

This would fetch a new certificate from buypass, a european alternative to Let’s Encrypt, and automatically configure your nginx block.

License: 🟢 No JavaScript (Notes)
🟢 No Tracking