Automatic WordPress Permissions
I’m quite the fan of WordPress. It has its faults, but it’s extremely easy to get going with. Unfortunately, one of the biggest issues is getting the permissions set up correctly.
In order to speed up this process, I’ve written a script which you can run on your server to automatically set things up the way they should be.
It’s a simple bash script which requires a little configuration. Once you’ve added in the right values for the owner/group of WordPress you’re good to go and should be able to run it.
Let’s get cracking!
Nerdy Facts
Recently a friend of mine asked about showing random facts on his blog. He found a nice website with a few facts, but it hadn’t been updated in a while so the search was on.
Fortunately, there’s a blog at tumblr called nerdy-facts which has plenty of content. This plugin simply scrapes that site, downloads the images and then shows a random one each day in a widget.
The code is a bit rough, assumes you have pre-seeded the storage folder and isn’t fully tested. But it works.
<?php /* Plugin Name: Nerdy Facts Plugin URI: https://eliottrobson.me Description: Adds a "nerdy facts" widget from tumblr. Version: 1.0 Author: Eliott Robson Author URI: https://eliottrobson.me */ define('NERDY_FACTS_FOLDER', trailingslashit(WP_CONTENT_DIR) . 'nerdy-facts/'); define('NERDY_FACTS_URL', trailingslashit(content_url()) . 'nerdy-facts/'); /** * Called when the plugin activates, setup once */ function nerdyfacts_activate() { // Create the storage folder wp_mkdir_p(NERDY_FACTS_FOLDER); // Schedule update if (!wp_next_scheduled('nerdyfacts_seed')) { wp_schedule_event(strtotime('23:00:00'), 'daily', 'nerdyfacts_seed'); } if (!wp_next_scheduled('nerdyfacts_select')) { wp_schedule_event(strtotime('00:00:00'), 'daily', 'nerdyfacts_select'); } // Check for updated images nerdyfacts_seed(); // Select a new images nerdyfacts_select(); } /** * Called when the plugin is deactivated */ function nerdyfacts_deactivate() { wp_clear_scheduled_hook('nerdyfacts_update'); } /** * Seed the folder with any new facts */ function nerdyfacts_seed() { // Only seed on mondays if (date('l') !== 'Monday') return; $blog = 'nerdyfacts'; $start = 0; $perPage = 50; $posts = []; // The folder has been "pre-seeded" in order to improve performance $lastId = (int) get_option('nerdy_facts_last_id', 161179905143); do { // Grab the next posts $url = 'http://' . $blog . '.tumblr.com/api/read/?start=' . $start . '&num=' . $perPage . '&type=photo'; $file = file_get_contents($url); $feed = new SimpleXMLElement($file); // We only want new posts $newPosts = array_filter($feed->xpath('posts//post'), function ($post) use ($lastId) { return ((int) $post->attributes()->id) > $lastId; }); $posts = array_merge($posts, $newPosts); // Get ready for the next posts $start = (int) $feed->posts->attributes()->start + $perPage; } while ($start <= (int) $feed->posts["total"] && count($newPosts) > 0); // If we don't have any new posts, bail if (count($posts) == 0) return; $newFiles = []; // We have all the data now, let's download the images foreach ($posts as $post) { $post_array = (array) $post; $id = $post_array["@attributes"]["id"]; if ($lastId < $id) { $lastId = $id; } // Let's not assume it'll always be the same $ext = pathinfo($post_array["photo-url"][0], PATHINFO_EXTENSION); // Store each fact image as a file on the server $newFile = $id . '.' . $ext; copy($post_array["photo-url"][0], NERDY_FACTS_FOLDER . $newFile); $newFiles[] = $newFile; } // Update last id update_option('nerdy_facts_last_id', $lastId); // Add new files into the list $allFacts = array_merge(_nerdyfacts_get_stored(), $newFiles); // Re-shuffle shuffle($allFacts); // Update the list update_option('nerdy_facts_all', json_encode($allFacts)); } /** * Get a shuffled array of all the stores facts */ function _nerdyfacts_get_stored() { $allFacts = get_option('nerdy_facts_all', false); if ($allFacts === false) { $allFacts = []; } else { $allFacts = json_decode($data); } if (count($allFacts) == 0) { // We only want the files, ignore all folders $allFacts = array_filter(scandir(NERDY_FACTS_FOLDER), function($item) { return !is_dir(NERDY_FACTS_FOLDER . $item); }); // Shuffle the facts shuffle($allFacts); } return $allFacts; } /** * Select an image from those already seeded */ function nerdyfacts_select() { // Get the current list of facts $allFacts = _nerdyfacts_get_stored(); // Take the first one update_option('nerdy_facts_current', array_shift($allFacts)); // Update the list update_option('nerdy_facts_all', json_encode($allFacts)); } /** * Build the widget for rendering */ class NerdyFacts_Widget_Class extends WP_Widget { public function __construct() { parent::__construct(false, $name = 'Nerdy Facts'); } public function widget($args, $instance) { $fact = get_option('nerdy_facts_current', false); if ($fact === false) return; $title = apply_filters('widget_title', $instance['title']); echo $args['before_widget']; if (!empty($title)) echo $args['before_title'] . $title . $args['after_title']; echo '<img src="' . NERDY_FACTS_URL . $fact . '" style="max-width: 100%; display: block; height: auto;" />'; echo $args['after_widget']; } public function form($instance) { if (isset($instance['title'])) { $title = $instance['title']; } else { $title = 'Nerdy Facts'; } ?> <p> <label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>" /> </p> <?php } public function update($new_instance, $old_instance) { $instance = array(); $instance['title'] = !empty($new_instance['title']) ? strip_tags($new_instance['title']) : ''; return $instance; } } /** * Called when widgets are initialised, so we can setup our own */ function nerdyfacts_widget() { register_widget("NerdyFacts_Widget_Class"); } register_activation_hook(__FILE__, 'nerdyfacts_activate'); register_deactivation_hook(__FILE__, 'nerdyfacts_deactivate'); add_action('widgets_init', 'nerdyfacts_widget'); add_action('nerdyfacts_seed', 'nerdyfacts_seed'); add_action('nerdyfacts_select', 'nerdyfacts_select');
Migrating WordPress
Migrating WordPress from one host to another can seem daunting, but unless you’re changing the domain name it’s actually fairly straight forwards. For this tutorial, I will assume you are NOT changing the domain name.
The Filesystem
There are 2 important parts to any WP installation, the database and the filesystem. In particular, the wp-content directory. In order to speed things up, my advice is to install the WP software as new (forcing an update to the latest version). Feel free to copy old system files if required, but it’s mostly just a waste of time and bandwidth.
Self-Hosting WordPress – Performance
Optimising WordPress is a fairly straight forward process, and in doing so it’s possible to squeeze a serious amount of speed and power out of a cheaper server.
Go ahead and ssh in!
WP Cron
Cron is a system that runs programs (or commands) at scheduled times. WP Cron is the WordPress implementation that mimics this. Due to the necessity for WordPress to be compatible on various systems it’s not, by default, done “natively”. Instead WordPress triggers the appropriate hooks the next time the website is accessed. It’s possible to fake this with tools like Pingdom which hits your site every minute to report downtime, but we’ll go ahead and create a full cron system.
The first step is to disable WP Cron, and it’s crazy simple.
nano /var/www/html/wp-config.php
Scroll down and insert this line (near the bottom), feel free to add that other command if you are missing it.
define('FS_METHOD', 'direct'); define('DISABLE_WP_CRON', true); /* That's all, stop editing! Happy blogging. */
Now we can edit the crontab, Linux’s built in cron system
Self-Hosting WordPress – Installation
Install all the WordPress
As part of this installation guide I’ll be showing you how to install the cli, setup the database and configure wp. It’ll be a relatively long post, however by the end you’ll have a working WordPress installation. Without further ado!
The CLI
The CLI is incredibly useful, it allows us to install WordPress, take database backups and anything else you might want to do in the web interface. Installing it is simple! From a server ssh connection:
cd /tmp
I always like to work out of the temp directory when downloading and installing files, just incase I mess up or leave a file behind.
So, what we’re going to want to do is download the cli installation script.
Self-Hosting WordPress – SSL
Free SSL certificate? Don’t mind if I do.
SSL is important, even Google is using it in the ranking algorithms now but historically it could be quite costly. Now that we have full server control, albeit virtual, we can use the Let’s Encrypt client to get a certificate for ourselves. And, as always – in a ridiculously straightforward way.
At this point I’m going to make a few assumptions. In order to get an SSL certificate you must own the domain you are trying to get it for. And additionally, it must be pointed at the server you are trying to authenticate. You can do this by setting an A Record to point the domain to the IP address.
It’s possible to do this step at any stage, but I much prefer doing it before installing WordPress in order to make the process smoother.
Firstly, ssh into your server and then run the following commands.
sudo apt-get update sudo apt-get install letsencrypt
We are updating the repository list and then installing the letsencrypt client. This has actually been renamed to certbot however the version included in the Ubuntu 16.04 repositories is perfectly fine for now!
Self-Hosting WordPress – PHP
PHP-FPM?
By default, Nginx doesn’t have native PHP processing. However, getting it up an running is a cinch. We will use php-fpm, fastCGI process manager. We’re going to install this and then tell nginx to pass php processing to this module.
sudo apt-get install php-fpm php-mysql
We’re actually installing 2 packages here, php and a mysql helper package for communicating with the database.
Guess what, you just installed PHP. However, by default it is setup with an insecure setting – we can change that.
Self-Hosting WordPress – MariaDB
MySQL or MariaDB?
MySQL has become the golden standard when working with PHP, however due to concerns about the acquisition by Oracle some of the original developers have forked it and created MariaDB. It is designed to be a “drop in replacement” that follows all the same API standards as MySQL. Let’s get to it!
The first thing you’re going to want to do is ssh into your server.
ssh yourname@server_ip
Now need to know the version of Ubuntu we’re using. At the time of writing this I installed 16.04. But you can check yours by running:
lsb_release -a
Distributor ID: Ubuntu Description: Ubuntu 16.04.2 LTS Release: 16.04 Codename: xenial
MariaDB provides a nice web interface to determine the repository we need to interface.
Self-Hosting WordPress – Nginx
Nginx or Apache?
Apache has been around since 1995 and powers more websites than any other software. However, under heavy load performance hits a bottleneck and ultimately suffers. Nginx, Engine X, was designed to address some of these issues. If you’ve been on a shared host, chances are you’ve been using Apache. We’re going to setup Nginx – hey, performance matters. We might as well do it right, right?
The first thing you’re going to want to do is ssh into your server.
ssh yourname@server_ip
Nginx is part of the Ubuntu core repositories, which means we can use the apt
package manager for a sleek installation.
sudo apt-get update sudo apt-get install nginx
The first thing we’re going to want to do is update the local index before installing nginx. You’ll be asked to confirm the installation. Type Y
to begin.
Self-Hosting WordPress – Ubuntu
Motivation
Hosting a website is awesome but rarely simple. Often you get poor performance and expensive costs just for going down the “setup my hosting for me” route. Instead, I’ll show you how you can get back control and performance for nothing more than the cost of a month’s lunch.
First things first, forget those hosts who promise you everything, unlimited this, emails that. They are bloated, expensive and the shared drives are simply useless. Instead, let me introduce you to Digital Ocean.
Digital Ocean have become my goto hosting provider for 3 main reasons.
- Speed – You can have a server up and running within a minute (really)
- Performance – You can optimise the entire stack (and all their servers are SSD)
- Price – $5 (£4) a month is all it takes. What else can you buy for that?