WordPress.org

Ready to get started?Download WordPress

Codex

Attention Interested in functions, hooks, classes, or methods? Check out the new WordPress Code Reference!

Running a Development Copy of WordPress

Whether you're developing WordPress plugins, WordPress themes, or rolling out a new site based on a customized version of WordPress, it's often helpful to be able to mirror your live site on your local system for development. This is common for commercial sites, where you have a live production server but you also need a development and/or staging server, which is run locally to test plugins, mods, themes, and everything else that you don't want to do on the live production server. For example, your live site could be http://my.web.zz/wordpress and your development site might be http://my.web.zz/dev. However, WordPress serves pages with embedded absolute URLs based on the absolute site URL configured in your database, so none of the links on your development site will work.

WordPress knows "what is this site's current URL?" because of two values in the wp_options database, specifically siteurl and home (WP base URL, and website homepage; if your whole site is WP they can be the same; these are normally set in your options panel).

Creating a Second Installation With Separate Tables

One approach is to make a copy of both the WP file system and the databases on your development site. You'll have to ensure that both wp-config.php files are properly configured (different DB servers and/or tables and/or table prefixes). In addition, you'll have to change the database itself in two entries. If your development site and development homepage are both "http://my.web.zz/dev", then the DB changes you'll have to do are:

SELECT * FROM wp_options WHERE option_name = "home" OR option_name = "siteurl";
UPDATE wp_options SET option_value = "http://my.web.zz/dev" WHERE option_name = "home" OR option_name = "siteurl"

You will have to redo these modifications each time you copy the database from one place to another.

There's also a simple hack that doesn't require you to make a separate database, which can avoid a lot of synchronization hassle. (This is only a good idea provided you don't expect the development site to wreck the main one's DB.)

Two WordPresses, One Database

The crux of what we want is that instead of the true DB values, the development site thinks it gets a modified value for siteurl/home.

Luckily, all calls to get this information are funneled through calls to get_option(), so we can focus on that.

By Changing Core Code

WARNING: It is highly not recommended to change core code because WordPress updates will remove your changes.

If you are okay with hacking your core code, this means adding a little logic at the start of get_option() in wp-includes/option.php is enough. The sample logic below assumes our live site is http://my.web.zz/wordpress/ and our dev site is http://my.web.zz/dev/.

// intercept these options
if ($option == "siteurl" || $option == "home") {
   // some sample logic to determine if we're on the dev site
   if (strcasecmp($_SERVER['REQUEST_URI'], '/dev') == 0
      || strcasecmp(substr($_SERVER['REQUEST_URI'], 0, 5), '/dev/') == 0) {
      return "http://my.web.zz/dev";
   }
}
// otherwise act as normal; will pull main site info from db

Using a Drop-In

What if we don't want to hack core code? (Which is a good practice for easy upgrading and sharing code.) There is even a filter for this (pre_option_siteurl and pre_option_home) but there's a problem: within wp-settings.php,

  • the filter can't be defined until after line 65 when functions.php is included
  • WordPress makes calls to get_option on line 155 of (via wp_plugin_directory_constants())
  • plugins aren't defined until later down around line 194.

However, in between lines 65 and 155, there is something we can use, namely the loading of the drop-in db.php; the filter can be safely defined there. (However, this is perhaps only halfway towards "not core" code.)

Check if you already have an existing wp-content/db.php before trying this technique. It is used by packages like W3 Total Cache for similar reasons.

<?php
// paste this in a (new) file, wp-content/db.php
add_filter ( 'pre_option_home', 'test_localhosts' );
add_filter ( 'pre_option_siteurl', 'test_localhosts' );
function test_localhosts( ) {
  if (... same logic as before to see if on dev site ...) {
     return "http://my.web.zz/dev";
  }
  else return false; // act as normal; will pull main site info from db
}

Old examples (WordPress 2)

You need to add code to your wp-config.php that assigns the right values of WP_HOME and WP_SITEURL to reflect the current location of WordPress, whether it's the live site or a local staging server.

To do this, simply add one of the following code sections to your wp-config.php above the last require_once statement.

Either this function:

function WP_LOCATION () {
    $script_path = realpath(dirname($_SERVER['SCRIPT_FILENAME']));
    $wp_base_path = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..');
    $web_subfolder = substr( $script_path, strlen($wp_base_path)); 
    $wp_path = $web_subfolder ? substr( dirname($_SERVER['SCRIPT_NAME']), 0, -strlen($web_subfolder) ) : dirname($_SERVER['SCRIPT_NAME']) ;
    $retval = 'http' . ($_SERVER['HTTPS'] ? 's' : null) . '://' . $_SERVER['HTTP_HOST'] . $wp_path ;
    return $retval;
}
$wpLocation = WP_LOCATION();
define('WP_HOME',$wpLocation);
define('WP_SITEURL',$wpLocation);
define('WP_CONTENT_URL',$wpLocation."/wp-content");

