How and Why Install WordPress Core in a Subdirectory

WordPress Core Files in a Subdirectory

We all love WordPress’ famous 5 minute install, but personally I really don’t like looking at all the WordPress files in the root of the site. It’s messy and looks unprofessional. What other sites would have a dependency located right in the root?

Luckily, WordPress allows us to customize the structure of our install directory in a number of ways, and in doing so we benefit from a certain level of security through obscurity. In this article, I’ll be showing you some methods for restructuring, as well as some pitfalls to avoid.

WordPress Core

WordPress itself has a great codex article describing how to set up a new install with the core files inside a subdirectory, but I thought I would do something a little different using WP-CLI.

The below gist is a small script which leverages WP-CLI to create a new WordPress site installation with the core files in a subdirectory. The script could be improved on for future re-use, but as an example it shows the basics of what is needed for the core files to run in a subdirectory:

  1. Download the WordPress install to the subdirectory
  2. Copy the index.php file to to the root
  3. Amend the path to wp-blog-header.php in index.php
  4. Update the ‘siteurl’ in the wp_options table to include the subdirectory path

Another great way to install WordPress like this is to use Composer. Gilbert has been writing a great series about using Composer with WordPress and will be covering this approach later, but I thought it was worth the mention for this scenario.

WordPress can be installed via your composer.json as a dependency and we can execute some post install commands to set up the site in a pre WordPress installation state:

{
    “name”: “polevaultweb/wp-composer-core-sub-dir”,
    “description”: “Installing WordPress in a subdirectory with Composer”,
    “require”: {
        “php”: “>=5.4”,
        “johnpbloch/wordpress-core-installer”: “0.2.0”,
        “johnpbloch/wordpress”: “~4.2”
    },
    “extra”: {
        “wordpress-install-dir”: “wp”
    },
    “scripts”: {
        “post-install-cmd”: [
            “cp wp/index.php ./index.php”,
            “sed -i ‘’ \”s/\\/wp-blog-header/\\/wp\\/wp-blog-header/g\” index.php”
        ]
    }
}

Once the install has been completed you will be viewing the site with the wp path included. To remove that you will need to edit the ‘home’ option of the wp_options table…

wp option update home http://wpcomposer.dev

wp-config.php

When I started to organize my WordPress installs in this way I didn’t know that the wp-config.php file could actually be placed a level above the core files without further config, so your structure would be like:

WordPress directory structure

I find it easier having the config file in the root, than having to navigate into the core directory for edits. However, that made me think about security: where is best to put wp-config.php? There is an argument that it should be placed even higher than the site’s root folder, and that argument has been really well explained here.

