Today I learned… in code

A lot happens between "Hello World" and "Supreme Master Programmer". Here, we share little discoveries made along the way. The more frustrating something was, the more likely it is to end up here on this site!

However, there’s more work to be done! Here’s what I did to raise my Google Insights mobile score.

Fixing render-blocking JavaScript on above-the-fold content on a WordPress site using W3 Total Cache and a CDN

Let’s look at this render-blocking JS/CSS problem first:

Eliminate render-blocking JavaScript and CSS in above-the-fold content.
Your page has 8 blocking script resources and 12 blocking CSS resources. This causes a delay in rendering your page.
None of the above-the-fold content on your page could be rendered without waiting for the following resources to load. Try to defer or asynchronosly load blocking resources, or inline the critical portions of those resources directly in the HTML.

This message is followed by a list of all the JS and CSS scripts causing the render blocking, which were 8 JavaScript files and 3 CSS files in my site’s case.

To fix the blocking JavaScript files I had to do a number of things to configure W3 Total Cache. This part took a couple hours of trial and error and some steps were not covered in the guides I used (or my situation was different from theirs), so I’ve attempted to document the process as I experienced it in this guide.

First, I enabled Minify in the general W3 Total Cache settings. Go to Performance > General Settings > Minify > check the “Enable” box and select the “Manual” radio button.

Next, go to Performance > Minify to work on minify settings as they pertain to JavaScript and CSS. You’ll need the list of blocking files from PageSpeed Insights report.

Watch out! The file paths shown in the PageSpeed Insights results might be truncated. To get the full path, you have to hover over the truncated path and copy the path from the tool tip.

In Performance > Minify there are a variety of options that apply to all the script in a particular area (before head, after body begins, before end of body). In my case, code in the head tag was already minified, so I set it to “Combine only” (rather than re-minify it). I also needed to keep it blocking, or else my page filled Chrome’s console with “can’t find JQuery!” errors.

Next, under JS file management, add all of the blocking files from the PageSpeed Insights results – be sure to get the full file path, not the truncated version.

Also! If you’re using a CDN, you’ll have to change the domain in the file paths that you paste.

Instead of this:

https://your-cdn.com/wp-includes/etc

You need to use your website’s actual domain. For each path you paste, change the CDN part to be your site’s domain instead, like this:

https://yourwebsitesrealdomain.com/wp-includes/etc

Or you can remove that part of the url entirely and go with something like:

/wp-includes/etc

…which is what I did for mine:

Note: I needed to set the jquery files to embed in <head> to avoid a slew of console errors when visiting the site. As per the earlier settings, anything embedded in head is going to be blocking. I think some blocking files might be inevitable; the point of these settings is to tease out which files belong where in the load sequence.

Save settings and purge all caches, then test again in PageSpeed Insights.

Woohoo – no JS files blocking loading. I also tested the site manually, and verified that it doesn’t look like garbage and doesn’t fill the console with errors. So far, so good.

Fixing render-blocking CSS with Autoptimize

I initially set up all the CSS file paths in W3 Total Cache but ran into these two cases that I couldn’t figure out how deal with through W3 Total Cache alone:

At this point, I loaded my site and found it was using serif typefaces on buttons and it wasn’t scaling the hero (header) image correctly. To fix this, I added custom CSS to Autoptimize’s custom CSS section. This CSS gets loaded right away, so the page doesn’t get stuck waiting on the js or css to “fix” certain issues that are visible right away to a visitor.

One more thing: I also uploaded a significantly more compressed “hero” banner image jpg to the site (old size: about 350kb, new size: about 110kb).

The reward for all these optimizations:

Note that I still have one render-blocking w3tc js file. For now, I’m going to call this “good enough”, since I don’t know how to fix this last one without breaking some fundamental aspect of the site, so I’m going to move onto the expiry problems and see if I can get the score higher that way.

Fixing “leverage browser caching” with W3 Total Cache

PageSpeed Insights found a few files it thinks could leverage browser caching. These files are all .js files.

The first step is to enable browser caching in W3 Total Cache’s settings. Go to Performance > General Settings and make sure Browser Cache is enabled.

Then, go to the actual Browser Cache section of the plugin. As per the guide, I ensured these boxes were checked:

Set expires header

Set cache control header

Set entity tag (eTag)

Set W3 Total Cache Header – this one was not already checked for me

Enable HTTP (gzip) compression

(I re-ran the PageSpeed Insights test at this time just in case that one checkbox was all it took. Alas, there was no change in the results.)

The next thing I did was scroll down a little bit to the CSS & JS section. My “expires header lifetime” was set to 172800, which is way less than the recommended 604800 (2 weeks) the guide recommends. I saved that change, cleared cache, and ran the test again.

Now I have just three files with unsuitable caching times:

Alas, two of the remaining files are out of my hands: they are Google Analytics’s own .js files. After reading this guide to browser caching Google Analytics, I decided not to try to cache the analytics js file myself. Doing so would require manually updating it periodically. Even the guide’s author says they don’t recommend this method. That’s fine by me.

But how about this emoji js file? I don’t use emojis on my site, so I wouldn’t mind removing them and reaping a small page speed boost.