Or alternately, you can add the following code, replacing LIVEURL and STAGINGURL with the locations for your live and staging servers:

$currenthost = $_SERVER['HTTP_HOST'];
$mypos = strpos($currenthost, 'localhost');
if ($mypos === false) {
define('WP_HOME','LIVEURL');
define('WP_SITEURL','LIVEURL');
} else {
define('WP_HOME','STAGINGURL');
define('WP_SITEURL','STAGINGURL');
}

WordPress 2.3.1 - WordPress 2.5

Edit the file wp-includes/options.php

In the function get_option, add this hack immediately after the line which says global $wpdb. It should look like the following:

function get_option( $setting ) {

	global $wpdb;

	/* Siteurl hack */

	if( 'siteurl' == $setting or 'home' == $setting )

	{

		$_REAL_SCRIPT_DIR = realpath(dirname($_SERVER['SCRIPT_FILENAME'])); 
		// filesystem path of this page's directory (index.php or whatever)

		$_REAL_BASE_DIR = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'); 
		// filesystem path of this file's parent directory 
		// (that wp-includes is within)

		$_MY_PATH_PART = substr( $_REAL_SCRIPT_DIR, strlen($_REAL_BASE_DIR)); 
		// just the subfolder part between <installation_path> and the page

		$INSTALLATION_PATH = $_MY_PATH_PART
			? substr( dirname($_SERVER['SCRIPT_NAME']), 0, -strlen($_MY_PATH_PART) )
			: dirname($_SERVER['SCRIPT_NAME'])
			; 
		// we subtract the subfolder part from the end of <installation_path>, 
		// leaving us with just <installation_path> :)

		$value = 'http' . ($_SERVER['HTTPS'] ? 's' : null) .
			'://' . $_SERVER['HTTP_HOST'] . $INSTALLATION_PATH
		;

		return $value;

	}
	/* end Siteurl hack */
		

	// Allow plugins to short-circuit options.
	$pre = apply_filters( 'pre_option_' . $setting, false );
	if ( false !== $pre )
		return $pre;

// ... rest of the function continues (do not copy this line!)

WordPress 2.0 - WordPress 2.3.1

The hack goes in wp-includes/functions.php

/* Options functions */

function get_settings($setting) {
  global $wpdb;

  $value = wp_cache_get($setting, 'options');
</tt>

<div style="background-color: #f9f9f9;"><tt>
    /* Siteurl hack */
    if( 'siteurl' == $setting or 'home' == $setting ) {

    $_REAL_SCRIPT_DIR = realpath(dirname($_SERVER['SCRIPT_FILENAME'])); 
    // filesystem path of this page's directory (index.php or whatever)

    $_REAL_BASE_DIR = realpath(dirname(__FILE__) . 
      DIRECTORY_SEPARATOR . '..'); 
    // filesystem path of this file's parent directory 
    // (that wp-includes is within)

    $_MY_PATH_PART = substr( $_REAL_SCRIPT_DIR, strlen($_REAL_BASE_DIR)); 
    // just the subfolder part between <installation_path> and the page

    $INSTALLATION_PATH = $_MY_PATH_PART
      ? substr( dirname($_SERVER['SCRIPT_NAME']), 0, -strlen($_MY_PATH_PART) )
      : dirname($_SERVER['SCRIPT_NAME'])
    ; 
    // we subtract the subfolder part from the end of <installation_path>, 
    // leaving us with just <installation_path> :)

    $value = 'http' . ($_SERVER['HTTPS'] ? 's' : null) .
             '://' . $_SERVER['HTTP_HOST'] . $INSTALLATION_PATH
    ;
  }
  /* end siteurl hack */

  if ( false === $value ) {
    if ( defined('WP_INSTALLING') )
      $wpdb->hide_errors();

With this hack in place, you can now use exactly the same database and exactly the same files for both your local and remote WordPress install. Possibly the only thing you'd need to do is put different settings in wp-config.php if you want to use a remote database.

Note: this hack is untested if your server runs PHP in Safe Mode. If you have any problems, please post them on the Discussion page.


This page is marked as incomplete. You can help Codex by expanding it.