Insight

A Complete Guide to WordPress in its Own Directory on Pantheon

This post is intended for WordPress developers, and contains technical information.

TL;DR

Create a file called “wp-cli.yml” in your Pantheon root (the code/ directory). It should contain one line:

path: web/wp

This assumes that you have “web_docroot: true” in your pantheon.yml file, and that the directory you keep your WordPress core files and directories in is called “wp”. See the post below for complete details and assumptions.


I’m a bit of a neat freak, at least when it comes to my digital life. Everything has to be neatly in its place, or it stresses me out. (I’m sure my husband would appreciate it if I were more like that in the physical world!) So the practice of keeping WordPress core files in their own subdirectory has always been my preference. But as a fan of Pantheon managed WordPress hosting, I always struggled when attempting to do this on their platform. Finally, I figured out the cause, and more importantly, the solution.

What Is “WordPress in its Own Directory”?

If you’re not familiar with the concept, “WordPress in its own directory” is the practice of keeping all of the core files and directories (wp-includes, wp-admin, etc.) in a subdirectory of your website document root, rather than sitting in the document root itself.

There are a few reasons we might want to do this. One, it just looks neater that way. For some people (like myself), that’s practically reason enough. But also you can increase your security by configuring your webserver to block any attempts to read and write to that directory that you don’t specifically authorize. (However, this will also prevent WordPress from auto-updating itself, so take that into account.) The security angle isn’t quite as important on Pantheon, which locks down the webserver filesystem on production environments anyway, but if you run your own webserver (or use a cheap one), then you can lock the WordPress directory down yourself (but how to do so is outside the scope of this post).

There’s one last reason that you might want to do this: If you’re managing WordPress core and plugins using Composer (again, somewhat outside the scope of this post), it’s just easier if WordPress is contained inside a subdirectory; WordPress core doesn’t like being installed in parallel with plugins when you’re using Composer. ¯\_(ツ)_/¯

Step 0: A Few Assumptions

There are about as many ways to configure a WordPress installation as there are stars in the sky. So to keep from having to write “[some directory name here]” and the like at every turn, I’m going to use the same directory names I use in my configuration, along with a few other assumptions based on how my Pantheon environment is set up.

web_docroot: true

I use the web_docroot: true configuration in my pantheon.yml file, which sets the document root of your installation as /code/web instead of just /code. Again, it’s the neat freak in me, and because there’s a lot of things I keep in the repository root that aren’t meant to be on the web.

wp/

We’re going to call the subdirectory into which we’ll place WordPress’ core files wp/. Again, this is personal preference. You can call this directory _wp/, wordpress/, or even gefilte-fish/. It doesn’t really matter, but in this post, we’re calling it wp/.

mysite

We’re going to call the website we’re working with “mysite”, just for convenience’s sake. Likewise, we’ll refer to the cloned Pantheon git repository as “mysite”.

Which Upstream?

I’m using an empty WordPress upstream for all of my Pantheon sites. These instructions probably won’t work if you’re using Pantheon’s default WordPress upstream. Instructions for switching upstreams can be found on Pantheon’s site.

Last Warning

And as mentioned above the TL;DR, this is a technical post. Some understanding of command-line linux (git cloning, symlinking, etc.) is assumed.

Step 1: Moving WordPress into its Own Directory

WordPress.org has complete instructions on how to do this, which we will be drawing from and adapting for Pantheon users.

1. git clone mysite (or whatever your Pantheon site is called) onto your local machine.

2. Create a directory for your WordPress files in the web/ directory. Again, we’re using wp/ as the name of the directory that will house our core files and directories.

3. Move all of the WordPress core files and directories into your new directory. As of WordPress 5.2.4, those files and directories are:

  • index.php
  • license.php
  • readme.html
  • wp-activate.php
  • wp-admin/
  • wp-blog-header.php
  • wp-comments-post.php
  • wp-config-sample.php
  • wp-cron.php
  • wp-includes/
  • wp-links-opml.php
  • wp-load.php
  • wp-login.php
  • wp-mail.php
  • wp-settings.php
  • wp-signup.php
  • wp-trackback.php
  • xmlrpc.php

Notice that neither wp-config.php nor wp-content/ are on this list; we’ll come back to those in a bit.

Copy (not move) the index.php file we moved into the web/ (document root) directory, and modify the last line to read:

require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

This tells WordPress to where to find the rest of the files it will need to run.

Pantheon has a more robust wp-config.php file than the sample one that comes from WordPress, and is tailored to their architecture. Since this is a file that can get tweaked frequently, we’re going to keep this in the web/ directory instead of moving it into the wp/ directory. WordPress, fortunately, will look one directory above its own to find this file.

