Ryan McDonnell - Pursuing Web Application Zen

Multiple blogs, one WordPress install, zero code changes

Thursday, July 12th, 2007 at 6:00 am

Now I know there are plenty of patches and other projects out there to host multiple blogs from a single installation of WordPress. Many of them focus on trying to host multiple blogs that have a common element such as users, site design, or subdomains. What I wanted to do was a bit different.

  • One installation of WordPress to make future upgrades easy
  • Run multiple distinct and completely unique sites from that single installation
  • Each site is configured as its own virtual host within Apache
  • Each site has a unique design, theme, database, users, etc.
  • There should be zero changes to the code file included with WordPress.
  • Future version upgrades should be as easy as uploading the newest files.
  • Separate ‘wp-config.php’ configuration file for each blog/site.
  • Everything about each blog/site is under one directory that I can provide different users access to via FTP.

Installing Wordpress

First things first, I downloaded the latest version of WordPress, unzipped it and uploaded it via FTP to my server. I chose to to upload it to “/var/www/.wordpress” and gave it the appropriate permissions.

Open ‘wp-config.php’ (create it if it doesn’t exist) and insert the following into it:

<?php require('local/wp-config.php'); ?>

That’s it. Your single installation is now ready to serve multiple sites.

Setup a new blog

Now comes the fun part. Create the folder you’ll see running the new blog under. For this example, I’ll use ‘/var/www/example.com’. This is the folder that will serve as the root folder for the virtual host under Apache.

Under the newly created folder, create an ‘.htaccess’ file and insert the following line into it:

php_value include_path ".:/var/www/example.com/local/:/var/www/example.com/"

Now create a new folder called “local”. Within that folder, put the ‘wp-config.php’ file that you would normally use with WordPress.

<?php
define('DB_NAME', 'wp_example');
define('DB_USER', 'example');
define('DB_PASSWORD', 'password');
define('DB_HOST', 'localhost');
prefix $table_prefix  = 'wp_';
define('WPLANG', '');
define('ABSPATH', '/var/www/example.com/');
require_once(ABSPATH.'wp-settings.php');
?>

There is one key change you need to make. Edit the second to last line and define the ‘ABSPATH’ variable to be an absolute path to the root of this site.

Now, drop back into the root folder of the site. Create a file called ‘wp-config.php’ and put the following line in it:

<?php require('local/wp-config.php'); ?>

So now your file structure should look like this so far:

/www/var/example.com/.htaccess
/www/var/example.com/wp-config.php
/www/var/example.com/local/wp-config.php

We’re almost there. Now create symbolic links to all the actual WordPress files/folders except for the ‘wp-content’ folder.

index.php -> /var/www/.wordpress/index.php
wp-admin -> /var/www/.wordpress/wp-admin/
wp-atom.php -> /var/www/.wordpress/wp-atom.php
wp-blog-header.php -> /var/www/.wordpress/wp-blog-header.php
wp-comments-post.php -> /var/www/.wordpress/wp-comments-post.php
wp-commentsrss2.php -> /var/www/.wordpress/wp-commentsrss2.php
wp-cron.php -> /var/www/.wordpress/wp-cron.php
wp-feed.php -> /var/www/.wordpress/wp-feed.php
wp-includes -> /var/www/.wordpress/wp-includes/
wp-links-opml.php -> /var/www/.wordpress/wp-links-opml.php
wp-login.php -> /var/www/.wordpress/wp-login.php
wp-mail.php -> /var/www/.wordpress/wp-mail.php
wp-pass.php -> /var/www/.wordpress/wp-pass.php
wp-rdf.php -> /var/www/.wordpress/wp-rdf.php
wp-register.php -> /var/www/.wordpress/wp-register.php
wp-rss.php -> /var/www/.wordpress/wp-rss.php
wp-rss2.php -> /var/www/.wordpress/wp-rss2.php
wp-settings.php -> /var/www/.wordpress/wp-settings.php
wp-trackback.php -> /var/www/.wordpress/wp-trackback.php
xmlrpc.php -> /var/www/.wordpress/xmlrpc.php

I didn’t want to make symbolic link to the ‘wp-content’ folder because I wanted to be able to edit the themes individually for each site. To get that working, create a folder called ‘wp-content’ in your site (’/var/www/example.com/wp-content’). Under ‘wp-content’, create a ‘themes’ folder and then make the following symbolic links:

/var/www/example.com/wp-content/index.php -> /var/www/.wordpress/wp-content/index.php
/var/www/example.com/wp-content/plugins -> /var/www/.wordpress/wp-content/plugins/
/var/www/example.com/wp-content/themes/default -> /var/www/.wordpress/wp-content/themes/default

The last symbolic link will make the default theme available to you until you upload a new one.

Everything is go!

Proceed with your WordPress installation like normal (database setup, login into admin area, etc).

Plugins

The ‘plugins’ folder has a central location under ‘/var/www/.wordpress/wp-content/plugins’. Upload your plugins there and all your blog can benefit from them. Each blog can enabled to use only the plugins you want to use.

Future versions

When future WordPress versions are released, just upload them to the ‘.wordpress’ folder and you’ve updated all your blogs at once! With each new version, take a quick look at the files included incase they’ve added some new files which you’ll need to create symbolic links for.

Caveats

Unfortunately my method requires some command-like access via a terminal/SSH and this method cannot be used with most shared hosting providers. Perhaps someone can build a small script that builds the symbolic links?

Some directives in the ‘.htaccess’ may not work unless you define ‘AllowOverrides All’ in the Apache virtual host configuration.

How it works

Symbolic links can cause problems when using the include() or require() functions within PHP. The formatting of the requested file’s path can influence if the file is included from the symbolic link’s folder or the original folder that the site is running under.

My solution to that problem is contained within the ‘.htaccess’ file. The PHP engine is configured to look for include files within a path before it the current folder is checked. This allows us to override the location of the ‘wp-config.php’ file and always grab the copy located within the ‘local’ folder on each site.

If you have any questions or issues please leave a comment below!

