Menu

WordPress Hacks: Serving Multiple Domains

Situation: using WordPress MU (possibly including BuddyPress) on multiple domains or sub-domains of a large organization with lots of users.

WordPress MU is a solid CMS to support a large organization. Each individual blog has its own place in the organization’s URL scheme (www.site.org/blogname), and each blog can have its own administrators and other users. Groups of blogs in WPMU make up a “Site” and one or more Sites can be hosted with a single implementation. (I’m capitalizing Site for the same reason WordPress docs capitalize Page) Each Site has a defined set of administrators and options controlling various features. You might, for instance, lock down the plugins on your blogs.site.org, while keeping it open on your www.site.org. Or maybe you’d like to let your helpdesk staff create new blogs at blogs.site.org, but not at www.site.org. That’s what WPMU’s notion of Site can help you control.

Challenge: setting up service on multiple (sub-) domains

WordPress MU makes it easy to host both blogs.site.org and www.site.org within a single implementation, but there’s little documentation for how to do it.

Once you get WPMU up and running on one of your domains you can add another. The following assumes that you’re using the vhost=no setting (correct: I really mean vhost=no), that you have access to and know how to manipulate your MySQL, that you have control over your DNS and know how to use it, and that you know how to configure Apache or similar. You’d also be smart to turn off any object caching you may have running, at least until we’re done doing direct database manipulation.

Some might ask why I’m not simply using Donncha’s plugin, and the answer is simple: it only works for vhost=yes sites. For my own use, I find that sub-directories are easier for users to make sense of (go ahead, try to tell your mom to go to “myblog.sub.domain.org“).

Set up your web server

If you’re using Apache, you’ll either need to create an IP-based virtual host or manually configure your name-based virtual host for every (sub-)domain you plan to serve. Why: WordPress will handle the domain mapping for you, so it’s better to keep Apache out of the way and let WPMU own the entire IP.

Set up your DNS

Point each subdomain you plan to host in WordPress to your webserver. You can use a wildcard domain, but you don’t have to.

Create a new blog in WPMU

It doesn’t matter what you call it or what the path is, just create one. Now go edit it in the Site Admin:

edit blog 1

Change the domain and path to match your new domain.

One quirk of WPMU is that it strips “www” from any domain name you enter (or is requested), so don’t bother trying to enter it (unless you’re willing to do some hacking to make it work). WPMU stores domain and path information in three locations: the wp_#_options table for the blog, the wp_blogs table, and the wp_sites table. When you edit a blog in the Site Admin, you’ll get a chance to edit the domain and path for both the wp_#_options and the wp_blogs tables. Clicking the helpful checkbox above will do most of that for you, but you’ll need to manually update the Upload Path.

Now make sure the new domain is shown in the blog's options as well.

Now make sure the new domain is shown in the blog's options as well.

You might be able to load the blog at the new URL as soon as you update those settings, but recent versions of WPMU set some constants in wp-config.php that can get in your way.

Reconfigure wp-config.php

Your wp-config.php might have something like this:

$base = '/';
define('DOMAIN_CURRENT_SITE', 'sub.site.org' );
define('PATH_CURRENT_SITE', '/' );
define('SITE_ID_CURRENT_SITE', 1);
define('BLOGID_CURRENT_SITE', '1' );

Those constants override the database checking that goes on in wpmu-settings.php to map the requested domain to a site. You have three choices: leave it as it is (and use only one “Site”), remove it and have WPMU do the mapping against the database, or expand the hard-coded mapping to include other sites.

I’ve used code like the following to do just that:

if( preg_match( '/(.+?\.)?([^\.]+?)\.site.org/i', 'www'. $_SERVER['SERVER_NAME'] , $matchedsubdomains ))
{
	switch( array_pop( $matchedsubdomains )){
		case 'connect':
			define('DOMAIN_CURRENT_SITE', 'blogs.site.org' );
			define('PATH_CURRENT_SITE', '/' );
			define('BLOGID_CURRENT_SITE', '1' );
			break;
		case 'www':
		default:
			define('DOMAIN_CURRENT_SITE', 'site.org' );
			define('PATH_CURRENT_SITE', '/' );
			define('BLOGID_CURRENT_SITE', '2' );
			break;
}
}
else
{
	define('DOMAIN_CURRENT_SITE', 'site.org' );
	define('PATH_CURRENT_SITE', '/' );
	define('BLOGID_CURRENT_SITE', '2' );
}

