browse by category or date

[nextpage title=”Background Story”]
Around three weeks ago, GoDaddy (where I was hosting this blog) suffered a downtime. Although they’re back online within 48 hours, something bad had happened to shared server that hosted my blog. I don’t really know the details, but I only know the outcome. This blog become super slow, intermittently spitted out timeout error, and frequently crashed (I need to terminate the web process from Hosting Control Panel). Being a good customer, I raised ticket and gave them chance to resolve this problem. But after two weeks with no improvement, my patience ran out. So I spent last week moving out this blog to Amazon EC2.

Why Amazon EC2? I can’t really elaborate why. But I can say that it’s a weird logic combination of:

  1. The Amazon brand
  2. I’ve seen many cool projects are hosted in Amazon EC2
  3. Free tier usage!!
  4. Learn something new 😀

Using few guides that I found on the Net, I managed to achieved the setup that I want. It wasn’t a smooth sailing though. In the end, I learned something new and now I write this guide hopefully will get you on a smooth sailing migrating your WordPress blog from GoDaddy to Amazon EC2.

Alright, let’s get dirty!!

[nextpage title=”Setup EC2 Micro Instance”]

After you login to your Amazon Web Service account, you’ll have something like below. Click EC2.


Now on your left screen you’ll have something like this:


You might need to change the location of your data center. For me, I changed it to Asia Pacific (Singapore) because majority of my blog readers come from this region.

Next, click Instance.


Then, click Launch Instance button. You will be prompted with 3 choices:

  1. Classic Wizard: You will be required to configure in details your EC2 instance.
  2. Quick Launch Wizard: You can choose from a number of commonly used OS + Software setup.
  3. AWS Marketplace: You’ll have myriads of setups available to choose. These setups already come with the prepackaged software.

For now, let’s just select Quick Launch Wizard and pick Ubuntu Server 11.10.


Before we can continue, we need to give name to our instance. We also need to create a new Key Pair and download the Key Pair. We are going to need Key Pair to establish SSH connection later on.


When you clicked the download button, you’ll receive a [Name].pem. In my example, it’s wpkey.pem. What to do now is to click the Continue button. After that, we will have the summary screen detailing our setup.


Next, click Launch button to have our instance running.

Assign IP Address

By default, our instance will only have internal IP. To make the instance have an external IP. We need to use Elastic IP. On the EC2 Navigation Panel, click Elastic IPs:


Click Allocate New Address button


Click Yes, Allocate. After a while, we will see the new IP Address in the table. Select the row, then click Associate Address button.


That’s it for now. Let’s continue to the next topic about establishing SSH Connection.

[nextpage title=”Setup SSH Connection”]

There are many tools that enables you to have SSH session with your EC2 instance. But I won’t review them all here, I’ll just discuss my choice of tools. Which are:

  1. PuttyGen: This tool is used to generate private key from previously downloaded Key-Pair file (e.g. wpkey.pem)
  2. Putty: This tool is used to establish SSH connection.
  3. FileZilla: This tool is an FTP client that capable connecting to SFTP protocol

Putty and PuttyGen can be downloaded HERE. Filezilla’s download page is HERE.

Generate Private Key

Run PuttyGen:

Click Load button, select the .pem file.


Change the Number of bits in a generated key to 2048. Click the Save private key button and name the file wpkey.ppk

Establish SSH Connection

Run Putty:

Go to Connection -> SSH -> Auth. We need to set the Private key for authentication with the .ppk file that we generate before. Click browse, and find the file. After that go to Session

Set the Hostname (or IP address) with the IP address obtained in previous page. Or you could use the Public DNS value shown in your Instance detail. Leave the default value (22) for Port. Under the Saved Session, give name to this connection (e.g. ec2), then click Save. Finally, click Open to initiate connection.

Putty will prompted you whether this connection can be trusted:

Click Yes. To login, use ubuntu as username.

Congratulations! You are now connected to your Ubuntu server through SSH.

Establish SFTP Connection

Run FileZilla, go to Edit -> Settings. In the Settings window, go to Connection -> SFTP. Press Add keyfile button. Select the same .ppk file as the one used by Putty.


Next, go to Sites Manager. Add connection to your instance by following configuration similar to below and save it for future usage.

Alright, so far so good. We now can connect to our instance and transfer files. Next, let’s start configuring our server for WordPress.

[nextpage title=”Setup MySQL”]

To install MySQL, run the following command:

sudo -i
apt-get update
apt-get install mysql-server

MySQL will prompt you to enter the root password. Write it down, or save the password somewhere in case you forget.

Once the installation completed, login to MySQL

mysql -u root -p

You’ll be prompted for password. Use the same password as you set in installation step.

At the mysql> prompt, run these commands, replacing NEW_PASSWORD with a password of your own