Normally, removing them is as simple as adding these two lines in your site’s functions.php:

Fixing the “flash of unstyled content”

A new problem has been introduced by all this work: now my page looks unstyled for half a second or so while it’s loading.

I used this tool: Critical Path CSS to identify a the “minimum” CSS needed to make the “above the fold” content look good (it’s a huge block, I’m sure a human could produce something more elegant but for now, it’ll do).

I put this CSS into Autoptimize’s “above the fold” CSS section and the flash of unstyled content seems to look better now, though I still see the fonts looking bad before the preferred “open sans” style comes into effect.

Done… for now

At this point, I’ve gotta call it good enough for now. My site’s score went from 8/100 to about 62/100. I still have a one blocking JS file that I don’t know what to do with and two files from Google Analytics with unsuitable cache expiration times.

That referrer, cdn1-mycompany.netdna-ssl.com, isn’t allowed to serve this file. But there’s an easy fix: you can whitelist the CDN itself in the CDN.

The fix: whitelist the CDN itself

In MaxCDN, go to Pull Zones > Security > Whitelist.

You might already have yourdomain.com in here. What you need to add is the domain your CDN files are pulled from.

In my case, that was a domain that took the form of cdn1-mydomain.netdna-ssl.com, but you can find out what yours is by looking in the Network tab while you try to load your site. Look for a red-colored error message and open the Headers.

Anyway, the fix is as easy as adding the domain to this list of whitelisted domains and waiting a few minutes (for me it was about 10 minutes). Reload your website and the Font Awesome icons should now appear.

Note: You will probably need to Purge All Caches, too, once the time has passed to actually see the change in your browser (I use the dropdown in the WP toolbar).

My W3 Total Cache settings

(Just in case it’s useful to someone else trying to debug this problem)

My W3 Total Cache settings are set to upload .css, .tff, .otf, .woff, and .woff2 files.

Provided again as text so you can copy/paste.

wp-includes file types to upload:

*.css;*.js;*.gif;*.png;*.jpg;*.xml

Theme file types to upload:

*.css;*.js;*.gif;*.png;*.jpg;*.ico;*.tff;*.otf;*.woff;*.woff2

This is a pretty specific configuration issue but hopefully it’ll help someone else out there!

The Twitter/Facebook 403 fix

There were two problems with my site:

Problem 1: My site’s posts didn’t define any Open Graph images in the first place. I figured Twitter, Facebook, etc. were smart enough to scrape the post and pick an image all on their own, but it seems that’s not always the case.

Now, at the bottom of every post, is the option to explicitly define an image to use when sharing. This image can be larger than images you might normally include in a post (maybe even custom-made for the purpose) and need not appear in the post itself. (Unfortunately, you do have to go back and manually add an image to each post.)

After doing this I was still getting a 403 when previewing my post in the Facebook and Twitter tools.

Problem 2: The other part of the problem was with my CDN settings themselves. Twitter (and Facebook, etc.) aren’t actually allowed to link to images hosted on my CDN – they aren’t whitelisted. My CDN is set up to only serve images on my site itself, so other people can’t link directly to my CDN images and effectively steal the bandwidth that I pay for (truthfully, I wish people were this eager to link to my content).

I had to add Facebook and Twitter to my W3 Total Cache’s list of rejected user agents.

Under the Performance tab (left side of WordPress interface), click on CDN:

Then scroll down into the Advanced section and find “Rejected user agents”. Type facebook.com and twitter.com. These agents are not allowed to access files hosted within the CDN. (Which is what we want, because the CDN won’t let them do it anyway.)

You may need to also do Performance > Purge All Caches from the top toolbar in WordPress, too.

I just moved one of my blogs to a new host (yay!). This blog uses MaxCDN for its content delivery, and moving the blog to a new host messed up the site’s styles and it took me a while (plus some back and forth with support) to get everything fixed because MaxCDN was still referencing the old host.

In case I ever do this again, here’s what needed to be done to move my WordPress blog to a new host with MaxCDN as my content delivery network.

Step 1: Add a CNAME record to your new host

My new host has CPanel (Digital Ocean, by contrast, had a Networking tab with a link to Domains and their records were accessible through there). If you have CPanel, click on Simple DNS Zone Editor.

Add a new CNAME record. It’ll probably look something like this:

Name
cdn1.yoursitename.com.
Record
cdn1.yourbusinessname.netdna-cdn.com

(Your CPanel might add the . at the end of name for you, and it might autocomplete for you if you just type the subdomain portion and then tab out of the field.)

Go to Zones > Pull Zones > Settings and get into that particular pull zone’s settings. At the bottom is Origin Information. Check the checkbox and enter the IP address for your new host. Click Update button to save.

Step 3: Whitelist your new IP

You may also need to whitelist your own IP address, if you get problems with cURL requests failing when you try to clear CDN cache.

Step 4: Update your WP caching plugin

You may need to reconfigure your WP cache plugin. I use W3 Total Cache which, for reasons unbeknownst to me, likes to replace my entry for “Replace site’s hostname with:” with the word “Array” instead of the URL I give it.

For reference, “Replace site’s hostname with:” should be followed by your cdn url, like cdn1.yoursite.com.

