CMS installation outside document root available for all domains

How do I configure a CMS installation like WordPress or Drupal, to run on multiple websites as a replicate of one main CMS installation. Owned and managed by different server accounts all hosted on the same server.

My setup is similar to the following:

  • Webserver software: nginx running together with apache2 (VestaCP)
  • Document Root: /home/user/example.com/public_html/

What do I mean with, “a replicate of one CMS installation”:

As described in the diagram all users owning a folder where DocumentRoot is pointed at, will be able to replicate/create an instance of/use the CMS installation.

Read More

You probably need the following files to run the CMS installation:

  • DocumentRoot/configuration.php – specify with what data the CMS should load.
  • DocumentRoot/index.php – Call and render the specified output.

Single CMS installation


WordPress

To sort out what approach will work out the best I could come up with these two different scenario’s:

  1. where there is only one symbolic linked folder, this folder includes the wordpress core files.
  2. all the wordpress core files and folders are symbolically linked.

In both scenario’s the setup is trying to require their own config file (my-config.php), so not the one what’s symbolically linked.

1: Symbolic linked wordpress folder:

wordpress/wp-config.php

<?php
require_once( ROOT_PATH . 'my-config.php');
if ( !defined('ABSPATH') )
    define('ABSPATH', dirname(__FILE__) . '/');

require_once(ABSPATH . 'wp-settings.php');

wordpress/test.php

<?php echo 'Path is '.getcwd(); ?>

DocumentRoot/index.php

<?php
define('ROOT_PATH', dirname(__FILE__) . '/');
require('wordpress/index.php');

DocumentRoot/my-config.php

Contains everything except the code inside wordpress/wp-config.php

<?php
define('WP_HOME', ... );
define( 'WP_CONTENT_URL', ... );
etc..

The symbolic link is as following

ln -s /path/to/wordpress/ .

My results

  • [Visited URL] – [Testing results]
  • ../ – Redirect to: ../wp-admin/install.php + 404
  • ../wp-admin/ – 404
  • ../wordpress/ – WSOD
  • ../wordpress/wp-admin/ – WSOD
  • ../wordpress/test.php – Output: Path is /path/to/wordpress

2: Another approach could be something like this:

wordpress/wp-config.php

<?php
require_once('my-config.php'); // So DocumentRoot/my-config.php
if ( !defined('ABSPATH') )
    define('ABSPATH', dirname(__FILE__) . '/');

require_once(ABSPATH . 'wp-settings.php');

wordpress/test.php

<?php echo 'Path is '.getcwd(); ?>

DocumentRoot/my-config.php

The same as the DocumentRoot config file used in scenario 1.

The symbolic links are as following