CREATE DATABASE wordpress;
GRANT ALL PRIVILEGES ON wordpress.* 
TO "wp_user"@"localhost" IDENTIFIED BY "NEW_PASSWORD";
FLUSH PRIVILEGES;
EXIT

Now let’s create a custom configuration file to cater the RAM size limitation in EC2 Micro.

vim /etc/mysql/conf.d/ec2.cnf

Press i to make Vim enter edit mode. Paste below code by mouse right-click

[mysqld]
key_buffer = 12M
query_cache_size = 0
table_cache = 32

ignore_builtin_innodb
default_storage_engine=MyISAM

Press Esc key to return to read mode. Press !wq to save the file.

Alright, that’s all for MySQL portion.

[nextpage title=”Setup PHP FPM”]

Run the following commands to install PHP FPM:

apt-get install php5-fpm php5-cli php-apc php5-suhosin php5-curl php5-gd php5-intl php5-mcrypt php-gettext php5-mysql php5-sqlite

After the installation completed, we’re going to customize it. But first, let’s backup the config files.

mkdir /root/backup
mkdir /root/backup/etc
mkdir /root/backup/etc/php5
mkdir /root/backup/etc/php5/conf.d
mkdir /root/backup/etc/php5/fpm
mkdir /root/backup/etc/php5/fpm/pool.d
cp /etc/php5/conf.d/apc.ini /root/backup/etc/php5/conf.d
cp /etc/php5/fpm/php.ini /root/backup/etc/php5/fpm
cp /etc/php5/fpm/pool.d/www.conf /root/backup/etc/php5/fpm/pool.d

Now let’s modify /etc/php5/conf.d/apc.ini. Replace its content with:

[APC]
extension=apc.so
apc.enabled=1
apc.shm_segments=1
apc.shm_size=16
apc.ttl=7200
apc.user_ttl=7200
apc.num_files_hint=1024
apc.mmap_file_mask=/tmp/apc.XXXXXX
apc.max_file_size = 1M
apc.post_max_size = 1000M
apc.upload_max_filesize = 1000M
apc.enable_cli=0
apc.rfc1867=0

Next, let’s modify /etc/php5/fpm/php.ini. Append the following text:

[apc]
apc.write_lock = 1
apc.slam_defense = 0

Lastly, let’s modify /etc/php5/fpm/pool.d/www.conf. Match the content with below configuration:

user = nginx
group = nginx
listen = /dev/shm/php-fpm-www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0666

Let’s now restart the service

service php5-fpm restart

[nextpage title=”Setup Nginx”]

To install Nginx, we will follow the installation guide found HERE.
First, we will download the Nginx signing key and add it to the apt program keyring

cd /tmp
wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key

Now we’re ready to install Nginx

deb http://nginx.org/packages/ubuntu/ oneiric nginx
deb-src http://nginx.org/packages/ubuntu/ oneiric nginx
apt-get update
apt-get install nginx

Next, let’s configure Nginx. But first, as usual, let’s backup the configuration files first.

mkdir /root/backup/etc/nginx
cp /etc/nginx/nginx.conf /root/backup/etc/nginx
cp /etc/nginx/php.conf /root/backup/etc/nginx

Replace the content of /etc/nginx/nginx.conf with:

user nginx;
worker_processes 1;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# nginx-naxsi config
	##
	# Uncomment it if you installed nginx-naxsi
	##

	#include /etc/nginx/naxsi_core.rules;

	##
	# nginx-passenger config
	##
	# Uncomment it if you installed nginx-passenger
	##
	
	#passenger_root /usr;
	#passenger_ruby /usr/bin/ruby;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
	
	port_in_redirect off;
}

Replace the content of /etc/nginx/php.conf with:

# Route all requests for non-existent files to index.php
location / {
	try_files $uri $uri/ /index.php$is_args$args;
}

# Pass PHP scripts to php-fastcgi listening on port 9000
location ~ \.php$ {

	# Zero-day exploit defense.
	# http://forum.nginx.org/read.php?2,88845,page=3
	# Won't work properly (404 error) if the file is not stored on
	# this server,  which is entirely possible with php-fpm/php-fcgi.
	# Comment the 'try_files' line out if you set up php-fpm/php-fcgi
	# on another machine.  And then cross your fingers that you won't get hacked.
	try_files $uri =404;

	include fastcgi_params;

	# Keep these parameters for compatibility with old PHP scripts using them.
	fastcgi_param PATH_INFO $fastcgi_path_info;
	fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

	# Some default config
	fastcgi_connect_timeout        20;
	fastcgi_send_timeout          180;
	fastcgi_read_timeout          180;
	fastcgi_buffer_size          128k;
	fastcgi_buffers            4 256k;
	fastcgi_busy_buffers_size    256k;
	fastcgi_temp_file_write_size 256k;
	fastcgi_intercept_errors    on;
	fastcgi_ignore_client_abort off;
	fastcgi_pass unix:/dev/shm/php-fpm-www.sock;

}
# PHP search for file Exploit:
# The PHP regex location block fires instead of the try_files block. Therefore we need
# to add "try_files $uri =404;" to make sure that "/uploads/virusimage.jpg/hello.php"
# never executes the hidden php code inside virusimage.jpg because it can't find hello.php!
# The exploit also can be stopped by adding "cgi.fix_pathinfo = 0" in your php.ini file.