Step 5: Purge all caches and check your site

It might also help to flush your local DNS. I’m on Windows and I do that in a command prompt with ipconfig /flushdns

Open a Chrome incognito tab and load your site – if your styles and images load, you’re good to go. If your site looks incomplete, look in the console for an error message. I found many of them (like 502 bad gateway) to be covered in MaxCDN’s documentation.

More tools for debugging DNS, caching issues

If you are using a custom domain with MaxCDN like I am, then putting that custom domain into whatsmydns should yield the actual “business” domain in the results list. In other words, if you search for cdn1.yourcustomdomain.com and you get responses of cdn1.yourbusiness.netdna-cdn.com, you’ve got it set up correctly.

Is your CDN URL responding? In a command prompt / Terminal window, ping cdn1.yourcustomdomain.com and see if you get anything. If it can’t find your host, this could indicate an error with your CNAME record with your new hosting service.

Are you seeing stale or current stuff in your browser? I use Chrome incognito because each window starts with a fresh cache and no cookies. CTRL SHIFT N opens up a new incognito window.

You may also want to flush your DNS in between tests. ipconfig /flushdns does this in Windows.

If all else fails, email MaxCDN’s customer support. Even on a US holiday, I got a response within 20 minutes and they helped me get things working again.

Moving a WordPress site from one host to another with minimal of downtime doesn’t have to be a huge hassle, and it’s easy to do it yourself even if you aren’t a web developer. Here is the process I use to move a WordPress site to a new host, with about 10 minutes or less of actual downtime (and because of caching, many visitors during the migration may not even see the outage).

I like this method because:

It’s easy

You don’t have to mess around in MySQL

It’s free

It’s maybe 10 minutes of downtime for your site, depending how fast you can upload your backup and how much you have to do to get your caching plugin/CDN (if you have them) on board with the new IP address

Before you begin, make sure you have:

A WordPress site on your current host

Access to your new hosting account (preferably with CPanel and phpMyAdmin to get the most out of this guide)

Access to your domain’s DNS records (yoursite.com may be registered with your current host, or a separate registrar)

Nameservers for your new host (they usually look like ns1.newhost.com)

FTP access to old host and new host via your choice of FTP software (I use Filezilla) *optional* – you can do the same stuff through your host’s CPanel File Manager if they have it

About an hour of time to dedicate to reading this guide and the actual migration

I use the StartUp package for my up-and-coming sites, and the GoGeek plan for my top performers. You can upgrade your plan at any time as a site grows. I especially like the GoGeek plan because they throw in SSL for free (or at least they did for my first year) and because it has a separate staging environment for testing stuff on a copy of the site before pushing it live.

Next, install WordPress on your new host. Many modern hosts (including SiteGround and BlueHost) have an easy one-click install for WordPress nowadays – look in the CPanel or just the dashboard in general once you’re logged in.

Don’t worry about picking a login/password you want to use in the long run, your Updraft backups will replace whatever you choose during setup with whatever your existing site already has. Do write down whatever name/password you choose here, you’ll need it to access your new WP install until you overwrite it with your backups.

It’ll probably tell you the installation was successful and you can go see it at the following url, but that link won’t work because you haven’t updated your domain’s nameservers yet.

Step 5: Change your domain’s nameservers

I do this in dynadot.com’s domain manager because that’s where my domain is managed, but your domain may be attached to your old hosting. In any case, change its two nameservers from ns1.oldhost.com and ns2.oldhost.com to ns1.newhost.com and ns2.newhost.com (or similar).

