Migrating WordPress blog to a new VPS

2014-Oct-06

Lesson learned:  stay away from service providers who try to be everything.

If the service provider thinks that being a domain name registrar, DNS host, web host, and email hosting are all one and the same “hosting” – R U N   A W A Y.   If you don’t – then you deserve everything you get, with interest.  Including the extra advertising popup you are forced to step past in order to actually get into your account control panels.

The experiment to try a blog at a hosting company only lasted 4 months.

In the end, it took one SSL certificate to do them in.   Their automation broke because they don’t host email for my domain,  and some really bizarre ideas came up after their customer support discovered that they also didn’t host my DNS.    Part of the extra confusion is my responsibility.  I have a tendency to tell tech support people too much,  and they have a tendency to focus intently on a detail that is most likely irrelevant to the issue at hand.

When compared to the hosted solution and the amount of effort I spent trying to communicate with customer service –   it turns out I can spend less effort and, actually, also less dollars to manage my own VPS.  While the initial set up effort is much higher,  the ongoing maintenance effort is less and much more satisfying than dealing with confused support and still getting nowhere.

Anybody with a bit of whois-fu can dig up that the new provider is RamNode,  well reviewed at http://lowendbox.com/.   I even get much better latency from home to Seattle than Boston.

It’s much much too early to form an opinion about them because nothing’s gone wrong yet.

 

 

Migrating WordPress to a VPS

Get the VPS going

A whole lot of what I did at the beginning to get running was directly from http://lowendbox.com/blog/yes-you-can-run-18-static-sites-on-a-64mb-link-1-vps/ Thank you LowEndAdmin!

  • apt-get update && apt-get upgrade  (the bash tweak to try to fix shellshock was already there)
  • apt-get install lighttpd
  • lighttpd-enable-mod simple-vhost
  • /etc/init.d/lighttpd force-reload
  • vi /etc/lighttpd/conf-enabled/10-simple-vhost.conf

Tweak the vhost.server-root and document-root to something that will work for you.  As it states, it will use  <vhost.server-root>/<hostname>/<document-root> to look for web pages.  Do another force-reload once done tweaking the config files.

  • apt-get install mysql-server php5-cgi php5-mysql php5-gd
  • cat > /etc/lighttpd/conf-available/10-cgi-php.conf
server.modules += (“mod_cgi”)
cgi.assign = (“.php” => “/usr/bin/php5-cgi”)
  •  lighttpd-enable-mod cgi-php

Create a simple test file in the document-root to test it

  • echo ‘<?php phpinfo(); ?>’ > /srv/httpd/blog.oneharding.com/html/phpinfo.php

Edit 2014-11-30, had a problem trying to upload some media files due to a file size limit. Immediate WTF for a default 2M limit on my own machine.  Narrowed it down to some php.ini settings.

Back up existing file in case bad things happen, look at current values.

# cp php.ini php.ini.20141130
# grep -E 'upload_max_filesize|post_max_size|max_execution_time|memory_limit' php.ini
max_execution_time = 30
memory_limit = 128M
post_max_size = 8M
upload_max_filesize = 2M

Replace the offending lines

# sed -e 's/max_execution_time\ =\ 30/max_execution_time\ =\ 300/' -e 's/post_max_size\ =\ 8M/post_max_size\ =\ 128M/' -e 's/upload_max_filesize\ =\ 2M/upload_max_filesize\ =\ 128M/' php.ini > php1.ini

Check that it worked as expected

# grep -E 'upload_max_filesize|post_max_size|max_execution_time|memory_limit' php1.inimax_execution_time = 300
memory_limit = 128M
post_max_size = 128M
upload_max_filesize = 128M

Just as it should be.  Make it so.

# mv php1.ini php.ini

 

 

Now try to work mysql memory usage down.

  • mv /etc/mysql/my.cnf /etc/mysql/my.cnf.`date +%s`
  • cp /usr/share/doc/mysql-server-5.5/examples/my-small.cnf /etc/mysql
  • vi /etc/mysql/my.cnf

The skip-innodb from lowendbox doesn’t work anymore to disable that, but a quick search found another individual that had the magic words.  Balancing that against LowEndAdmin‘s advice – key_buffer_size up to 1MB, and table_open_cache = 10 – end up with this.  Note the log_error line,  mysql wasn’t sending anything to /var/log until I included this.

[client]
port		= 3306
socket		= /var/run/mysqld/mysqld.sock

[mysqld]
port		= 3306
socket		= /var/run/mysqld/mysqld.sock
log_error=/var/log/mysql.log
skip-external-locking
key_buffer_size = 1M
max_allowed_packet = 1M
table_open_cache = 10
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 128K
ignore_builtin_innodb
default_storage_engine=MyISAM
server-id	= 1

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash

[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M

[mysqlhotcopy]
interactive-timeout

Replace rsyslog – http://lowendtalk.com/discussion/comment/615917/#Comment_615917

  •  apt-get install inetutils-syslogd

Firewall

China was up and at ‘em early, already hammering brute force attempts on poor sshd.  This made the place much quieter to hang around.

  • vi /etc/init.d/firewall
#!/bin/sh
### BEGIN INIT INFO
# Provides:          firewall
# Required-Start:    $network
# Required-Stop:     $network
# Default-Start:     S 2
# Default-Stop:
# Should-Start:      fam
# Should-Stop:       fam
# Short-Description: Start the firewall
# Description:       iptables rules for uber goodness
### END INIT INFO

#. /lib/lsb/init-functions


#Flush any existing firewall rules
iptables -F
iptables -X
iptables -t nat -F
ip6tables -F
ip6tables -X

# Set Default Policies
#iptables -P INPUT DROP
#iptables -P OUTPUT DROP
#iptables -p FORWARD DROP
#ip6tables -P INPUT DROP
#ip6tables -P OUTPUT DROP
#ip6tables -P FORWARD DROP

# Match all traffic - counters
iptables -A INPUT
iptables -A OUTPUT
iptables -A FORWARD
ip6tables -A INPUT
ip6tables -A OUTPUT
ip6tables -A FORWARD

# Allow any connections from my IPs
iptables -A INPUT -s xx.xx.xx.xx -j ACCEPT
iptables -A INPUT -s xx.xx.xx.xx -j ACCEPT
# ISP1 range
iptables -A INPUT -s xx.xx.xx.xx/yy -j ACCEPT
# ISP2 range
iptables -A INPUT -s xx.xx.xx.xx/yy -j ACCEPT

# loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

iptables -A INPUT -p tcp --dport 22 -j DROP
ip6tables -A INPUT -p tcp --dport 22 -j DROP

# take care of mysqld
iptables -A INPUT -p tcp --dport 3306 -j DROP
ip6tables -A INPUT -p tcp --dport 3306 -j DROP

# HTTPD - 2014-10-05 should be good to go 
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT

Don’t block port 22 until you’re sure that your IP address is getting through properly. What should have been a simple script and some symlinks was, for some reason, not loading this script at boot time. That’s why the weird script headers are at the top of the file (it isn’t like that on another VPS where it runs fine). After this, then it works like a hot damn:

  • update-rc.d -f firewall start S

Eventually the default policy for all of the filter tables will be DROP. This means explicitly spelling out all of the expected traffic including:

  • NEW,ESTABLISHED,RELATED connections
  • icmp
  • DNS
  • allow outbound http/https connections for wget software updates/etc.
  • whatever I end up doing for a backup solution – maybe rsync to a specific location or allow initiating outbound ssh sessions.
  • routing for ipv6.. can’t get ipv6 at home, this could be my own gateway!

WordPress install

http://codex.wordpress.org/Installing_WordPress

  • mkdir /srv/httpd/blog.oneharding.com/html
  • wget https://wordpress.org/latest.tar.gz && tar xzf latest.tar.gz
  • mv wordpress/* /srv/httpd/blog.oneharding.com/html
  • mv wp-config-sample.php wp-config.php
  • mysql -u root -p
  • mysql> CREATE DATABASE blog;
  • mysql> GRANT ALL PRIVILEGES ON blog.* to ‘wordpress’@’localhost’ IDENTIFIED BY ‘wppassword';
  • mysql> FLUSH PRIVILEGES;
  • vi wp-config.php
<?php
define('FORCE_SSL_ADMIN',true);
define('DISABLE_WP_CRON',true);
define('DB_NAME', 'blog');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'wordpress');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
define('AUTH_KEY',         'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('SECURE_AUTH_KEY',  'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('LOGGED_IN_KEY',    'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('NONCE_KEY',        'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('AUTH_SALT',        'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('SECURE_AUTH_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('LOGGED_IN_SALT',   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
define('NONCE_SALT',       'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
$table_prefix = 'new_wp_';

Make sure you get some nice randomness for the salts.
Get SSL working before setting FORCE_SSL_ADMIN to true, and – if you’re going to – change the table prefix up front instead of later.
The calling of wp-cron.php in access.log was bothering me, so I disabled it and created a cronjob to take care of it.  I didn’t look at what it does, but 4x/day seems reasonable for whatever.

  • cat /etc/cron.d/wordpress
# /etc/cron.d/wordpress: crontab fragment for wordpress wp-cron.php
# does whatever the wp-cron.php does.  I was tired of seeing it in the access.log

* */6     * * *     www-data  /usr/bin/php5-cgi -q /html-dir/wp-cron.php oops!
00 */6     * * *     www-data  /usr/bin/php5-cgi -q /html-dir/wp-cron.php 

Browse to the httpd server, step through the installation to create an admin user.

WordPress Migration

http://codex.wordpress.org/Moving_WordPress

WordPress Importer plugin fail

Trying to use the wordpress importer plugin didn’t work very well.    While trying to install the plugin on the new machine, it demanded FTP details for the file to import.   Since the export process simply downloads a file for you – this didn’t really make any sense.

https://wordpress.org/support/topic/plugin-wordpress-importer-import-to-a-new-wp-installation-on-a-server-with-no-ftp

  • wget https://downloads.wordpress.org/plugin/wordpress-importer.0.6.1.zip
  • apt-get install unzip

Good to go.

Copied /wp-content/uploads/  (pictures, etc) from the old onto the new machine,  then imported the export file with the importer plugin.

The media library didn’t regain all of it’s pictures.   Give up on this path for now, try something else.

Raw database transfer / renaming tables

The old service provider provided access to phpMyAdmin.  It took some fiddling to get a .sql file exported from it – for whatever reason it wasn’t compressing the export like it should have, and I wasted hours before I realized that it wasn’t actually creating all of the tables.  There was also a problem with copy/paste where my .sql file had a bunch of “&lt;” HTML codes in inappropriate places.

Once you get your full .sql file out of it – it needs to be mangled a bit for the different table prefix.   In the example above, we told wordpress to use ‘new_wp_’ as the table prefix.   In my case, the old installation was using ‘wp_gpjn_’ as the table prefix.

  • sed -e “s/wp_gpnj_/new_wp_/g” db-export.sql > db-import.sql
  • mysqldump -u wordpress -pwordpress blog > blog-`date +%s`.sql
  • mysqlimport blog db-import.sql

Cleverly (and unintentionally in my case), the sed line also takes care of any of the actual data lines that have the table prefix in them.  So this worked well for me the first time.    Bear in mind that it will also modify any content that might exist in the database if the sed regex matches on any of it.  (afterthought – maybe that’s related to why my metadata disappeared for only the bmp image files?)

When I opted to change the table names again later, I only changed the actual table names – which caused  “You do not have sufficient permissions to access this page.”  grief.  It’s related to the table prefix – look in the <prefix>options!option_name and <prefix>usermeta!meta_key fields and change the values in these that contain the old prefix.

http://www.wpbeginner.com/wp-tutorials/how-to-change-the-wordpress-database-prefix-to-improve-security/

Securing / Pretty permalinks

Installed the Gauntlet Security plugin, and set to work following it’s recommendations.  The recommended .htaccess fixes won’t work because those are Apache configuration files and this install is running lighttpd.

To only allow explicit file extensions to be downloaded/executed  from /wp-content/uploads,  and restrict access to the includes folders – I used the access module in lighttpd.

  • cat > /etc/lighttpd/blog.oneharding.com.conf
$HTTP["host"] == "blog.oneharding.com" {
        $HTTP["url"] =~ "^/wp-content/uploads.*$" {
                $HTTP["url"] !~ "\.(jpe?g|png|gif|mp3|wav|ogg|m4a|mp4|mov|wmv|avi|mpg|ogv|3gp|3g2|pdf|docx?|pptx?|ppsx?|odt|xlsx?|zip|bmp)$" {
                        url.access-deny = ( "" )
                }
        }
        $HTTP["url"] =~ "^/wp-admin/includes" {
                url.access-deny = ( "" )
        }
        $HTTP["url"] =~ "^/wp-includes/.*\.php.*$" {
                url.access-deny = ( "" )
        }

# now try for pretty permalinks. 
# http://www.guyrutenberg.com/2008/05/24/clean-urls-permalinks-for-wordpress-on-lighttpd/
# Lighttpd version 1.4.31 can't handle the url.rewrite() inside of the $HTTP["url"] selection block.
# had to download/compile a later version where that had been changed.
# the only difference it makes is that it breaks the link to get into the control panel.
        $HTTP["url"] !~ "^/wp-admin/" {
                url.rewrite = (
                        "^/(.*)\.(.+)$" => "$0",
                        "^/(.+)/?$" => "/index.php/$1"
                )
        }

}
  • echo include “/etc/lighttpd/blog.oneharding.com.conf” >> /etc/lighttpd/lighttpd.conf

 SSL/TLS

You’ll notice that this site is available over SSL/TLS now, AND there is also – at the time of this writing – another virtual host running with a DIFFERENT SSL certificate.  Shove THAT somewhere unpleasant webhost provider who said that was impossible.     The SNI won’t work with XP browsers.   Fortunately working properly with XP and mobile browsers wasn’t a priority this time. It still needs a default go-to certificate because the SSL connection is established before the host is requested. I haven’t read on how exactly SNI is supposed to work.

cat > /etc/lighttpd/lighttpd-ssl.conf

$SERVER["socket"] == ":443" {
        ssl.engine = "enable"
        ssl.pemfile = "/srv/ssl/blog.oneharding.com.pem"
        ssl.ca-file = "/srv/ssl/startssl-chain.pem"
        $HTTP["host"] == "blog.oneharding.com" {
                ssl.pemfile = "/srv/ssl/blog.oneharding.com.pem"
                ssl.ca-file = "/srv/ssl/startssl-chain.pem"
        }
        $HTTP["host"] == "otherhost.oneharding.com" {
                ssl.pemfile = "/srv/ssl/otherhost.oneharding.com.pem"
        }
}

echo include “/etc/lighttpd/ligttpd-ssl.conf” >> /etc/lighttpd/lighttpd.conf

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>