Now, let’s create a specific configuration file for our site (e.g. /etc/nginx/available-sites/mysite.conf). You need to change every word sodeve.net found below into the name of your blog URL.

server {
	listen 80;
	server_name www.sodeve.net sodeve.net;
	root /var/www/sodeve.net/public;
	index index.html index.htm index.php;

	access_log  /var/www/sodeve.net/access.log;
	error_log  /var/www/sodeve.net/error.log;

	# Directives to send expires headers and turn off 404 error logging.
	location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
		expires max;
		log_not_found off;
		access_log off;
	}

	location = /favicon.ico {
		log_not_found off;
		access_log off;
	}

	location = /robots.txt {
		allow all;
		log_not_found off;
		access_log off;
	}

	## Disable viewing .htaccess & .htpassword
	location ~ /\. { deny  all; access_log off; log_not_found off; }

	include /etc/nginx/php.conf;
	#Download
	#rewrite ^/downloadz/(.*)$ /wp-content/plugins/download-monitor/download.php?id=$1 last;
	
	# BEGIN W3TC Minify cache
location ~ /wp-content/w3tc/min.*\.js$ {
    types {}
    default_type application/x-javascript;
    expires modified 31536000s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding";
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
}
location ~ /wp-content/w3tc/min.*\.css$ {
    types {}
    default_type text/css;
    expires modified 31536000s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding";
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
}
location ~ /wp-content/w3tc/min.*js\.gzip$ {
    gzip off;
    types {}
    default_type application/x-javascript;
    expires modified 31536000s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding";
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
    add_header Content-Encoding gzip;
}
location ~ /wp-content/w3tc/min.*css\.gzip$ {
    gzip off;
    types {}
    default_type text/css;
    expires modified 31536000s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding";
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
    add_header Content-Encoding gzip;
}
# END W3TC Minify cache
	# BEGIN W3TC Page Cache cache
location ~ /wp-content/w3tc/pgcache.*html$ {
    expires modified 3600s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding, Cookie";
    add_header Pragma "public";
    add_header Cache-Control "max-age=3600, public, must-revalidate, proxy-revalidate";
}
location ~ /wp-content/w3tc/pgcache.*gzip$ {
    gzip off;
    types {}
    default_type text/html;
    expires modified 3600s;
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
    add_header Vary "Accept-Encoding, Cookie";
    add_header Pragma "public";
    add_header Cache-Control "max-age=3600, public, must-revalidate, proxy-revalidate";
    add_header Content-Encoding gzip;
}
# END W3TC Page Cache cache
	# BEGIN W3TC Browser Cache
gzip on;
gzip_types text/css application/x-javascript text/x-component text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;
location ~ \.(css|js|htc)$ {
    expires 31536000s;
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
}
location ~ \.(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml)$ {
    expires 3600s;
    add_header Pragma "public";
    add_header Cache-Control "max-age=3600, public, must-revalidate, proxy-revalidate";
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
}
location ~ \.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$ {
    expires 31536000s;
    add_header Pragma "public";
    add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
    add_header X-Powered-By "W3 Total Cache/0.9.2.4";
}
# END W3TC Browser Cache
	# BEGIN W3TC Minify core
rewrite ^/wp-content/w3tc/min/w3tc_rewrite_test$ /wp-content/w3tc/min/index.php?w3tc_rewrite_test=1 last;
set $w3tc_enc "";
if ($http_accept_encoding ~ gzip) {
    set $w3tc_enc .gzip;
}
if (-f $request_filename$w3tc_enc) {
    rewrite (.*) $1$w3tc_enc break;
}
rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;
# END W3TC Minify core
	# BEGIN W3TC Page Cache core