31 Comments

  1. Glenn

    Nice approach Ryan. How do you manage de-activating plugins for an upgrade? Does renaming your master plugins directory - temporarily for the upgrade - have the same effect as disabling the plugins for each blog?

  2. Ryan McDonnell

    Thanks for the comment!

    I’ve not tested that yet as I personally don’t deactivate plugins when performing an upgrade.

    I believe you are correct in that renaming the folder would disable the plugins. I know that when a plugin is being unruly and you can’t even access the admin area, that deleting the plugin solves the problem. I’ll test your theory out during the next WordPress upgrade.

  3. Tamlyn Rhodes

    Good stuff. I was thinking of using a similar approach for a site I’m working on. Glad to see someone’s got it working with a minimum of fuss.

  4. Qrystal

    This is exactly what I was hoping would be easily done, but I decided to search around to see if anyone’s tried it (or put its setup into a script…)

    Any updates on this method? Did you upgrade to Wordpress 2.3.2?

  5. Ryan McDonnell

    I currently don’t use this method anymore so I have not tested how well it works with the more recent versions of Wordpress. I don’t see any obvious reasons it would not work though.

    If you happen to try it, please follow up and let me know how it goes. :-)

  6. Gary LaPointe

    Ryan,

    I’m trying to make sure understand why I wouldn’t just make dynamic links to everything (except the wp-config file)?

    The local folder part is confusing, but I think I need to understand it before I commit to this :)

    Is the htaccess mod only for the include() / require() functions or does it help other things too? (I think it’s just for the functions).

    Lastly, why aren’t you using this method anymore? what are you using?

    Thanks
    Gary

  7. Ryan McDonnell

    @Gary:

    What do you mean by dynamic links?

    The local folder part is a way to work around how PHP interprets the path when using the “include” and “required” directives. It also serves to create a folder to be the first target of the “incudes” path. There may be a more elegant way to achieve this solution but the above worked well for my needs.

    I’m not using this method any more simply because I don’t have multiple WordPress blogs running on the same server anymore. If I did, I think I’d still use this method.

  8. Gary LaPointe

    Sorry, I meant symbolic links (I don’t know where that came from).

    I think I’m just going to have to trust you on the local folder part.

    Thanks very much for the info. A few other people have done similar posts, but not as straightforward (they completely muck with the .htaccess file, where the symbolic links seem better).

    I think I’ll be trying this soon (maybe today)

  9. Ryan McDonnell

    When the WordPress code requests to include the “wp-config.php” file, PHP would look for “wp-config.php” relative to the local path. Since the WordPress code is in a common location (/var/www/.wordpress) it won’t be able to find the “wp-config.php” that pertains to the current blog. Instead, I’ve mapped the “wp-config.php” to a folder called “local” and included that path in the “includes” search path. Now when the WordPress code requests to include the “local/wp-config.php” file, PHP searches in the “includes” path and is able to locate the correct “wp-config.php” file for the active blog.

    Hopefully that makes more sense.

  10. Gary LaPointe

    Ahh… I’m understanding the local folder part better. When I was reading it I didn’t realize there were TWO wp-config.php files, as I was following the steps and saw the second it made a lot more sense (and the “php_value include_path” looks useful for future projects). I’d say I understand it now, except…

    I’m really close but it’s still not working. (I think it’s the “php_value include_path” stuff, or possibly permissions)

    My test site is /mypath/_sites/gaea.com
    My wordpress files are at /mypath/_sites/_multipress/wordpress-current
    my .htaccess line says
    php_value include_path “.:/mypath/_sites/gaea.com/local/:/mypath/_sites/gaea.com/”

    Unfortunately, I get a “500 Internal Server Error”. But I think I’m really close.

    I can get around it by hardcoding the full path in both wp-config.php files and remove the line from the .htaccess then it lets me get the “name my blog” config page. But obviously this isn’t going to work for more than one site. (Even hard coded it won’t work until I remove the .htaccess file)

    If I remove the .htaccess line and have the config.php lines they way you say I get
    Warning: main(local/wp-config.php) [function.main]: failed to open stream: No such file or directory in /home/garyforp/public_html/_sites/_multipress/wordpress-current/wp-config.php on line 1
    Warning: main(local/wp-config.php) [function.main]: failed to open stream: No such file or directory in /home/garyforp/public_html/_sites/_multipress/wordpress-current/wp-config.php on line 1
    Fatal error: main() [function.require]: Failed opening required ‘local/wp-config.php’ (include_path=’.:/usr/lib/php:/usr/local/lib/php’) in /mypath/_sites/_multipress/wordpress-current/wp-config.php on line 1

    otherwise I get the 500 error. So it appears the .htaccess line is doing something…

    Any thoughts? I’d love for this to work and I think I’m very close…

    Gary

  11. Ryan McDonnell

    I think your problem lies with the directive in the .htaccess file. If you are in shared hosting environment, you may not have permission to use “php_value”. Your host may provide the ability to edit a unique php.ini file for your site, and you can change the PHP include_path variable in there.

    If it’s not a shared hosting environment, check the Apache configuration for the site. See the “Caveats” subheading in the article regarding “AllowOverrides All”.

  12. Gary LaPointe

    I figured if you didn’t spot something specific, it’d be something along those lines. I will check in and let you know what I find…

    Thank you SO much for your time Ryan.
    Gary

  13. Gary LaPointe

    Unfortunately, it appears Site5 won’t let me put it in the .htaccess

    I’m running php4 (by default), so I’m trying some local PHP.INI files. It seems like the correct spot to put them is in the site directory and the wordpress directory which both have the wp-config files with the “local” path (it’s possible I need them in every subdirectory) but I can’t even get them to work with static paths (unless I need every subdirectory). I’ll play with this a bit longer.

    I can’t change the php.ini for my server either. But unless I can just add “local” to the search path, a specific path wounldn’t work for me anyway, right? (can I add just local)

    The same goes for php5 which lets me have a master local php.ini file but since I need a specific path for each fake WP install, I’m still out of luck.

    If I give up at Site5, I’ll try another server I have access to.

    If that doesn’t work, I’ll try the way some others were using one install (for multiple sites) by parsing the domain name (in htaccess?) and modifying some other code (mostly in WP config I think). I wasn’t too keen on that method. The way you were doing it was the way I thought would be best.

  14. Ryan McDonnell

    I figured. It’s common for shared hosts to not provide such permissions.

    From my experience, the include path was required to be able to dynamically “include” the proper wp-config.php file for each site.

    My goal in the above method was to completely avoid editing any WordPress code. If you are willing to forego that requirement, I suggest you look into changing the PHP include path using the ini_set method in PHP. You may be able to change that setting via PHP code within the wp-settings.php file or another section of code that is common to every page.

  15. Simon Thompson

    Thanks for the writeup on this, it’s been super helpful. I’ve just managed to get this running in the last couple of days, and I’ve found that I didn’t need the ‘local’ folder. I left the wp-config.php file in the directory of the domain, then told the wp-config in .wordpress/ to , it finds it fine.

    One thing that took me a while to figure out though is that if you’re in an environment with a number of vhosts (we’re with MT, so I have root access to the server, which is handy), you’ll need to add/edit the vhost.conf file for each domain to add

    php_admin_value open_basedir "/var/www/path/to/your/httpdocs:/tmp:/var/www/.wordpress"


    Then restart apache.

    I’m not sure if every server would need the /tmp dir, but MT does. Also, after I created the vhost.conf file, I had to register it with plesk:
    /usr/local/psa/admin/sbin/websrvmng --reconfigure-vhost --vhost-name=domain.com, then restart apache.

    Hopefully this also helps somebody else.

  16. Simon Thompson

    Hmm, previous comment missed a code block. The third sentence should read: I left the wp-config.php file in the directory of the domain, then told the wp-config in ./wordpress to
    require('./wp-config.php');
    And it finds the localised version fine

  17. Ryan McDonnell

    Glad to hear this worked for you Simon. Thanks for the follow-up.

  18. Simon Thompson

    A couple of other things I’ve found:
    As mentioned above, I don’t need the ‘local’ directory at all. Also, I don’t have to edit the wp-config.php file in the domain. Leaving it in the domain directory allows you to use:
    define('ABSPATH', dirname(__FILE__).'/');
    just fine. My copy of wp-config.php in the /.wordpress directory is able to find the domain version with its require call.

    @Gary: do you have FollowSymLinks turned on for your server? Try adding
    Options +FollowSymLinks
    to the start of your .htaccess file. It may be that your server has it turned off by default. I noticed some other people complaining of http 500 errors on other message boards when it was off.

  19. Ryan McDonnell

    @Simon: Which version of Apache and PHP are you using? Some of the differences in our solutions may be due to version differences. Good point about FollowSymLinks.

  20. Simon Thompson

    Hmm, good point. I’m on Apache 2.0.52 and php4.

    That’s something I’ll have to watch for when we switch to MT’s new server with php5 on it

  21. Simon Thompson

    Actually, scratch everything I said previously about not needing the ‘local’ folder :)

    If you try and get to wp-admin without it, it gets all confused.

  22. Ryan McDonnell

    Haha. :-)

    it’s been a while since I implemented this but that did jog my memory of running into issues with wp-admin and developing the need for the “local” folder and the PHP include path.

  23. Leonardo Parada

    Hello: Congratulations for your solution.
    I am testing some solutions for similar problem:
    I want have 5 blogs, with diferent themes.
    one data base
    one worpdress instalation
    one panel admistration.
    I don’t want use wordpressMU because I think that is very big for 5 blogs.
    This is other site with solutions, about that.
    http://striderweb.com/nerdaphernalia/features/virtual-multiblog/
    but I can’t found the right answer.
    …Will search more.
    Greetings.
    Leonardo Parada
    http://www.leonardoparada.cl

  24. […] Multiple blogs, one WordPress install, zero code changes Digg it […]

  25. Daniel Hernandez

    Really nice solution. If I only would have knew it earlier. Will try it with WP 2.5

  26. Optonotes

    Thanks for this solution, but I had better luck with this one. Very straightforward and plastic. It doesn’t require you do any crazy stuff with symbolic links, or a .htaccess — no need for terminal/SSH.

  27. Ryan McDonnell

    Thanks for the link Optonotes. That is a much simpler options but would combine all the assets for all the blogs into a single directory. The traffic logs would all be contained in the same log file which would make any server-side analytics useless.

  28. Martijn

    Just a quick note about the solution Optonotes added (mine).You *do* have the themes and plugins combined in one folder, but often that doesn’t hurt. As for the assets you can set ‘Store uploads in this folder’ to different paths for each blog. For analytics I would rather use Mint or Google Analytics than server side logs anyway.

  29. Ryan McDonnell

    Thanks for stopping by Martijn. Your solution is perfect for the user on a shared hosting environment or lacking SSH/terminal access.

  30. Ryan S.

    Ryan,

    I tried your solution and it worked for a little bit. Then my host changed my server obviously changing my php settings. You mentioned that you are no longer using this method. Are you implementing another method that is working for you and if so what is it. Can you link to it?

  31. Ryan McDonnell

    The problem this solves just no longer exists for me. I was using the solution to manage several blogs on the same server but since then all the sites have migrated to different platforms.

Leave a Comment

The following tags are allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

About Ryan McDonnell

I am a web application developer living in southern California. Commonly called a “jack of all trades” by collegues, I constantly strive to broaden my knowledge into other fields.
More about Ryan McDonnell »


 Subscribe in a reader

Quick Links & Notes

© 2004-2008 Ryan McDonnell. View my profile on LinkedIn
Some rights reserved under the Creative Commons Attribution-Share Alike 3.0 United States License.