If you do decide to move the file outside of the site directory you will need to do the following (taken from answer on Stack Exchange):

  1. Move the wp-config.php file to your decided location on the server, e.g. ../conf (change this where applicable).
  2. Create a new empty ‘wp-config.php` in the site root.
  3. Add this to the file:
/** Absolute path to the WordPress directory. */
if ( !defined(‘ABSPATH’) )
    define(‘ABSPATH’, dirname(__FILE__) . ‘/‘);

/** Location of your WordPress configuration. */
require_once(ABSPATH . ‘../conf/wp-config.php’);

wp-content

With our WordPress core files now in a subdirectory, it doesn’t really make sense to have the wp-content directory inside it. Themes, plugins and user uploads should be separate to the core files, especially if they are version controlled.

Again WordPress gives us a way to accomplish this, using some constants in wp-config.php:

define( 'WP_CONTENT_URL', get_option(‘home’) .  ‘/content' );
define( 'WP_CONTENT_DIR', dirname( ABSPATH ) . '/content' );

I’ve renamed the wp-content directory to simply content here. As Mark Jaquith argues in his WordPress skeleton site, it is cleaner and no longer in the scope of the wp directory. I’ve also used get_option(‘home’) to make this snippet reusable.

If your site is a multisite installation then you may have issues defining the URL and directory like this. If these constants are undefined, by default WordPress sets them in /wp-includes/default-constants.php like so:

define( ‘WP_CONTENT_URL’, get_option(‘siteurl’) . ‘/wp-content’);
define( ‘WP_CONTENT_DIR’, ABSPATH . ‘wp-content’ );

Notice the use of get_option(‘siteurl’) in the URL constant. This means that the constant will be specific to a subsite when using multisite. If we define the URL ourselves, it will not be dynamic in this way.

We actually came across this issue whilst developing the upcoming pro upgrade for WP Offload S3. I had my local development site set up with the core files in a subdirectory and wp-content in the root defined with the constant. This meant that when I inserted media into a post for a subsite, the URL did not contain the subsite path:

http://mysite.dev/wp-content/uploads/sites/3/2015/07/image.png

Instead of:

http://mysite.dev/testsubsite/wp-content/uploads/sites/3/2015/07/image.png

This has caused us a few headaches, but is exactly what WordPress means when they show this error when installing multisite on an install with a custom content directory:

Multisite compatibility error

WP Migrate DB Pro

The WordPress codex article also describes how to use a pre-existing subdirectory install.
If you have existing content that will now have incorrect URLs you can use the export feature of WP Migrate (Pro) to produce an export of your database with the old URLs replaced with the new ones. You can then import the SQL file back into your database.

We often receive support requests from people attempting to perform a migration where one site has the core files in a subdirectory and the other does not. Unfortunately we don’t support that at the moment. The sites would need to be converted to the same type of install before a successful migration can take place. We do support migrations between two sites with the same name subdirectory where the WordPress core files are stored. We are looking to introduce full migration support in the future.

I hope this has given you a good idea of the different directory structures WordPress allows, and a different approach to implementing the install.

What’s your preferred WordPress install setup? Let me know in the comments below.

About the Author

Iain Poulson

Iain is a WordPress and PHP developer from England. He builds free and premium plugins, as well as occasionally blogging about WordPress. Moonlights as a PhpStorm evangelist.

  • Great article! It’s so much nicer working on projects with WP installed by Composer in a subdirectory. Bedrock handles this and more, including separate configs per environment (development, staging, production).

    • Daniel

      Roots’ Bedrock & Trellis for the win!

    • Thanks Ben! Yep, I’ve used Bedrock in the past and am a fan 🙂

    • +1 🙂

  • wpjim

    This is so timely, because I had the exact problem yesterday, where I was trying to migrate from an installation that was in a subdirectory to one that was not. My solution was to alter the “migrate to” url to include the subdirectory, so it was like, test.com/ —> test2.com/sub. Then, after the migration, all I had to change was the Site URL (or the Home URL, I can never remember which) in the database and everything worked.

  • Jon

    Oh how I wish this was the WP default… Thanks for the great writeup.

    • Cheers Jon! That has made me think – why isn’t it the default? Must look into that…

  • This is a fantastic article. Great improvement to my workflow, thanks!

  • harishchouhan

    Great article. I previously used a setup of having WP core as a GIT submodule in my main project. I do hope you also write more about WP-CLI and especially using it on WIndows 🙂

  • daronspence

    Very cool idea! Does this mean that you guys are going to “officially” support migrations to-and-from subdirectories now? 🙂

    https://deliciousbrains.com/wp-migrate-db-pro/doc/wordpress-core-installed-subdirectory/

  • Our Multisite setup is almost exactly the same, as it makes it super easy to destroy and rebuild the core /wp/ directory if something goes wrong (one of the best perks about moving wp-content/ and wp-config.php out of there)! Just because of the way our platform works, we use git to clone the official WordPress core from their GitHub repo into /wp/ (using Composer would be a nice upgrade). This also makes it easy to quickly upgrade to new versions using git checkout [tag].

    Instead of get_option(‘site_url’), I made use of the DOMAIN_CURRENT_SITE constant and so far so good on our subdirectory-based Multisite installation (if anyone has a rationale for why this is bad…please say so!):

    define( ‘WP_CONTENT_URL’, ‘https://’ . DOMAIN_CURRENT_SITE . ‘/wp-content’ );

    • Adam – that is another great reason to keep `wp` separate that I didn’t mention, thanks!

      Your setup sounds great, and using that constant for the MS root sounds sensible to me.

    • Jamie

      Any chance you can share your setup for this Adam? I’m having all kinds of problems in getting my multisite install to work with this setup.

      • Hi Jamie, not sure what I can add beyond what’s described in this article, as it’s pretty similar. What’s going on with your Multisite setup that’s giving you trouble? Is it throwing errors, or just not working somehow?

        Here’s a quick overview of how our files are structured on our Multisite installation (“/” is the document root of our site):

        /index.php [the normal WordPress index.php, updated to point to /wp/]
        /wp/ [WordPress core directory cloned using git, current version checked out by its tag]
        /wp-content/ [our set of plugins and themes, separated from the WP core]
        /wp-config.php [WordPress knows out-of-the-box to look one directory higher than the core install for your config file]

        And here are some lines from our wp-config.php that might be illuminating — I think these are the only things that are custom to this kind of setup:

        /* In the Multisite section of wp-config.php — this should be generated for you when you set up Multisite, but just in case it’s not clear… */
        define(‘DOMAIN_CURRENT_SITE’, ‘example.com’);
        define(‘PATH_CURRENT_SITE’, ‘/’);

        Then below that:

        /*
        * Let’s customize the location of our wp-content directory, keeping it out of the WordPress core (change the http to https if you’re enforcing HTTPS across your whole site).
        */
        define( ‘WP_CONTENT_URL’, ‘http://’ . DOMAIN_CURRENT_SITE . ‘/wp-content’ );
        define( ‘WP_CONTENT_DIR’, dirname( __FILE__ ) . ‘/wp-content’ );

        • Jamie

          Thanks Adam, appreciate your response. With your clarification and a fair bit of googling I managed to get it working with a few changes.

          For the sake of others who may have similar challenges, here’s what I did. First off, I used this to define the custom content directory rather than setting the variable as you did:
          define( ‘WP_CONTENT_URL’, ‘http://’ . $_SERVER[‘HTTP_HOST’] . ‘/content’ );

          That’s so that if i’m using subdomains then it will direct to the correct one instead of always to the root domain.

          The other thing I had to do was to change some settings in wp_options table before I installed multisite on the install.
          siteurl:
          http://www.example.com/wp (with wp)
          home:
          http://www.example.com (without wp and no
          slash)

          That way it all worked.

  • Great post, I’m looking forward to add this to my WP workflow.

  • Ezekiel Gabrielse

    Great post. I’m glad this approach seems to be catching on. Over the past year or so, my company has moved towards a similar structure. We took a few notes from projects like Bedrock and WP-Skeleton, but also integrated npm, build tools and created a command line utility to quickly get up and running using Vagrant. We’ve recently open sourced it here, if anybody is interested in checking it out: http://themejuice.it/

  • Benoît Chantre

    Thanks for this article! I have one question related to the .htaccess file: it doesn’t seem to be managed by the script. In the codex, it is said to copy this file in the root directory. Is it a mistake?

  • Steve Byde

    Hi. I’ve have got three sites to move that were built on Bedrock. Is this easy to do?

  • longislandonlineradio

    Hi Iain, thank you for this article. I see that this was written over a year ago, but I have just one issue after following your steps above. Everything works well, except that now, my theme’s CSS is still pointing toward my subdirectory (/wp in this case) and the site is still looking for my wp-content directory under my subdirectory, even after I added define( ‘WP_CONTENT_DIR’, ABSPATH . ‘wp-content’ ) to my wp-config.php. Please help.

    Thanks,
    Bruce

    • Hi Bruce, what are the siteurl and home options defined as in the options table?

  • Great to see this nice write up. It is very helpful.

  • Its very well explained.

  • Semiprecious.com

    I have have followed the instructions for making a subfolder installed wordpress appear like it is running in root, however, /wp-admin is not working, anything else needs to be configured besides this?