https://deliciousbrains.com/move-wordpress-root-subdirectory/
In this article, we look at the benefits and limitations of installing WordPress in a subdirectory, how to install and manage subdirectory installs, and how to move WordPress core out of and into a subdirectory for an existing site.
One of the common criticisms PHP developers level at WordPress is that it installs all its core files directly in the web root directory. Modern PHP frameworks like Laravel install their required files as separate dependencies in a vendor
directory, each residing in its own specific subdirectory. Not only that, but unlike WordPress, Laravel keeps almost all the PHP files out of a publicly accessible directory, which is very nice for security. This makes for a cleaner, more secure folder structure in the application’s web root.
These modern PHP application directory structures make a default WordPress install look decidedly unprofessional. While it’s not possible to move all the PHP files out of the public web root in a WordPress install, you can customize your installation to mimic this dependency-based development concept by installing WordPress in a subdirectory.
#Table of Contents
How to Tell if WordPress Core is Installed in a Subdirectory
How to Install WordPress Core in a Subdirectory
Considering the WordPress File and Folder Structure
Converting a Root Install to a Subdirectory Install
Converting a Subdirectory Install to a Root Install
#The Benefits of Installing WordPress Core in a Subdirectory
In most web hosting environments, your account is provisioned under a /home/username
folder where username
is your account username. This folder is sometimes called the top-level or root of your account, and the actual path varies from web host to web host. You will typically have a public
or public_html
folder configured to serve the website files. This is generally known as the web root directory, and it’s where WordPress is usually installed.
The direct benefit of moving WordPress core files into a subdirectory is a cleaner and more professional-looking directory structure in your web root. Some folks think that switching to a subdirectory install improves security through obscurity. This is only really applicable if you move your wp-config.php
file outside of the publicly accessible web root directory, whether you use a subdirectory install or not.
However, in the modern world of automated scanning bots, moving your WordPress core files to a subdirectory might help decrease the chances of malicious bots detecting vulnerabilities when scanning your site. For example, in a typical WordPress install, all your core files are in the public web root, which is accessible from your apex domain (e.g., https://hellfish.media
). So all a scanner needs to do is scan your domain name, check for a common WordPress file like https://hellfish.media/wp-blog-header.php
, and it will know you have a WordPress install. If you move the core files to a subdirectory, it makes it harder (but not impossible) for attackers to identify that you’re running WordPress.
A cleaner directory structure and an increase in security are sound reasons to move an install into a subdirectory, but there are some things you’ll need to know to accomplish this. You won’t be able to just move the WordPress install to a subdirectory and expect everything to work.
#How to Tell if WordPress Core is Installed in a Subdirectory
With a WordPress install you’ve completed yourself, you’ll likely know if it’s installed in the web root (i.e., a root install) or a subdirectory. However, you might be in a situation where you inherit a site, or use a hosting provider’s one-click installer, or it’s been a really long time since the initial setup.
To determine if your WordPress install is in the web root or a subdirectory, head to your WordPress dashboard and navigate to the Settings > General screen. There you will see the “WordPress Address (URL)” setting and “Site Address (URL)” settings:
If the values match, you have a root WordPress install. If the “WordPress Address (URL)” has an additional path compared to the Site Address (URL), it’s in a subdirectory. You can also tell by looking at the URL when you log into your site. If WordPress was installed in a subdirectory, then the wp-admin
page will be shown under that subdirectory (such as hellfish.media/wp/wp-admin/
).
#How to Install WordPress Core in a Subdirectory
There are a few different ways you can install the WordPress core files in a subdirectory.
#The Official WordPress Documented Methods
The methods to move WordPress into a subdirectory are detailed in the WordPress.org support documentation. These options are available to you after you’ve already installed WordPress.
The two options are:
- Moving a WordPress install to its own directory, without changing the site URL
- Moving a WordPress install to its own directory, with a change to the site URL
Neither of these options really help with the increase in security we discussed earlier. The first option moves the site files to a subdirectory and uses rewrite rules in a custom .htaccess
file to rewrite requests to the subdirectory. Any bots scanning your URL will still be able to determine that it’s using WordPress.
The second option is even less related to security, and more useful if you have a custom site setup. This is often used where you want to use WordPress to power a blog at a subdirectory of your URL (e.g., https://hellfish.media/blog
) and you’ll use something else to power your top-level URL.
That being said, both methods move your WordPress install to a subdirectory, so your web root at least looks tidier.
What would be nice is that first option, but without any URL rewriting for better security.
#Manually Configure a Subdirectory Install
If you recall the earlier description of how the files in a WordPress installation “require” each other, you might have also noticed that in the wp-blog-header.php
file, the ABSPATH
constant is defined. This constant points to the location where the WordPress core files are installed, no matter where that might be on the file system.
This means that with a little bit of knowledge and some tinkering, we can move all the WordPress core files into a subdirectory, and make a few small changes to let WordPress know where everything is. The process looks like this:
- Follow the default WordPress install process.
- Once WordPress is installed, create a subdirectory and move all the files from the web root to that subdirectory. To help with “hiding” the core files, you should give the subdirectory an uncommon name, but for our example, we’ll create a subdirectory called
wp
, and move all the files to thewp
subdirectory. - Then, copy (not move) the
index.php
file from the subdirectory back into the web root directory. - In that newly copied file, update the line that requires the
wp-blog-header.php
file, to point to the new subdirectory, and save that file:require __DIR__ . '/wp/wp-blog-header.php';
- The last step is to update the
siteurl
value in your WordPressoptions
table— using something like the SQL Buddy plugin or phpMyAdmin—to point to the new subdirectory install, for example,https://hellfish.media/wp
.
You’ll note we said “copy (not move)” in step 3. This is important because WordPress needs an index.php
file in the install directory (in this case the subdirectory), or it will cause PHP warnings to display on your site or fill up your debug.log
file.
Once you’ve completed these steps and all your core WordPress files are in a subdirectory, you will still be able to browse to the front end of your site at your top-level domain (e.g., https://hellfish.media/
), but to access your Dashboard you’ll need to browse to wp-admin
in the subdirectory (e.g., https://hellfish.media/wp/wp-admin
). You might be concerned about losing the changes you’ve made to the copy of the index.php
file the next time WordPress is updated. However, WordPress will only update core files in the installation directory, in this case, the subdirectory. So it’s perfectly acceptable to edit the index.php
file you copied to the web root directory. Your web root is a little tidier, and it’s a bit harder to determine you’re running WordPress.
While this gives you a small increase in security, we’ll dive into other changes later on to further improve security.
#Using WP-CLI to Install WordPress in a Subdirectory
You’re probably going to want to automate the above process if you are managing your own WordPress installs or you have your own VPS servers. Fortunately, this is possible with a little Bash scripting and WP-CLI. The following script leverages WP-CLI to create a new WordPress site installation with the core files in a specific subdirectory, essentially reproducing everything we did above. It also assumes you don’t already have the database created, and creates it for you. The process will still work if you have an existing database, but will display a MySQL error message that the database already exists.
#!/bin/bash
# Installation:
## Download the script to your home directory
# Make sure it has execute permissions (`chmod +x wp-install-core-sub-dir.sh`).
# Install the script in one of the folders in your PATH. (`mv wp-install-core-sub-dir.sh /usr/local/bin/wp-install-core-sub-dir`)
#Usage:
# $ mkdir mysite
# $ cd mysite
# $ wp-install-core-sub-dir {sub-directory} {db_name} {db_user} {db_pass} {site_url} "{site_title}" {admin_user} {admin_pass} {admin_email}
CORE_DIR=${1-'wp'}
DB_NAME=${2-'wordpress'}
DB_USER=${3-'root'}
DB_PASS=${4-'password'}
SITE_URL=${5-'https://wordpress.test'}
SITE_TITLE=${6-'WordPress Site in a Subdirectory'}
SITE_USER=${7-'admin'}
SITE_PASS=${8-'password'}
SITE_EMAIL=${9-'your@email.com'}
# create the dir for the core files
mkdir $CORE_DIR
cd $CORE_DIR
# download WordPress files
wp core download
# create the wp-config.php file
wp config create --dbname=$DB_NAME --dbuser=$DB_USER --dbpass=$DB_PASS
# create the database
wp db create
# install WordPress (less than 5 mins)
wp core install --url=$SITE_URL --title="$SITE_TITLE" --admin_user=$SITE_USER --admin_password=$SITE_PASS --admin_email=$SITE_EMAIL
# Copy (not move) index.php file to root
cd ../
cp "$CORE_DIR/index.php" ./index.php
# Edit index.php to point to correct path of wp-blog-header.php
perl -p -i -e "s/\/wp-blog-header/\/$CORE_DIR\/wp-blog-header/g" index.php
# Update the siteurl in the database with sub directory path
cd $CORE_DIR
wp option update siteurl $(wp option get siteurl)/$CORE_DIR
# Uncomment the below line if you want the config in root
#cp "$CORE_DIR/wp-config.php" ./wp-config.php
echo 'Install finished!'
You can also view this script on Github.
#Using Composer to Install WordPress in a Subdirectory
A popular way to manage WordPress projects is to use Git for source control, and Composer to install WordPress core, the theme, and any plugins as dependencies in the composer.json
file of your project.
When you’re ready to deploy your site, you trigger an automated deployment process, which runs composer install
to install all your required dependencies.
If you use the johnpbloch/wordpress
package, it will install the wordpress-core
package in a subdirectory called wordpress
by default. You can change this by supplying a custom wordpress-install-dir
directory in the extra
section of your composer.json
. You can then make use of the Composer post-install-cmd
event to run the necessary subdirectory changes you need.
{
"name": "polevaultweb/wp-composer-core-sub-dir",
"description": "Installing WordPress in a subdirectory with Composer",
"require": {
"johnpbloch/wordpress": "^5.8"
},
"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"
]
}
}
As you can see, this replicates the subdirectory process we’ve already outlined earlier. Once the composer install
has been completed, WordPress will be installed in the wp
subdirectory, the index.php
file will be copied to the web root, and edited to update the path to wp-blog-header.php
It is worth noting that the post-install-cmd
event only fires after the install command has been executed with a lock file present. It won’t trigger the subdirectory the first time you run composer install
locally, as the lock file is only generated after the first time composer install
is run.
Unlike the Bash script above, this does not create the wp-config.php
file or run the WordPress installation to create the database tables. You can do these manually in your browser, using WP-CLI commands in the post-install-cmd
event, or using post deploy scripts if your hosting provider supports them.
#Considering the WordPress File and Folder Structure
As mentioned earlier, moving the core files to a subdirectory allows for a small security improvement, but it’s still possible to determine if your site is running WordPress. This might be a good time to think about any other changes we could make to improve our directory structure and increase security.
Our recommendation is to configure your top-level or root directory structure to look something like this:
/home/username/
conf/
wp-config.php
public/
content/
wp/
index.php
wp-config.php
We’ve already shown you how to move the WordPress core files into a subdirectory (in this case wp
) so let’s see what else we can change, and why.
#Moving the WordPress Config File
The wp-config.php
file contains your database credentials, and often any other important API or third-party access keys, but did you know that it’s possible to move this file to a different location from the rest of your WordPress core files?
Moving this file to somewhere other than the web root or the subdirectory install is generally considered a much safer choice. The reason for this is that in some cases, problems with the web server could cause it to stop processing PHP files correctly. Common causes for this include a lack of available memory, or a web server misconfiguration. Whatever the cause, if this happens, visitors to the site might be able to view the contents of PHP files including the database credentials in your wp-config.php
file.
In fact, many would argue that it should be placed even higher up in the directory tree than the root directory.
To achieve this, you would move the wp-config.php
file to a different location on the server (in our case we’ll move it to the conf
directory just above the public
web root directory) and either comment out or delete all the code after the following line:
/* That's all, stop editing! Happy publishing. */
We then create a new wp-config.php
file in the public web root, with the following code:
<?php
require_once __DIR__ . '/../conf/wp-config.php';
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
WordPress will automatically look for the wp-config.php
in the parent folder to where the core files are. By creating our custom wp-config.php
file in the web root, we keep the wp
folder that contains the core files clean of any custom files. The “true” wp-config.php
file is moved to a location that’s not publicly accessible, but where WordPress can still require it, and your database (and other) credentials are safer.
#Moving the WordPress Content Folder
With our WordPress core files in a subdirectory, we could consider moving the wp-content
directory too. Themes, plugins, and file uploads should (in most cases) be separate from the core files. This is especially useful if you use version control software like Git to manage your project source code.
WordPress allows us to accomplish this, by setting some WordPress-specific constants, in the wp-config.php
file in our wp
subdirectory.
define( 'WP_CONTENT_DIR', dirname( dirname(__FILE__) ) . '/content' );
define( 'WP_CONTENT_URL', 'https://' . $_SERVER['HTTP_HOST'] . '/content' );
This configuration tells WordPress to expect to find the content directory in a directory called content
in the web root. It’s inspired by Mark Jaquith’s WordPress skeleton, with minor tweaks to match our directory structure. As Mark points out in the GitHub readme, it’s cleaner and the content directory is no longer in the scope of the wp
directory.
If you make this change on an already existing site, you will need to move the contents of your wp-content
directory (plugins
, themes
, uploads
, etc.) to the new content directory. You will also need to run a Find & Replace on all your data. The goal is to update URLs for your media and asset files to point them to the new content URL, as well as ensuring any saved file paths are now pointing to the new content path.
If any external sites are linking to any of your media or assets, you’ll want to set up a path redirect, so that those links don’t throw 404 errors. This can be achieved by setting a path redirect using regex with a matching string of ^/wp-content/(.*)
and setting the redirecting string to /content/$1
. We recommend setting up this redirect at the web server level for best performance. If using SpinupWP, this can be done via the Path Redirects screen:
ts screen:
If you’re unable to set up a redirect at the web server level, the Redirection plugin is the next best thing.
#Converting a Root Install to a Subdirectory Install
Installing the WordPress core files when starting a new site is straightforward enough, but converting an existing root install to a subdirectory install requires some care. It’s not simply a case of creating a subdirectory in your web root and moving all the WordPress files there. You also need to take into account any URLs or paths stored in the database that points to your root install directory. We cover the details of this in our WordPress Core Installed in a Subdirectory help doc, but let’s look at a simple example.
I’ve set up a local site, https://hellfishmedia.test
, as a WordPress root install.
I’ve also created a very basic page to serve as the site home page, which includes an image in the page content, and a link to a Contact page.
#Step 1: Prepare Your Site
If this is a live site, it’s a good idea to show a maintenance message while you make changes that could break the site. For this, we recommend the WP Maintenance Mode plugin. Install it, activate maintenance mode in the plugin settings, and your users will see a message letting them know about the brief period of downtime.
Front-end users will only see the maintenance page, but we can still access the WordPress dashboard through https://hellfishmedia.test/wp-admin/
.
#Step 2: Update the Database
For our example, we’ll be moving WordPress into a new /wp
subdirectory, so you need to update the database to reflect that change – but only for URLs that need it.
You only need to worry about references to image files and assets contained in theme or plugin folders. All of those are located under /wp-content
by default, or a custom content path, if you’ve configured one. As such, you can use the content path to target URLs and file paths you need to change. If you’ve configured a different location for your content as we described earlier, you might need to tweak this to match.
This is possible using any decent find and replace tool, including the WP-CLI search-replace
command, Better Search Replace, or WP Migrate DB. For our purposes we’ll be using WP Migrate DB Pro‘s Find & Replace tool.
In our example site, we’ll want to find //hellfishmedia.test/wp-content
and replace it with //hellfishmedia.test/wp/wp-content
, and find /home/iain/development/websites/hellfishmedia/wp-content
and replace it with /home/iain/development/websites/hellfishmedia/wp/wp-content
to update URLs and file paths respectively. For the sake of the test, we’re also replacing the site title. Here’s what this will look like in WP Migrate DB Pro:
What’s great about the WP Migrate DB Pro Find & Replace tool is that you can preview the changes before you run them.
You can also inspect what updates the plugin will make on a per-table basis.
Once you run the find and replace, and take a look at any page content, you’ll see that while links to the Contact page don’t include the subdirectory, the embedded image does:
Your next step is to update the “WordPress Address (URL)” setting in your “General Settings” page to include the subdirectory. You can do this by navigating to Settings > General in your WordPress admin, and adding the subdirectory to the “WordPress Address (URL)” field. This will break the backend of your site until you complete the rest of this process, so make sure you don’t need to access it for a bit.
Once you save the change, your WordPress admin dashboard will show you a “Page not found” error and your maintenance page will lose all its styling. This happens because WordPress is still looking for all its admin and asset files under https://hellfishmedia.test/wp/
, but they don’t exist.
#Step 3: Moving WordPress Files Into the Subdirectory
While the maintenance page won’t look pretty at this point, at least it displays the maintenance message to your site users. Unfortunately, that’s going to break in the next step, as you need to move the files from the web root into the correct subdirectory. For our example, we’ll do the following:
- Create the
wp
subdirectory in the web root. - Move everything from the web root into the
wp
directory. - Copy (not move) the
index.php
file and the.htaccess
file from/wp
back into the web root directory.
You can do this via your local file manager, or via an FTP client if it’s a remote server.
Video Player
00:00
00:21
Alternatively, if you prefer the terminal, the commands would look like this:
cd ~/development/websites/hellfishmedia
mkdir wp
mv * .* wp/
cp wp/index.php .
cp wp/.htaccess .
This will cause the maintenance page to display an error.
Warning: require(/home/iain/development/websites/hellfishmedia/wp-blog-header.php): Failed to open stream: No such file or directory in /home/iain/development/websites/hellfishmedia/index.php on line 17
Fatal error: Uncaught Error: Failed opening required '/home/iain/development/websites/hellfishmedia/wp-blog-header.php' (include_path='.:/usr/share/php') in /home/iain/development/websites/hellfishmedia/index.php:17 Stack trace: #0 {main} thrown in /home/iain/development/websites/hellfishmedia/index.php on line 17
The last step is to update the require
expression in the index.php
file in the web root with the path to the new subdirectory, as we showed you right at the beginning of this guide. Edit the file in your code editor, and update the line to look like this:
require __DIR__ . '/wp/wp-blog-header.php';
Alternatively, if you prefer to run a terminal command, you can use the same perl command that our WP-CLI script used earlier:
perl -p -i -e "s/\/wp-blog-header/\/wp\/wp-blog-header/g" index.php
Your root install should now be fully converted to a subdirectory install. You should take this opportunity to check all your content, make sure everything appears to be working, and that images display as you’d expect.
It’s also a good idea to log into your dashboard and navigate to Settings > Permalinks to save and flush your permalinks.
One last thing to note is that the example site we set up here doesn’t have a lot of plugins or a custom theme. These aspects could add additional complexities when converting between root and subdirectory installs. For this reason, we recommend making sure you’ve thoroughly tested the site after converting it from one install type to another.
#Converting a Subdirectory Install to a Root Install
If you’ve completely converted to running all your WordPress sites as subdirectory installs, you might come across a situation where you need to work with a live client site that is a root install. If you use a tool like WP Migrate DB Pro, it’s not possible to migrate from a subdirectory install to a root install. This means you might need to first convert a local dev or staging site to a root install before migrating it to the live site. Converting a subdirectory install to a root install is almost always easier than the reverse because the URLs and paths are already using the subdirectory path you’re converting from. To do this, you’ll need to update both the database and the file structure, ideally with a minimal amount of downtime.
I’ve set up a local site, https://hellfishmedia.test
, with WordPress installed in a subdirectory named wp
.
I’ve also created a very basic page to serve as the site home page, which includes an image in the page content, and a link to a Contact page.
#Step 1: Prepare Your Site
Converting the site is going to require some database changes but before you do this you’ll want to ensure that you can still access wp-admin
. To achieve this, define the site URL and home URL values in the wp-config.php
file. This will override the settings for siteurl
and home
in the wp_options
table and will prevent you from losing access to the WordPress dashboard.
// The URL where the WordPress application files are accessible
define( 'WP_SITEURL', 'https://hellfishmedia.test/wp' );
// URL where the front end is accessible
define( 'WP_HOME', 'https://hellfishmedia.test' );
If the subdirectory site is live, it would be a good idea to show a maintenance message while you make changes that could break the site. For this, we recommend the WP Maintenance Mode plugin. Install it, activate maintenance mode in the plugin settings, and your users will see a message to let them know about a brief period of downtime, rather than a broken site.
However, we can still visit the dashboard using https://hellfishmedia.test/wp/wp-admin/
.
The next step is to update the database.
#Step 2: Update the WordPress Database
Now you can update the database to remove any references to the subdirectory. This is possible using any decent find and replace tool, including the WP-CLI search-replace
command, Better Search Replace, or WP Migrate DB. For our purposes, we’ll be using the WP Migrate DB Pro Find & Replace tool.
To start, we search for the source site URL, including the subdirectory (//hellfishmedia.test/wp
) and replace it with the URL excluding the subdirectory (//hellfishmedia.test
). We also want to update any physical paths from /home/iain/development/websites/hellfishmedia/wp
to /home/iain/development/websites/hellfishmedia
. We’re also updating the site title for demonstration purposes:
As we mentioned earlier, WP Migrate DB Pro’s Find & Replace tool lets you preview changes before you run them.
You can even inspect what updates the plugin will make on a per-table basis. Here I can confirm that the link to the image in the wp_posts
table for my Home page will be updated, as well as the GUID for the image attachment. If I don’t want to update the GUID, I could alter this in the settings. In this case, it makes sense to change the GUID.
You might not notice any changes after you run the find and replace. The front end will still show the maintenance page, while the back end will still work because of the hardcoded constants.
However, if you look at some post content, you’ll notice that the images are broken and internal links now lack the subdirectory. Here is the home page in the Block Editor:
To fix this, there’s one last step we have to do.
#Step 3: Moving WordPress Out of the Subdirectory
Your last step is to manually move all the files in the subdirectory into the public web root directory of your site.
You drag and drop the files in your local file manager or use an FTP client if you’re working on a remote server. However, be sure to select the option to overwrite the index.php
file:
Once you’ve copied over the files, you can delete the wp
subdirectory.
You can also do this on the command line, but if your site is on a remote server you need to SSH into the server first. In this case, I’m working with my local hellfishmedia
directory that we ran the find and replace on earlier.
cd ~/development/websites/hellfishmedia
rm index.php
cp -R wp/. .
rm -rf wp
The last piece is to remove the WP_SITEURL
and WP_HOME
constants from your wp-config.php
and then test your site.
It’s also a good idea to log into your WordPress dashboard and navigate to Settings > Permalinks. Here, click the Save Changes button to make sure you flush the permalinks and that your URLs still work.
#Wrapping Up
Hopefully, we’ve given you enough information to consider switching your root installs to subdirectory installs, as well as the tools to do so. If you’re looking for a modern, Composer-based option, you could consider using a similar structure as our SpinupWP spinupwp-composer-site package, which takes the idea of installing WordPress as a dependency to a whole new level.
Do you run your WordPress installs in a subdirectory? What workflows do you use to manage your WordPress directory structure? Do you think WP Migrate DB Pro should do more to make the migration process between the root and subdirectory more straightforward? Let us know your thoughts in the comments section below!
This entry was posted in WP Migrate DB Pro, Workflow and tagged WordPress, Migration Tools, Development, Deployment, subdirectory.
ABOUT THE AUTHOR
Iain Poulson
Iain is a product manager based in the south of England. He also runs multiple WordPress products. He helps people buy and sell WordPress businesses and writes a monthly newsletter about WordPress trends.