Set up your new Site

Once you have your new sub-domain working with one blog, you can create your new Site. Even if you don’t plan to create separate management policies for the different sites, it’s easier to create new blogs at each sub-domain if they each have their own Site.

Go in to your MySQL tool of choice and browse the wp_site table. There you’ll see just one row, but if you’ve made it this far you can also probably figure out how to create a new row representing the site at the new sub-domain. And once you do that, you can change the entry in the wp_blogs table to associate it with your new Site.

Set up the admins of the new site

Creating the new entry in wp_site doesn’t set the options for the new Site, and that means there are no Site administrators yet. Once again in your MySQL tool of choice, open up the wp_sitemeta table and look for an entry with meta_key = 'site_admins'. The meta_value for that entry is a serialized array containing WordPress usernames of the people who have site-wide administration privileges on the first Site. I’m assuming that if you have MySQL access you’re also a Site admin, so the easy thing to do is copy that row and change the site_id to match the auto-increment value from the new wp_site entry you made in the last step.

With the database manipulation done, you should now be able to go to the WP dashboard at your new Site, visit the WPMU admin options screen, and set the other options as necessary. You could decide to make one of your Sites open registration (remember, however, that users are shared across all Sites), while making other Sites more closed. And, obviously, you can delegate different Site admins for each Site.

Sub-domains or just different domains?

It’s worth noting that the instructions so far apply to both sub-domains and domains. You can use a single implementation of WPMU to manage content at both lolzors.org and tehsite.org. The detail about sub-domains really only applies to the next part. It’s also worth noting that you can support an arbitrary number of blogs, sites, and domains; I’m just using two sub-domains as an example.

Challenge: unified log in cookies

What’s the point of hosting multiple sub-domains with one WPMU implementation if you’ll need to log in separately at each one?

Setting a cookie path that that’s broad enough to cover the entire domain will solve this:

define('COOKIE_DOMAIN', 'site.org');
define('ADMIN_COOKIE_PATH', '/');
define('COOKIEPATH', '/');
define('SITECOOKIEPATH', '/');

Avoid conflicts with other WordPress installations at your domain

But having such a broad cookie domain can interfere with other WordPress implementations. You’ll have to solve that by setting a unique cookiehash:

define( 'COOKIEHASH', 'asdf_arbitrary_string' );

You’d do better to keep it shorter than that, though.

Challenge: unified log in location/URL

WordPress MU is happy to handle authentication requests wherever it hosts a blog, but some organizations prefer to funnel all authentication requests through a single location. The idea is to provide some protection against fishing (assuming users can ever be taught to look at URLs) and make it easer to integrate external applications.

Filter login_url and logout_url

Set the log in and log out path to whatever you want, just make sure the destination knows how to create (or destroy) the WordPress cookies.

$hack_base_domain = 'site.org';

function hack_login_url( $path ){
	global $hack_base_domain;

	return preg_replace( '/^.+?\/wp-login.php/' , 'https://login.'. $hack_base_domain .'/wp-login.php', $path );
}
add_filter( 'login_url' , 'hack_login_url' , 10 );
add_filter( 'logout_url' , 'hack_login_url' , 10 );

Filter allowed_redirect_hosts

WP will normally block redirects outside the web root of the active blog, so you’ll need to tell it about your other sub-domains.

function hack_allowed_redirect_hosts( $allowed_domains ){
	global $hack_base_domain;

	$allowed_domains[] = $hack_base_domain;
	$allowed_domains[] = 'www.'. $hack_base_domain;
	$allowed_domains[] = 'blogs.'. $hack_base_domain;

	return $allowed_domains;
}
add_filter( 'allowed_redirect_hosts' , 'hack_allowed_redirect_hosts' , 10 );