ln -s /path/to/wordpress/* .

My results

  • [Visited URL] – [Testing results]
  • ../ – Redirect to: ../wp-admin/install.php + WSOD
  • ../wp-admin/ – WSOD
  • ../test.php – Output: Path is /home/user/example.com/public_html

Conclusion WordPress

In both scenario’s the main index file redirects to the installation file. This normally happens when the connection to the database has been made but the database is still empty, so by following the installation instruction you will insert your first data. But then why does it give a WSOD?

The test file (test.php) clears things up a bit. In the scenario 1 the path to the wordpress directory becomes the absolute path for the wordpress core files. For the scenario 2 the DocumentRoot’s path becomes the absolute path for the wordpress core files… or not?

Although not for the sub directories in the wordpress directory, as we can learn from scenario 1. For example the wp-admin directory, files what call getcwd() should output /path/to/wordpress/wp-admin right? If so then it means, all calls to files within those directories will never find the file. Because the file /home/user/example.com/public_html/wp-admin/file.php doesn’t exists! It’s not symbolically linked and so it’s only accessible via /path/to/wordpress/wp-admin/file.php.

Related posts

3 comments

  1. It’s actually fairly easy to do this, the key of course is your index files. MVC frameworks, and I’m just tossing wordpress in to that category very loosely, all have a single entry point. Obviously the entry point for most of these is the index file.

    Really what it amounts to is that the only file requested is the index file, it’s really the only code that is called. The rest of the stuff that gets ran is routed because the index file is hard linked to code that knows how to do that.

    So all you have to do is point the index file back to the code it’s pointed at when you move it. It doesn’t matter to the webserver where any of the files are because it doesn’t route anything to them only the index. PHP can access the files as long as the system user it is using can access the files.

    It may be more difficult to have separate config files for each one, for example if you want to use a separate database. Most CMS or MVC frameworks don’t allow you to tell them where the config file is, it’s just in some arbitrary location. The hitch you will have in wordpress is that in the general settings you have the site url, and wordpress url. These unfortunately are in the database, which makes it harder to change them. Also you probably don’t want exactly the same menu for each site.

    So really using the wordpress example you will have to include the config files elsewhere by piping them into the actual config file Load it when WordPress wants it to load:. I think your example config file is on the right track, but you will have to use separate databases, because of the site url and wordpress url..

    This fact actually makes it harder to maintain not easier, because wordpress wont necessarily update the right database when doing upgrades to the core.

    The site url needs to be the actual domain.
    The wordpress url is where your admin script is accessed from. So there is that part too to sort out, wp-admin.

    Anyway good luck, and make sure to change those settings in the general tab in the WordPress admin area.

    UPDATE

    What I would suggest doing is making a really simple router out of the orignal config file, it doesn’t have to be fancy. You can tell on a per-request basis using $_SERVER[HTTP_HOST] what domain is doing the requesting. With that information you could do something as simple as this (PSUDO CODE)

     switch( $_SERVER['HTTP_HOST' ){
           case 'domain1':
                require 'location of domain1 config';
           break;
           case 'domain2':
                require 'location of domain2 config';
           break;
           default:
                require 'master site config';
     }
    

    Then inside each site’s respective folders place a config file for the site_url, database, and what have you in the normal wp-config.
    Excreta, then it’s just a matter of working out if you want to give access to the admin area to each instance of the site.

    This ( only changing the original config and not wp-load.php ) might be easier to maintain because it relatively trivial to find each config file at a later point, and you are making as little impact on the wordpress core code as possible. Granted it’s unlikly wp-load would change, but is it a natural place to look for something custom to each site.

    My concern with admin area is that you have to run it through wp-admin.php and not the index page. I am not that familiar WordPress’s architecture, in how it relates to loading the admin area to say for sure, but there may be some tweaking needed there..

  2. Finally!

    I found a way to use one set of WordPress core files to run multiple websites on without using WP Multisite or any significant changes to the core files.

    This example is based on the following structure:

    var/
    |
    ├── core/ # chown -R www-data:www-data .
    |   |     # find . -type d -exec chmod 755 {} ;
    |   |     # find . -type f -exec chmod 644 {} ;
    |   ├── wp-admin/
    |   ├── wp-content/
    |   |   ├── themes/
    |   |   ├── plugins/
    |   |   └── languages/
    |   ├── wp-includes/
    |   └── wp-config.php
    |   └── # all other wordpress core files...
    |
    ├── sites/ # chown -R www-data:www-data .
    |   |      # find . -type d -exec chmod 775 {} ;
    |   |      # find . -type f -exec chmod 664 {} ;
    |   ├── alpha/
    |   |   ├── wp-admin -> /var/core/wp-admin/
    |   |   |   # ln -s /var/core/wp-admin /var/sites/alpha/
    |   |   ├── wp-includes -> /var/core/wp-includes/
    |   |   |   # ln -s /var/core/wp-includes /var/sites/alpha/
    |   |   ├── wp-content/
    |   |   |   └── . # files only available for alpha
    |   |   ├── wp-login.php -> /var/core/wp-login.php
    |   |   |   # ln -s /var/core/wp-admin /var/sites/alpha/    
    |   |   └── index.php
    |   └── beta/
    |   |   ├── wp-admin -> /var/core/wp-admin/
    |   |   |   # ln -s /var/core/wp-admin /var/sites/beta/
    |   |   ├── wp-includes -> /var/core/wp-includes/
    |   |   |   # ln -s /var/core/wp-includes /var/sites/beta/
    |   |   ├── wp-content/
    |   |   |   └── . # files only available for beta
    |   |   ├── wp-login.php -> /var/core/wp-login.php
    |   |   |   # ln -s /var/core/wp-admin /var/sites/beta/ 
    |   |   └── index.php
    |   └── . # etc...
    .
    

    At /var/core/wp-config.php make the following changes:

    <?php
    if (defined('MULTIPRESS_ABSPATH')) {
        require_once(MULTIPRESS_ABSPATH . 'wp-config.php');
    } else {
        $instance_path = $_SERVER['SCRIPT_FILENAME'];
        if (strpos($instance_path, 'wp-admin') > -1) {
            $instance_path = dirname(dirname($instance_path)) . '/';
        } else {
            $instance_path = dirname($instance_path) . '/';
        }
        require_once($instance_path . 'wp-config.php');
    }
    

    Leave all other core files as you wish, you might want to add some plugins or themes to this core directory’s wp-content to make them globally available for all sites/instances.

    Now we can setup an instance of this our WP MultiPress core. Below you can see the /var/sites/alpha/wp-config.php of the alpha site. Commonly you only have to define the Unique Keys and Salts and your MySQL settings. Next to that you can also configure a custom name for your wp-content directory.

    <?php
    /* -------------------------------------------- *
    **  MySQL settings.                            **
    * -------------------------------------------- */
    define('DB_NAME', 'wp_multipress');
    define('DB_USER', 'manager');
    define('DB_PASSWORD', 'mylittlepony');
    define('DB_HOST', 'localhost');
    define('DB_CHARSET', 'utf8');
    define('DB_COLLATE', '');
    $table_prefix = 'alpha_';
    
    
    /* -------------------------------------------- *
    **  Authentication Unique Keys and Salts.      **
    * -------------------------------------------- */
    // Generate a set of keys:
    // https://api.wordpress.org/secret-key/1.1/salt/
    
    
    /* -------------------------------------------- *
    **  Dynamically determine the WP root URL.     **
    * -------------------------------------------- */
    $path = trim(substr((strpos(dirname(__FILE__),'wp-admin')===false)?dirname(__FILE__):strstr(dirname(__FILE__),'/wp-admin'), strlen(rtrim($_SERVER['DOCUMENT_ROOT'],'/'))), '/');
    $domain = !empty($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:$_SERVER['SERVER_NAME']; $force_domain = $domain;
    $http = 'http'.(isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!='off'?'s':'').'://'; $force_http = $http;
    
    // Uncomment the line below to force to https
    //$force_http = strpos($http,'https')===false?'https://':$http;
    
    // Uncomment the line below to force to www.
    //$force_domain = strpos($domain,'www.')===false?'www.'.$domain:$domain;
    
    // Or uncomment the line below to prevent access to www.
    //$force_domain = strpos($domain,'www.')!==false?substr($domain,strpos($domain,'www.')):$domain;
    
    $url = $force_http.$force_domain.(!empty($path)?'/'.$path:'');
    if ($force_http!==$http||$force_domain!==$domain){ header('Location: '.$url.$_SERVER['REQUEST_URI']);exit(); }
    
    
     /* -------------------------------------------- *
     **  General settings.                          **
     * -------------------------------------------- */
    define('WP_HOME', $url);
    define('WP_SITEURL', $url);
    define('WP_TEMP_DIR', sys_get_temp_dir());
    
    
    /* -------------------------------------------- *
    **  File System settings.                      **
    * -------------------------------------------- */
    define('FS_METHOD', 'direct');
    define('WP_CONTENT_DIR', dirname(__FILE__).'/wp-content');
    
    
    /* -------------------------------------------- *
    **  Debug settings.                            **
    * -------------------------------------------- */
    define('WP_DEBUG', false);
    define('WP_DEBUG_LOG', false);
    define('WP_DEBUG_DISPLAY', false);
    define('SCRIPT_DEBUG', false);
    define('SAVEQUERIES', false);
    
    
    /* -------------------------------------------- *
    **  Cache settings.                            **
    * -------------------------------------------- */
    define('COMPRESS_CSS', true);
    define('COMPRESS_SCRIPTS', true);
    define('CONCATENATE_SCRIPTS', true);
    define('ENFORCE_GZIP', true);
    
    
    /* -------------------------------------------- *
    **  WordPress Localized Language.              **
    * -------------------------------------------- */
    define('WPLANG', '');
    
    
    /* -------------------------------------------- *
    **  That's all, stop editing! Happy blogging.  **
    * -------------------------------------------- */
    if ( !defined('ABSPATH') )
        define('ABSPATH', dirname(__FILE__) . '/');
    require_once(ABSPATH . 'wp-settings.php');
    

    Next up is the index of the instance, have a look at the contents of /var/sites/alpha/index.php below.

    <?php
    /**
     * Front to the WordPress application. This file doesn't do anything, but loads
     * wp-blog-header.php which does and tells WordPress to load the theme.
     *
     * @package WordPress
     */
    
    /**
     * Let MultiPress render this website.
     *
     * @var string
     */
    define('MULTIPRESS_ABSPATH', dirname( __FILE__ ) . '/');
    
    /**
     * Tells WordPress to load the WordPress theme and output it.
     *
     * @var bool
     */
    define('WP_USE_THEMES', true);
    
    /** Loads the WordPress Environment and Template from MultiPress */
    require( '/var/core/wp-blog-header.php' );
    

    Make sure all files in /var/core/ are available for reading, for all processes running the sites. Also don’t forget to create the symbolic links (shown with -> in the structure example).

    That should do it!

Comments are closed.