Categories
Linode SpinupWP

Be your own host

This month I offered to demo SpinupWP and Linode for our local MSPWP WordPress meetup. The slides from that presentation are available here, as are the other links I shared. My apologies for the awkward minutes when the first part of the demo failed!

As emphasized in the presentation, using SpinupWP to manage a cloud server like those at Linode is a great solution if you are looking for control of your WordPress infrastructure. But it does demand more attention from you than shared hosting or managed WordPress hosting.

Categories
SpinupWP WordPress

Bad Link

One of our clients accidentally used a preview link when publicizing a post in a mass email. Of course, the readers of this email could not resolve a link to a preview of a post on the client’s WordPress site. Since this link went out by email, there was no opportunity to correct the mistake on that end. We wanted to create a redirect from the preview link to the actual page using Nginx.

We created a file called custom-redirects.conf in their site’s server directory. In this case that was at /etc/nginx/sites-available/ediblecleveland.com/server/custom-redirects.conf. In that file we added:

if ( $query_string = "preview_id=15781&preview_nonce=4538cdad70&_thumbnail_id=15792&preview=true" ) {
	return 301 /archives/15781;
}

This simply looks for the very specific query string that was accidentally used in the email and, if found, returns the plain post instead.

After making this change we used sudo nginx -s reload to reload the configuration with syntax checking, so we didn’t accidentally bring Nginx down with a syntax error.

Categories
Care and Feeding SpinupWP

Upgrades

SpinupWP will let you know that non-security updates are waiting on the underlying server, but it won’t install them. We use “longview” at Linode to look at recent performance, and longview includes a list of waiting upgrades so you can see what is in store.

To install them we ssh to our sudo-capable account and issue the following commands:

$ sudo apt update
$ sudo apt upgrade

The update makes sure all Linux packages needing an update are accounted for. The upgrade actually installed the updated packages.

We see “cryptsetup” warnings regularly, but have not been worrying about them.

If we are asked about keeping the php.ini changes, we choose to “keep the local version currently installed”.

We try to check for these updates weekly.

Categories
SpinupWP WordPress

Domain mapping with a WordPress network

A few years ago when I wanted to use domain mapping for domains in a WordPress network, it was a bit of a chore. At that time I had to use a special domain mapping plugin and another tool called “sunrise.” This week, when setting up domain mapping with a network generated by SpinupWP we discovered things had gotten a lot simpler.

Since WordPress 4.5 domain mapping has become a native feature. All we had to do was use SpinupWP to define additional domains for our network site, create a subsite for that domain on WordPress, and then go to Sites and edit that site to put the domain name into the Site Address (URL).

That worked! But then we decided we also wanted upload URLs that were a bit more friendly than the site numbers that WordPress automatically uses. A typical piece of media on a site mapped subsite would get a URL like https://subsite.com/wp-content/uploads/site/3/2020/2/media.jpg. That works, but I find the site/3 a bit too mysterious and revealing at the same time. It is mysterious in that it really means nothing to a person reading the URL. It is revealing in that it is trivial to guess that there might be a site/4 or a site/5.

We wondered if we could not use something like sub instead, so that the URL would become https://subsite.com/wp-content/uploads/sub/2020/2/media.jpg. This would take two things, (1) a way to define the string to use for a given subsite, and (2) a way to tell WordPress to use that string instead of the blog ID number in the upload URLs.

We decided to keep defining the string as an experts-only affair. Basically, if this string was present then we wanted to assume an expert had decided the substitution should take place. Since only experts were going to do this, we left it to a WPCLI command:

$ wp --url=https://subsite.com option add tg_upload_dir sub

In other words, we created an option to hold the string. If that option exists, we would make the substitution.

To carry out the substitution we create a one-function upload directory modifier plugin to look for that option and use if when found. The heart of this plugin is the following function:

add_filter( 'upload_dir', 'tg_upload_dir_filter' );

function tg_upload_dir_filter( $dirs ) {
	global $wpdb;
	
	$directory = get_option('tg_upload_dir');
	
	if (! $directory) {
		return $dirs;
	}
		
	$dirs['baseurl'] = site_url() . '/wp-content/uploads/' . $directory;
	$dirs['basedir'] = ABSPATH . 'wp-content/uploads/' . $directory;	
	$dirs['path'] = $dirs['basedir'] . $dirs['subdir'];
	$dirs['url']  = $dirs['baseurl'] . $dirs['subdir'];
	
	return $dirs;
}

While this gave us the URLs we wanted, we still had to take one more step to get these URLs to work: we had to create a symlink from the directory WordPress had created to the friendlier name we wanted to use. To accomplish this we used SSH to connect to the server behind our SpinupWP site and then did the following:

$ cd files/wp-content/uploads
$ ln -s sites/3 sub

The beauty of this approach is that both the sites/3 URL and the sub URL will work, so we don’t have to search and replace existing media URLs unless we want to.

Categories
Hardening SpinupWP

Battling the bots

We found our clients getting attacked by bots trying to login. This was tedious, so we looked for ways to discourage these bots.

Our first attempt was to limit their access to our usernames. To accomplish this we added the following to our /server/tenseg.conf file in each available site (we should figure out how to not use the explicit URL):

if ($arg_author) {
  return 301 $scheme://ggp.tenseg.net;
}

This didn’t stop them from trying, though. So we also added the following to this main site’s /before/tenseg-limits.conf file. Note, we can only add this once, so we do not add it to any of the other available servers.

limit_req_zone $binary_remote_addr zone=WPLIMIT:10m rate=15r/m;
limit_req_status 429;

This creates an Nginx rate limit named WPLIMIT that allows sites to only access something 15 times per minute and uses 10MB of space holding the addresses of those making attempts. Once this limit is created we can add the following to each available server’s /server/tenseg.conf file:

location = /wp-login.php {
    limit_req zone=WPLIMIT burst=5 nodelay;
    include fastcgi.conf;
    fastcgi_pass unix:/run/php/php7.3-ggp.sock;
}

location = /xmlrpc.php {
    limit_req zone=WPLIMIT burst=5 nodelay;
    include fastcgi.conf;
    fastcgi_pass unix:/run/php/php7.3-ggp.sock;
}

Note the inclusion of the FastCGI stuff in these entries. Without this, the php file does not function as a PHP file at all. We learn the values to use in each site by checking the values used in that site’s sites-available configuration file.

The burst=5 nodelay on these limit_req settings ensures that the normal human login experience is still satisfactory. We have found that if a person is using a password manager they could well exceed the one-request-per-four-seconds (15r/m) rate just by having the password manager enter their credentials and one-time token. Allowing an undelayed burst makes regular logins flow smoothly.

After making these changes, the best way to restart Nginx is to use sudo nginx -s reload because this will report syntax errors and not actually restart the server if the configuration files are flawed.

In addition to this, we also learned to use ufw (the “uncomplicated firewall” that SpinupWP has included) to block certain bad actors.

sudo ufw insert 1 deny from 23.100.85.179 to any

This should block a site at a level below Nginx. We include insert 1 to make this the first rule, it has to come before we allow in web traffic. You can view all the settings with sudo ufw status numbered.

We’ll see how this goes.