Assuming you’re using the Pantheon version, find the line in wp-config.php that begins define('WP_SITEURL', and modify it to read:

define('WP_SITEURL', $scheme . '://' . $_SERVER['HTTP_HOST'] . '/wp');

Again, we’re telling WordPress that its heart and soul lies within the wp/ directory. Now we just need to tell WordPress one more thing: where to find all of our plugins and themes.

After the define('WP_SITEURL'… line, add two more lines:

define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', WP_HOME . '/wp-content' );

This lets WordPress know where all the plugins, themes, etc. can be found. You can now test your website on your dev environment, and if it works properly (?), move those changes to test (and test it again), and then finally to live (and test it once more).

Step 2: wp-cli.yml

When I used to host websites on AWS, I did the “WordPress in its own directory” thing all the time. When I switched to Pantheon, I tried to do it using the procedure outlined above. While everything appeared to work, I knew that something was wrong because every time I tried to clear the caches, the cache-clearing process would return an error. I just couldn’t figure out why, and I wasn’t getting any help from the Pantheon console.

One hint I found after a lot of googling was to try and run the cache-clearing process using Pantheon’s command-line tool, terminus:

mysite$ terminus env:clear-cache taupecatstudios.live

Doing so still returned an error, but this time, with an actual error message!

[error] Error: This does not seem to be a WordPress install.

At last, progress. I recognized this as being a wp-cli error. But the question remained: how to fix it?

Fast forward to WordSesh when Andrew Taylor gave a presentation on “Continuous Integration with Pantheon”. While I’ve never been able to successfully navigate CircleCI, I do use Composer to manage WordPress core and plugins (see above). But until now, being stymied by the fact that WordPress via Composer doesn’t play nice with installing plugins at the same time, I had to resort to hacky Composer post-install shell-scripts to make sure everything worked correctly. Andrew’s presentation showed me that there had to be a better—if undocumented—way.

One of the things I got out of that WordSesh session was a new Pantheon sandbox configured for Composer and CircleCI. I just didn’t have time to do anything with it, and it sat untouched for so long it got frozen. When at last I had a little downtime, I unfroze it, inspected the contents, and finally discovered the secret sauce that makes the whole thing work:

Inside the root (code/) directory, there should be a file called wp-cli.yml with one line: path: web/wp.

Adding and committing that file into my Pantheon project solved the wp-cli cache clearing problem. But alas, not everything was 100% right. Not yet.

Running other wp-cli commands via terminus gave me odd results. For example, listing my plugins with the command:

terminus remote:wp mysite.live -- plugin list

gave me an empty list of plugins. Same thing happened when I tried to list my themes.

 +------+--------+--------+---------+
 | name | status | update | version |
 +------+--------+--------+---------+
 +------+--------+--------+---------+

Certainly I’m not running WordPress without any plugins or themes. But remember when we didn’t move our wp-content/ directory along with our WordPress core files? It’s time to tweak things a little more to accommodate.

In your cloned git repository, on the linux command line, create a symlink to your wp-content/ directory:

mysite/web/wp$ ln -s ../wp-content wp-content

Commit that to git, push it to your Pantheon repository, and then up the Pantheon dev/test/live workflow. Now when you run terminus wp mysite.live -- plugin list, you should see a complete list of the plugins available on your website:

 +------------------------------------+----------+-----------+---------+
 | name                               | status   | update    | version |
 +------------------------------------+----------+-----------+---------+
 | acf-content-analysis-for-yoast-seo | active   | none      | 2.3.0   |
 | advanced-custom-fields-pro         | active   | none      | 5.8.5   |
 | akismet                            | active   | none      | 4.1.2   |
 | black-studio-tinymce-widget        | active   | none      | 2.6.9   |
 | classic-editor                     | inactive | none      | 1.5     |
 | codepen-embedded-pen-shortcode     | active   | none      | 0.7.1   |
 | contact-form-7                     | active   | none      | 5.1.4   |
 | debug-bar                          | inactive | none      | 1.0     |
 | easy-digital-downloads             | active   | none      | 2.9.17  |
 | featured-images-for-rss-feeds      | active   | none      | 1.5.2   |
 | flamingo                           | active   | none      | 2.1     |
 | google-analytics-for-wordpress     | active   | none      | 7.9.0   |
 | duracelltomi-google-tag-manager    | active   | none      | 1.10.1  |
 | iq-block-country                   | active   | none      | 1.2.6   |
 | limit-login-attempts               | active   | none      | 1.7.1   |
 | worker                             | active   | none      | 4.9.1   |

(etc.)

Finally, everything in its place, and working properly! Caches are clearing! Plugins are listing! And all is right with the world!

Conclusion

I wish this were more thoroughly documented on Pantheon’s site. But since originally tweeting out about this last week, I’ve been contacted by several folks at Pantheon about it. I hope that all of this makes it into their documentation soon.

If you’re a stickler for digital neatness like I am and have been trying to find a way to organize your Pantheon installation just so, I hope that this post has helped.

Got any other neat tricks for keeping your Pantheon sites neat and tidy and running great? Let me know in the comments!