It should propagate fairly quickly (check it here: https://www.whatsmydns.net/) but it may take a while to see the change on your machine. One way to speed it up (on Windows, anyway) is to open a command prompt (cmd) and type ipconfig /flushdns.

Load your site again (in an Incognito window in Chrome or after clearing browser cache) and you should now see your new WP install.

Step 6: Install UpdraftPlus on your new blog and restore backups

Now that you have dashboard access to your new WordPress installation, install UpdraftPlus and click Restore.

Drag your 5 files here and wait for them to upload.

When those are done uploading, click Restore.

Follow the prompts until you’re force to re-log in to your site. It should now look exactly like it used to on your old host, but you can confirm that it’s actually on your new host by pinging it in a command prompt or Terminal window (ping yoursite.com). If the IP address returned matches your new host’s, you’re good to go.

Extra step for CDN users:

I’m on MaxCDN, but regardless of what CDN you use (if you use one) there will probably be some additional setup steps to make sure your existing CDN account references your new IP and host.

Step 7: You may need to do some other setup on your new host

Leave your old host active for a little while while you confirm everything’s working on your new site over the next couple days.

You may need to move the following separately:

Email accounts. If you had email accounts set up at your old host, take note that they don’t come with the Updraft migration and you’ll have to recreate them (and redo any redirects) on your new host.

Favicon: if your site had a custom favicon sitting in your site’s root directory, you might need to copy it from your old host and upload it to your new one

Google Analytics .html file: If you put any .html files for analytics tracking (Google Analytics is the one I always have to move manually) in your old site’s root folder, you will have to copy them to your new host

Robots.txt and anything else sitting in root (this will vary by site)

Images or other media in dedicated folders: Anything that’s part of your site but not part of WordPress will have to be manually moved. For me, this is sometimes a site logo or images on the site that I keep in a separate images folder, not uploaded to WordPress’s file manager.

If you’re afraid of losing anything off your old site, take the time now to download a copy of its entire directory off your old host, before you shut down your old hosting account. That way, if you find something missing later on, at least you can dig around the old files and maybe find it.

Error establishing database connection is my most hated WordPress problem – it’s so cryptic and so many things can cause it. I run about a dozen WordPress blogs: some are on shared hosting, some are on virtual private servers (with Digital Ocean), and nearly all of them have had this problem at some time or another. This article documents what I do when it happens to me.

If it’s any comfort, I’ve never not solved this problem (eventually). It’s definitely fixable, but there are a lot of things that can cause it, so if nothing here helps you just keep digging around in the Googles – and good luck.

Before you do anything, turn on error messages and see what the problem actually is

Get into your website’s files, either through FTP or your host’s control panel, and turn on debug mode in wp-config.php. This file is in your WordPress installation directory.

Change this line to true:

define('WP_DEBUG', true);

Now go back to http://sitename.com/wp-admin and get those juicy error messages.

This step alone can save you a lot of frustration as you debug the actual cause of your WordPress blog’s Error Establishing Database Connection problem.

Possible fix #1: make sure DB_USER and DB_PASSWORD match what your host has

The vast majority of the times I run into Error Establishing Database Connection on a shared hosting site, it’s because something (I don’t know what) caused the DB_PASSWORD in wp-config.php to become out of sync with the password my host has for that user. This particular flavor of the error connecting to db problem seems to only affect my sites that are on shared hosting (most recently, it happened to a site I host on lunarpages.com).

Basically, what wp-config.php has for DB_USER and DB_PASSWORD has to match what your host has saved for that database and particular user.

By turning on WP_DEBUG in step 2, I was privy to the following error messages when attempting to access http://mysite.com/wp-admin:

If you know what your DB_USER and DB_PASSWORD are supposed to be, maybe you’ll spot a discrepancy here. Chances are, you don’t know what’s supposed to go here, so you can’t tell just by looking if it’s right. That’s okay, I don’t either, but it’s easy to get everything matching.

First, if you have CPanel on your host, you can log into CPanel and go into MySQL Databases to see a list of users associated with your database(s).

Next, find your database and look in the Privileged Users column. One name from the Privileged Users column has to match the username given in wp-config.php.

If the user your wp-config.php file is expecting is already in this column, you’re good to go to the next step where you reset its password.

If you don’t have the same user your wp-config.php expects, either add that user here or change wp-config.php to reference a user you do have.

Still on the MySQL Databases page, scroll down into “Current Users” and find the user your db is using. Click Set Password.

I just change the user’s password to something randomly generated, it doesn’t matter. Copy that password and paste it right into wp-config.php

Try the site again: if it works now, you’ve resolved your “access denied” error and may now have full access to your WordPress site again.

Possible fix #2: it could be a bad plugin

This one is easy to test the fix for but I’ve only seen it be the problem once, and it happened right after I messed with plugins so the cause was obvious. However, with more plugins and WP things going to “auto update” these days, I could see how this might crop up independent of blog-owner interaction.

Rename the plugins folder.

I log into my site’s file manager via my hosting service’s website or FTP, navigate to /wp-content and rename the folder called plugins. (Don’t delete it, just put an X at the end or something.)

plugins > pluginsX

Try the site again – if it loads, your problem is one of your plugins. You can narrow it down by renaming plugins back to its normal name and then turning plugins off in groups to narrow it down to a specific one.

If your site doesn’t load, put plugins back to normal and go to the next step.

Possible fix #3: maybe your MySQL service croaked – try restarting it

This particular flavor of “Error Establishing Database Connection” seems to affect my Digital Ocean (VPS) hosted blogs (not my shared hosting blogs). There are many reasons why MySQL can crash, but when your WP site is down and you’re losing money by the hour, getting it back online is probably your #1 priority.

Since my Digital Ocean hosting runs on Linux, I log in to the virtual console and check if mysql is running with this command:

mysqladmin -u root -p status

This command brought MySQL back up:

service mysql start

Now, as to why it crashed in the first place, that could be any number of things, and chances are, MySQL will go right back down again as soon as the same conditions return.

The various fixes I’ve applied in effort to stop chronic MySQL crashes on Digital Ocean merit their own article someday, but for the sake of helping anyone who might find this, here’s a brief overview of stuff I’ve done on my VPS WordPress to try to stop frequent MySQL crashes.

I tried to figure out what was using up memory by logging into my droplet’s virtual console and looking at all the active processes sorted by what resources they are consuming. The command to see that chart is top.

You should now see your process list sorted with the most memory intensive processes at the top. What you find here will help you Google for solutions.

For me, mysqld is always at the top, soaking up all the memory, so I focused on that when I was trying to fix chronic “Error Establishing Database Connection” problems on my Digital Ocean WordPress blog. After mysqld was always a whole ton of apache2 instances.

Restarting the droplet has helped, too. One time I cleared my blog’s MaxCDN cache and that immediately took the site down and replaced it with Error Establishing Database Connection. When that happened, restarting the droplet brought it back up.

To be honest, MySQL crashes on Digital Ocean are kind of an ongoing issue for my most popular WordPress blog, but I’ve managed to lengthen the time between crashes/restarts with the above steps.

Even more help with WordPress db error (articles, threads, etc)

WPBeginner has the Internet’s de facto go-to article on the subject, and they also report that somehow the database credentials on their shared host site got reset. They also have some solutions I’ve never had work for me but are worth looking into if nothing in this article worked for you.

This guide was written after I completed the process of hooking up MaxCDN to my Digital Ocean hosted WordPress blog.

This wasn’t a straightforward process, partially due to my own errors and partially because I was using WP Super Cache which I’m pretty sure just doesn’t cooperate with MaxCDN for reasons I may never understand. However, I now have it working so here’s my guide to everything I did.

Why I wanted a CDN: To speed up my top money-making property, which is also my slowest because it’s got long articles and lots of images.

I chose MaxCDN because it seemed to have a lot of good reviews and at about $10/month was reasonably priced.

Setting up MaxCDN with W3 Total Cache and a custom CNAME on Digital Ocean

I signed up and was disappointed to see that the URL MaxCDN gave me by default included the business name I used when I signed up. I wanted cdn.mysitename.com, but by default MaxCDN gave me cdn1.mybusinessname.netdna-cdn.com. MaxCDN calls this the “branded domain” and you can use it as-is, but I would guess most people want to customize it.

My business name and my website name are not the same and I didn’t want to expose the former in the latter. (They don’t tell you they’ll use your business name this way when you sign up, nor do they let you change it once you opened your account. Ugh.)

Fortunately, you can set up a custom domain and use that instead, and my steps below include that process.

Step 1:Enter your custom domain into the pull zone settings. Go to Pull Zones > Settings and fill in the Custom Domains field with cdn.yoursitehere.com (or cdn1.yoursitehere.com, or whatever you prefer. The important thing is that all three parts of the url are present.)

Step 2: Get into Digital Ocean’s record management. Inside Digital Ocean’s droplet management page, go to Networking and then go to Domains. Click the magnifying glass next to the domain you’re adding this CDN to to get into its records. More help adding/editing domain records in Digital Ocean.

Look in Networking, Domains

Step 3: Add a CNAME record to your droplet’s domain. If you want cdn1.yourdomain.com, fill the form out like this:

Enter Name: cdn1

Enter hostname: cdn1.yourdomain.netdna-cdn.com (the “branded” domain that Digital Ocean gave you by default)

Be sure to click the Create CNAME Record button to actually add it to the list of records.

Assuming nothing else is conflicting (you didn’t leave your DNS hooked up to CloudFlare like I did, you don’t have a competing www.yourdomain.com record like I did, etc), this should happen in 20 minutes or less.

At first, I was using WP Super Cache and every time I turned on the CDN feature my site’s styling and images disappeared. I fought with this for a while, got good and frustrated, then tried W3 Total Cache like MaxCDN suggests and WOW – it was like night and day. I followed MaxCDN’s tutorial and basically, it just worked. Be sure to whitelist your site’s IP (go into Terminal or command prompt and ping www.yoursite.com to get your site’s IP if you don’t know it, or look in Digital Ocean’s droplet list).

If everything’s working, you should be able to load your site (yoursite.com) at its usual url (not the cdn url) and look in the network tab of your browser to see responses coming in from the CDN url (cdn.yoursite.com).

Fast way to check: right click any image on your site and view it in another tab. Its url should be your cdn’s url.

If you don’t have any images, you can see this in Chrome by right clicking to Inspect and then click over to the Network tab before loading your site. Hover over some resources (like .css resource) and you should see cdn1.yourdomain.com.

You should also start to see improvements to your page’s load time immediately.

If you don’t see the changes right away: While the cdn1.mysite.com changeover was observable right away on the computer I was working on, it wasn’t on my laptop. The cause seems to be the computer’s own DNS settings. By switching my laptop’s DNS settings to Google’s, I was able to see the most up-to-date (ie: not cached) version of the website.

(Alternatively, waiting a while – up to a day – should resolve it with no changes to your computer.)

Pingdom results, before and after MaxCDN

Here’s a couple of “before” load times on my website, taken less than a minute apart and both from New York City. The thing I often notice on Pingdom’s Website Speed Test is how much variance there can be in load time across multiple tests when all other factors remain the same (location testing from, time of day, etc).

Site speed: before

Here, there’s a huge difference in load time just on these two “before” tests.

The kind of extreme difference in load time seen above is why I like to run several Pingdom tests when collecting my “before” data before making a change that I think should effect load time. Here’s another test, this one from San Jose, California.

Site speed: after

Whoo! Look at that, down to <2 seconds since turning on MaxCDN:

Here’s an even faster one, best one I saw in my several tests:

And one from San Jose:

So far I like MaxCDN. Their online support is very responsive. I sent a detailed plea for help on a Sunday afternoon in the U.S. and had a (reasonably) customized response within an hour. Their documentation is plentiful and thorough. They include many examples and their screenshots are up-to-date with their current interface.

The real test will be in seeing if getting the site speed down to ~2 seconds has any noticeable effect on the site’s Google rank and total conversions (sales).

CDN Traffic Boost?

Totally unscientific study here, but my site’s traffic pulled out of a slump as soon as my CDN hookup went live. (March 17 is just beginning as I post this update.)

This report is for Year 2 of my “blogging for bucks” endeavor in which I turn my love of research and writing into a profitable hobby. Read about the first year here (it’s a much longer and more detailed post than this one and covers all the “how to get started” stuff).

When I started blogging for affiliate bucks in summer 2013 I said all of the following:

“I’m starting too late”

“All the good ideas are taken”

“The Internet doesn’t need another blogger”

“The people who actually make money off this know tricks and secrets I’ll never know”

“I’m going to shill products I don’t give a shit about, and it’s going to be boring writing about them.”

Then I went ahead and did it anyway.

Turns out, none of the above was true. 2015 was an excellent year, bringing in more than twice 2014’s haul.

I made $12,600 this year from the Amazon Affiliate program and Google Adsense. Just like in 2014 (my first full year blogging), nearly half of that money was made in the last three months of the year (I love you, holiday shopping season). It wasn’t all for me, though: the IRS took about a quarter of it as their cut in tax season (ow).

2015 Amazon Earnings screenshot(my earnings are the bottom right number)

I still firmly believe that this is a viable make-money-online strategy that anyone with the willingness to learn, research, and write can do. It’s not too late to start. It’s not even very labor intensive, as evidenced by this “coasting year” still making a decent profit. Most of the work you’ll do is upfront when you set up a new site and load it with your (well-written) content.

To be frank, I ran out of blogging steam for most of 2015. I didn’t write but a handful of articles and I didn’t start any new blogs until December. I was busy with my brand new job and some major life changes.

But if 2014 was the year of sowing, then 2015 was the year of reaping. As the blogs aged, more people found them – and they liked what they found.

2015 Blogging Year in Review

January

I started my first full-time programming job in January 2015. This was a career change many years in the making and I didn’t have any spare brain cells for my blogs after work each day.

The sites coasted through January – I added no new content and I didn’t promote anything.

But sales didn’t drop with the conclusion of the US holiday season, curiously enough: January 2015 brought in almost as much ($739) as December 1014 ($819). My guess: people go buy themselves the stuff they didn’t find under the tree.

February and March

Sales dropped these months, reaching their lowest point of the year in March ($386). The holiday gravy train had to end somewhere, but I don’t think this was some flaw in my blogs – I think people just don’t buy that much in these months, or they don’t buy that much stuff in the niches my sites are in. Traffic more or less held steady, though, across all sites.

April, May, June, July, August, September

In April sales began to climb again, peaking in August with $1,166! I continued my do-nothing approach and added very little new content during these 6 months (I added 1-2 articles to each of my top-performing niche sites). I put some minimal effort into updating existing content to keep it fresh, though. On Gizmo Blog, this meant keeping up with manufacturer updates to products and industry news.

I think the moral of the story here is to not “give up” on a site. Leave it up, sites are cheap. Even my worst performing sites making $1-2 a month pays for their own domain by the end of the year, and all the while they’re aging like a fine wine (in Google’s eyes).

It’s okay to keep adding content at a snail’s pace, updating here and there, tweaking things along the way. Maybe if you want to make five or six digit earnings every month then you need to add content weekly or daily or whatever, but I definitely didn’t add anything meaningful to my already-established blogs and I was rewarded with a decent “side income” for my complete lack of effort.

October, November, December

Here they are: the three months that make 48% of the year’s income. November was the top-performing month, bringing in $2,076, but December was almost as good, bringing in just $30 less than November.

In honor of the shopping season, I did a few things to help buyers find me:

Added a 2015 holiday buyer’s guide and top sellers list for each niche site

Tweeted those lists (regularly) from each site’s associated Twitter account

Honestly, I’m amazed that a collection of neglected sites did so well. Imagine what they could’ve done if I’d been adding content all year long!

Year in summary (a pie chart!)

As the chart illustrates, Gizmo Blog accounts for a full 66% of my Amazon Affiliate earnings, and Craft Blog the next 20%. That means a full 86% of my earnings come from just two sites. Better diversifying my Amazon Affiliate income is one of my goals for 2016 – I don’t like having too many eggs in any one blog basket.

“Other” includes this blog, TILCode, which made a grand total of $7 off Amazon Affiliate earnings in 2015 :D (Thank you, whoever bought a book or two through this site.)

What about SEO techniques?!

I chatted with a number of people about blogging for side income in 2015 and the most common question they asked me was, “what kind of SEO do you do?”

The short answer is, I don’t do any.

I pick a product, research it (sometimes I buy it and use it myself), basically obsess over it, and tell people why it’s better than its competitors in a 1500-2500 word review.

I pick products I like myself – stuff I’d buy if I had unlimited funds, or stuff I already own. That’s it! People who want to know this information – people on the cusp of making a purchase – find me through Google and go through my site to Amazon to make their purchase, which I then get a cut of at no extra cost to the shopper.

There are a lot of techniques out there that promises results (paid links, WordPress plugins, content strategies, backlink strategies) and I can’t vouch for any of them. I mean, maybe there’s something out there that would turn my $2,000 months into $5,000 months, but at what cost? Hundreds of dollars in bought backlinks that Google would eventually sniff out and knock me down in the search results for? I’ve been content to build my sites myself and let the natural links come in at their own pace. YMMV.

2016 goals

Better diversification – 86% my Amazon Affiliate income comes from just two of my sites, so I want to get that down to 50% by EOY by growing income from other sites.

Alternative revenue source – virtually all of my passive online income comes from Amazon Affiliate program. It’s a great program but if it ever goes away, I’ll be pretty sad and won’t have anything to back it up.

$18k in earnings – that’d be nice!

Set up my blogs as a legal business entity, as per my financial adviser’s advice and because it’ll make tax season easier and more in line with what the IRS expects from a “side business”

Optimize sites – load times are bad (>6 seconds in some cases), they’re mostly on shared hosts, I get a lot of “Error Establishing Database Connection” problems (my VPS seems to get overloaded with one site in particular) – there’s just a lot of technical performance things that could be better

Actually add more new content to sites

And that’s it for 2015! Onwards to 2016, where I hope to do a better job of adding content and optimizing the sites for even better earnings.

This post documents the steps of moving multiple subdomain blogs from my old host to Bluehost. I had about 8 blogs to move from Lunarpages to Bluehost, and by the time I was done I had this process down to a science.

These steps are almost certainly applicable to moving from just about any shared host to Bluehost, but they are written specifically to WordPress and to the process of moving WordPress blogs that are using add on domains / subdomains and exist in folders contained within the root (public_html) directory.

If you have a bunch of WordPress blogs on one host, you probably have them set up as subdomains like this:

techblog.mydomain.com

koreanbbqblog.mydomain.com

catpicturesblog.mydomain.com

Most “how to move your WordPress blog” steps assume you’re moving one blog, but this post will help you move lots of blogs. This guide also assumes you were technically savvy enough to get yourself into the situation of having multiple blogs on subdomains and isn’t written for first timers.

Also, this process will take the specific site you are moving down for the some part of the move. My steps minimize the downtime, but be aware that there will be some downtime while you copy your database over to your new host. I was okay with that (it was about 25 minutes of downtime per blog), but if you’re not, you may want to look into alternative methods of moving your blog(s).

If you aren’t already a Bluehost customer and are still shopping for an excellent shared host for your WordPress blogs, personal portfolio site, etc, take a look at their current deals below:

Before you begin

Have all of these things handy:

an ftp program (such as Filezilla)

old host website login credentials (oldhost.com)

old host ftp login credentials (ftp into your site)

new host website login credentials (bluehost.com)

new host login credentials (ftp into your site)

domain registrar login credentials (if separate)

about 1-2 hours to step through this process

Prep Steps

These steps can be done any time. They won’t take your blog down, and the wp-content copy step can take a while, so feel free to start them and continue the “Moving Day” steps later.

Step 0: Log into your existing WordPress installation.

While you’re in here, make sure it’s up to date. Disable any plugins you can live without. Disable caching plugins. (You can keep all plugins enabled, but I find that they move more easily when disabled.)

Copy wp-content to your local hard drive. This is where all of your blog’s content is kept, and it’s really the only WordPress thing you need to copy over. Everything else will be handled by a fresh install of WordPress on your new host.

Fun fact about Filezilla: by default, there are speed limits on upload and download. Remove them by going to Transfer > Speed Limit.

Did you upload a favicon, a banner, an .html file for identifying your site to Google webmaster tools, etc? You might have things in your site’s root folder, so copy those things over, too.

Step 3: Copy your blog’s sql database to your local hard drive.

Log into your old host’s cPanel.

Find phpMyAdmin and log in.

Find the database associated with your blog (and click it)

Click Export in the toolbar at the top

The default settings are fine

Click OK

You’ll download a .sql file – hang onto this for later

If you have lots of blogs on one host, you might have lots of databases and they may have cryptic names. If this is you, look in the table with the _options suffix to verify which db goes with which blog.

Wait, there’s one more thing!

How large is your .sql file? If your .sql file is under 50mb, skip ahead to “Moving Day”. If it’s larger than 50mb, I have some bad news: Bluehost won’t let you upload it via phpMyAdmin. You’ll have to use .ssh and the command line to upload your database. Those steps are further down in this guide, but they’ll add 1/2 to 2 hours to this process depending on how experienced you are with .ssh and how quickly you can get set up and logged in.

If your database is under 50mb, you can keep following along in the next section.

Moving Day

These steps will take your blog offline while you complete them. Expected downtime is less than an hour, depending on your db size.

Step 4: Change your nameservers to Bluehost’s (or whoever’s).

Log into your registrar (I use and love Dynadot) and find the domain of the blog you are moving. Change its nameservers to the nameservers Bluehost tells you to use for your account.

For me, that’s:

ns1.bluehost.com
ns2.bluehost.com

Step 5: Add the “add on domain” to your Bluehost account

Log into Bluehost, go to cPanel, and look for Add on Domains. Enter your domain into the field and wait for Bluehost to validate it.

After Bluehost validates your domain, scroll down. Keep the “add on domain” radio button checked. Create a new directory for this add on domain. I like to name my add-on directory after the WordPress site it’ll soon hold.

Step 6: Return to cPanel, install WordPress.

Bluehost has (or at least had) a quick “Mojo Marketplace” WordPress installer. I like to reconfigure the defaults and name my site SiteNameBLUEHOST to help me identify its database later on, since Bluehost gives the WordPress databases cryptic names.

Choose your subdomain out of the list (I always pick the one without the .www, might just be personal preference).

Wait for the install to complete.

Step 7: In your FTP program, log into Bluehost and upload wp-content into the new install’s directory.

You just installed WordPress, so navigate to its folder via your FTP program and when you find wp-content, copy your site’s version of wp-content over it. This step may take a while.

Step 8 (small database): Replace that new Wordpress installation’s database with your site’s exported database. (This only works if your database is under 50 mb).

While wp-content copies, you can log into Bluehost’s cPanel again and go to phpMyAdmin.

Find the new database and click it. If you have a lot of databases, look in the table with the _options suffix to identify the correct one. (This is why I like to name my new blog installation something identifiable, especially when dealing with multiple sites. That blog title you entered at installation time will show in _options.)

With the correct database open, click “Check All” and choose With Selected: “Drop”. (Drop is database speak for “delete”).

Now use Import to import your existing .sql file into this database. Note the prefix used. (In my screenshot above, the prefix is wp_ but not all of the databases I imported came with wp_.)

The import command is really the only tricky part. It needs all of the following:

Your database’s username. This is not your Bluehost account name. The database user is defined when you set up the database and is probably prefaced with your db name. You can see a list of users associated with your databases by clicking on MySQL Databases in Bluehost’s cPanel and scrolling all the way down to where the users are kept.

Target database’s name. This is the database you’re going to overwrite. By default, Bluehost WordPress databases have cryptic names like youraccountname1_wo1234. If you have a lot, make sure you know which one is the one you want to overwrite.

.sql file name. You have this on your hard drive, and you’ll need to upload it to your account into a place you can find easily while you’re in the terminal (you can just dump it into public_html via your ftp program, just remove it when you’re done).

Go back to your FTP program (hopefully wp-content is done copying over by now) and upload your .sql file somewhere on your Bluehost account. I just dumped mine into public_html (you can remove it later). (What was that about .ssh being more secure?)

Now go log in via Terminal (Mac) /Putty (Windows). Remember, you are on your account’s part of Bluehost’s server, not your local hard drive. Use pwd and ls to get your bearings. Navigate (cd foldername) to the folder you uploaded your .sql database file and run the command that imports it.

That command will look something like this:

mysql -p -u username_wo1234 username_wo1234 > yourdb_filename.sql

Uploading through .ssh got the job done, but using it for the first time came with a lot of kinks to work out and added nearly 2 hours to my site’s downtime. Hopefully, the steps detailed above will help you do it faster than I did.

Step 9: Hook up the database by ensuring $table_prefix matches.

If you’re here, congrats – you’re through the hardest parts. There’s a small chance your site is already working.

There’s a larger chance, however, that your site is just a white page or a database connection error message. This step fixes the database connection problem. If you don’t have that problem, skip ahead to the next section.

In Bluehost cPanel, go to File Manager.

Navigate to public_html and click on the directory where you’ve set up your blog. Inside, you should find wp_config.php. Right click the file and choose Code Edit.

Inside, look for $table_prefix on or around line 65. Make sure whatever’s here matches what your db actually uses as a prefix.

If you had a database connection problem, there’s a good chance this solved it.

Step 10: Disable plugins to fix blank white WordPress page

If you’re getting a blank white page, try logging in directly via yourblogurl.com/wp-admin. If you can get in, try disabling plugins until the site loads.

If you can’t get in, go into File Manger and find the plugins folder. Right click it, choose rename, and rename it something else – like pluginsX. Doing this will disable all of your WordPress plugins. Now try yourblogurl.com/wp-admin. If you can get in, reactivate plugins from within your admin panel.

Try your links now – do they work? If so, go back to Permalinks and change them back to the way they were. If they don’t work, try editing a post and saving it.

Step 12: Set up email addresses, forwarders for your site.

Chances are, you had some nice emailaddress@yourdomain.com for each of your WordPress sites, probably with forwarders to the main address you use. This step is just a reminder to go to Bluehost’s cPanel and recreate those, along with the forwarders.

If your sites are like mine, you have a few special things in the root folder of your site. This is a reminder to re-upload those things to your new host.

All done!

At this point, your subdomain blog should now be fully moved over to Bluehost! Well done!

I hope you enjoyed this guide, and if you spotted any errors or outdated information, please let me know in the comments.

And, in case you’re curious, yes, I love Bluehost. I was a Lunarpages customer for 9 years but Bluehost outclasses it in every way – bandwidth, CPU allocation, ability to handle the traffic my blogs collectively pull in, uptime, ease of use, and customer service.