rewrite ^(.*\/)?w3tc_rewrite_test$ $1?w3tc_rewrite_test=1 last;
set $w3tc_rewrite 1;
if ($request_method = POST) {
    set $w3tc_rewrite 0;
}
if ($query_string != "") {
    set $w3tc_rewrite 0;
}
if ($http_host != "sodeve.net") {
    set $w3tc_rewrite 0;
}
set $w3tc_rewrite2 1;
if ($request_uri !~ \/$) {
    set $w3tc_rewrite2 0;
}
if ($request_uri ~* "(sitemap(_index)?\.xml(\.gz)?|[a-z0-9_\-]+-sitemap([0-9]+)?\.xml(\.gz)?)") {
    set $w3tc_rewrite2 1;
}
if ($w3tc_rewrite2 != 1) {
    set $w3tc_rewrite 0;
}
set $w3tc_rewrite3 1;
if ($request_uri ~* "(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php|download.php|securimage.php)") {
    set $w3tc_rewrite3 0;
}
if ($request_uri ~* "(wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
    set $w3tc_rewrite3 1;
}
if ($w3tc_rewrite3 != 1) {
    set $w3tc_rewrite 0;
}
if ($http_cookie ~* "(comment_author|wp\-postpass|wordpress_\[a\-f0\-9\]\+|wordpress_logged_in)") {
    set $w3tc_rewrite 0;
}
if ($http_user_agent ~* "(W3\ Total\ Cache/0\.9\.2\.4)") {
    set $w3tc_rewrite 0;
}
set $w3tc_ua "";
set $w3tc_ref "";
set $w3tc_ssl "";
set $w3tc_enc "";
if ($http_accept_encoding ~ gzip) {
    set $w3tc_enc _gzip;
}
set $w3tc_ext "";
if (-f "$document_root/wp-content/w3tc/pgcache/$request_uri/_index$w3tc_ua$w3tc_ref$w3tc_ssl.html$w3tc_enc") {
    set $w3tc_ext .html;
}
if (-f "$document_root/wp-content/w3tc/pgcache/$request_uri/_index$w3tc_ua$w3tc_ref$w3tc_ssl.xml$w3tc_enc") {
    set $w3tc_ext .xml;
}
if ($w3tc_ext = "") {
  set $w3tc_rewrite 0;
}
if ($w3tc_rewrite = 1) {
    rewrite .* "/wp-content/w3tc/pgcache/$request_uri/_index$w3tc_ua$w3tc_ref$w3tc_ssl$w3tc_ext$w3tc_enc" last;
}
# END W3TC Page Cache core
	# BEGIN W3TC Skip 404 error handling by WordPress for static files
if (-f $request_filename) {
    break;
}
if (-d $request_filename) {
    break;
}
if ($request_uri ~ "(robots\.txt|sitemap(_index)?\.xml(\.gz)?|[a-z0-9_\-]+-sitemap([0-9]+)?\.xml(\.gz)?)") {
    break;
}
if ($request_uri ~* \.(css|js|htc|html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$) {
    return 404;
}
# END W3TC Skip 404 error handling by WordPress for static files
}

Alright, we’re almost done. What’s left now is just migrating our existing existing WordPress files from GoDaddy to our new EC2 instance.

[nextpage title=”Migrating WordPress”]

To migrate WordPress, we basically need to do two things:

  1. Migrate MySQL Database
  2. Migrate WordPress files
  3. Change DNS Record

Migrate MySQL Database

First, let’s backup the DB. The easiest way to do this is using WP-DB Manager. Install this extension first if you don’t have it.

WP-DB Manager backup screen
Once you backup it, use FileZilla to retrive the backup file. Because we are going to import the .sql file to database we created in step 4, it important to add this at the beginning of the file:

use wordpress;

Save the .sql file and then transfer it to our EC2 instance (put it in /tmp/ folder).
Now, in Putty, let’s import the backup file into MySQL.

mysql -u wp_user -p < /tmp/1348812291_-_wordpress.sql

Migrate WordPress files

First let’s create the directory to store the WordPress files

mkdir /var/www
mkdir /var/www/sodeve.net/
mkdir /var/www/sodeve.net/public
chown nginx:nginx /var/www/sodeve.net/public

Now we can copy all files from the old WordPress site at GoDaddy’s into our EC2 instance

Change DNS Record

To host our DNS we need to move our NameServers address to CloudFlare’s NameServers. First, login to your GoDaddy Domain Control Panel.

Click Set Nameservers and change the name servers to:

  1. DAVE.NS.CLOUDFLARE.COM
  2. PAM.NS.CLOUDFLARE.COM

Setting up DNS in CloudFlare is really simple so I’ll give it a pass. 😀
Alright, that’s basically all you need to do to migrate your blog from GoDaddy’s shared host to Amazon EC2.

I hope it helps. If you encountered problem following this example, please drop a comment or two below.

Cheers!

About Hardono

Hi, I'm Hardono. I am working as a Software Developer. I am working mostly in Windows, dealing with .NET, conversing in C#. But I know a bit of Linux, mainly because I need to keep this blog operational. I've been working in Logistics/Transport industry for more than 9 years.

1 Trackback

 

No Comment

Add Your Comment