While the WordPress upgrade system is great, sometimes people prefer to use command line tools or similar to manage their sites.

I hadn’t done this much until recently, but when I was at #wptybee, Matt asked me to set up a site using SVN externals. This turned out to be easier than expected, and a great way to keep a site up-to-date and easy to manage. So I thought I’d document the process a little bit here.

This is actually not an uncommon way of creating sites and maintaining them. I’d just never seen it documented anywhere before. If you know SVN, you probably already know how to do this.


First, you’ll need to have a website on a host. Obviously.

You’ll also need shell access on that host. SSH will do fine.

Next, you’ll need that host to have SVN. Easy way to check: just run “svn –version” at the command line.

Finally, you’ll need an SVN server. A central place to store your files. Some hosts let you set these up easily. Sometimes you’ll have to set one up yourself. How to do this is slightly outside the scope of this article.

Create the SVN environment

The first thing you’ll want to do is to check out your SVN area into a directory on the machine. You can do this part on either your local machine, or on the server. I did it on my Windows 7 laptop, using TortoiseSVN.

The basic idea here is that this site will become your new public_html directory. Alternatively, you can make it a subdirectory under that and use .htaccess rules or any form of rewriting to change where the root of your website is. Regardless, the directory as a whole will be your main website directory.

SVN Externals

Now, we’re going to get WordPress installed into your SVN. To do that, we setup the WordPress SVN to be an external of the SVN root.

If you use TortoiseSVN, then you’re going to right click inside the root of your checkout, then select TortoiseSVN->Properties. In the dialog that follows, you’ll create a new property named “svn:externals” and give it a value of “wp http://core.svn.wordpress.org/trunk/“. Hit OK to make it stick (note, do not select the recursive option).

Note, if you’re using the command line, a guide on svn externals is here: http://beerpla.net/2009/06/20/how-to-properly-set-svn-svnexternals-property-in-svn-command-line/.

What this does is simple, really. It tells the SVN that the contents in the “wp” directory will come from http://core.svn.wordpress.org/trunk , which is the main trunk version of WordPress.

Alternatively, if you’re not brave enough to run trunk all the time, you could use http://core.svn.wordpress.org/branches/3.0, which would make it get the latest 3.0 version at all times (which is 3.0.4 right now). And if you wanted to use a directory name other than “wp”, you could change that as well.

The point being that instead of having to manually update WordPress, you’re telling your SVN server that the contents there actually come from another SVN server. So when you do an “update”, it will go and grab the latest version of WordPress directly, without having to updated by hand.

After you’ve modified the externals setting for that root directory, you have to do a commit, to send the new property to the SVN server. Then you can do an update and watch it go and grab a copy of WordPress and put it into your directory directly.

Custom Content

So now we have a WordPress setup, but it’s not installed. No worries, but we’re going to make a custom installation here, so we’ll hand edit the wp-config file eventually.

Why are we doing this? Well, we want our wp-content folder to live outside of the WordPress directory. It’s going to contain our custom plugins and themes and so forth. That way, no changes to WordPress’s files in their SVN can ever touch our own files.

So we need to make a folder in the root called “custom-content”. Or whatever you want to call it. Since this name will be visible in your HTML, you might want to choose a good one.

Now inside that folder, make a plugins and a themes directory. For the themes, you’re probably using a custom theme for your site, and you can just put it in there directly.

Plugins are a different matter.

Plugins as Externals

I assume that whatever plugins you are using are coming from the plugin repository. Or at least, some of them are. For your custom ones, you can do pretty much the same as the themes and just drop the plugins into your plugins directory. But for plugins from the repo, there’s a better way.

In much the same way as we made the wp directory an external pointer to the core WordPress SVN, we’re now going to do the same for our plugins.

So step one, choose a plugin you want. Let’s choose Akismet for a demo.

Step two, in your plugins directory, do the SVN Properties thing again, and this time, add this as an svn:externals of
akismet http://plugins.svn.wordpress.org/akismet/trunk/” to get the akismet trunk.

Step three, commit and update again, and voila, now you have Akismet.

But wait, we might want more than one plugin. Well, we can do that too. Let’s add the stats plugin.

Step one, go back to the SVN->Properties of the plugins directory.

Step two, edit the existing svn:externals setting. This time, we’re going to add “stats http://plugins.svn.wordpress.org/stats/branches/1.8/” on a new line. Basically, you can have as many externals you want, just be sure to put each one on a new line.

Step three, commit and update and it’ll download the stats plugin.

Repeat for all the plugins you want to keep up-to-date.

You’ll note that I used a branch of the stats plugin instead of the trunk. That’s because the trunk isn’t the latest version in the case of the stats plugin. Not every plugin author treats the trunk/tag/branch system the same, so you should investigate each plugin and see how they keep things up-to-date with their setup.

Theme note

Note that I am using a wholly custom theme, but you might not be. Maybe you’re using a child theme of twentyten. Themes exist inside an SVN too, and you can create externals to them in the same way.

For example, in the themes directory, you could set svn:externals to  “twentyten http://themes.svn.wordpress.org/twentyten/1.1/” and get the twentyten theme for use. Any theme in the Themes directory should be available in this way.

Custom wp-config.php

Now we need to create our wp-config.php file. Grab a copy of the sample wp-config file and put it in the root. Yes, not in the wp directory. WordPress looks in both its own directory and in one directory above it, so having it in the root will still allow it to work and will keep the wp directory untouched.

Edit the file, and go ahead and add all the database details. Then add these lines:

define( 'WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/custom-content' );
define( 'WP_CONTENT_URL', 'http://example.com/custom-content');

You’ll need to use your own directory names and URL, of course. The purpose here is to tell WordPress that the wp-content directory isn’t the one to use anymore, but to use your custom content directory instead.

Special .htaccess

Because we’ve installed wp in a subdirectory off of what will be the root of the site, it would normally be referenced as http://example.com/wp/ which is undesirable. So I crafted some simple .htaccess rules to move the “root” of the site into the /wp directory, and still have everything work.

Options -Indexes
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteCond %{REQUEST_URI} !^/wp/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /wp/$1
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteRule ^(/)?$ wp/index.php [L]

The Options -Indexes just turns off the normal Apache directory indexing. I don’t like it when people try to look through my files. 🙂

The rest basically rewrites any URLs for directories or files that don’t exist into calls to the /wp directory, without actually modifying the URL. The rewrite is internal, so the URL remains the same. This has the effect of moving the root to the /wp directory, but still allowing calls to the custom content directory to serve the right files.

After you’re done, save the file, and commit everything you’ve done so far.

Running it on the site

At this point, you should be capable of going to the site and loading up all that you’ve done onto it. So SSH in (or whatever) and do a checkout of your new SVN setup into the proper location for it. You may need to rename your existing public_html directory and/or set proper permissions on it and such.

If you’re setting up a new site, you’ll be able to go to it and have it create the database tables and such as well.

What’s the point?

Okay, so you may have read through all this and wondered what the point was. Well, it’s simple, really.

Go to your server, and do “svn up public_html”… and then watch as it proceeds to update the entire site. WordPress, plugins, themes, your custom files, everything on the site. The externals makes WordPress and the plugins and such all update directly from the WordPress SVN systems, allowing you to do one command to update them all.

If you didn’t use trunk, then updating is only slightly harder. Basically, you just change the externals to point to the new version, then commit and do an svn up.

In fact, if you want to live dangerously, you can hook everything to trunk and then have it automatically do an svn up every night. Not many people recommend running production sites on trunk, but for your own personal site, why not? You’ll always get access to the latest features. 🙂

An additional thing to keep in mind is that with a system like this, the files on the site are always backed up on the SVN server. For things like inline file uploads, you’ll need to log in every once in a while and do “svn add” and “svn commit” to send the new files to the SVN server, but for the most part, everything on the site will be in your SVN and you can work with it locally before sending it to the site proper.

And it’s expandable too. You can get your sites files from any host that has svn on it. You could run more than one webserver, and have them on a rotation or something. There’s a lot of possible configuration methods here.

It sure is a nice way to update though. 😀



  1. […] Creating a WordPress site using SVN (Otto on WordPress) […]

  2. Great Post,
    thanks for the tips this will be a great resource, I too am thinking of documenting this on my blog as a reference, I just had @iamjohnford take me through the way he sets up his svn repositories for WordPress sites.
    Next part is deploying them live, I’ve heard that the .svn folders need to be locked down so just wondering if you accomplish this via the

    Options -Indexes

    in your .htaccess file?

    again thanks for a great read.. bookmarked for later 🙂

  3. Sorry in advance for the long post.

    Local Development of WordPress 3.0 with:

    1. Easy subversion updating of core WordPress files, themes and plugins
    2. Using mysite.com instead of localhost for easier deployment

    I had number 2 working using the advice from http://devpress.com/blog/a-really-sweet-wordpress-development-environment/ and the httpd.conf and hosts file changes listed below. Then, I moved core WordPress files into a subdirectory based on the directory structure below and item 1 worked, but item 2 is now broken. See http://ottopress.com/2011/creating-a-wordpress-site-using-svn/ re: item 1. I’m sure it has something to do with the Virtual Host setup I have, but I don’t know how to fix it.

    Windows XP
    XAMPP 1.7.4
    Wordpress 3.2.1

    Directory Structure:
    In my xampp folder, I have
    Under this directory, I have:
    /wp_core (all wordpress files from svn with my modified .htaccess)
    /wp_content (exported from /wp_core with my added plugins/themes)

    # BEGIN WordPress

    RewriteEngine On
    RewriteBase /mysite/wp_core/
    RewriteRule ^index\.php$ – [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /mysite/wp_core/index.php [L]

    # END WordPress


    ServerName mysite.com
    DocumentRoot “C:/xampp/htdocs/mysite/wp_core”
    ServerAdmin me@mysite.com

    Order Allow,Deny
    Allow from all

    /* Added definitions to move content location to subfolder */
    define( ‘WP_CONTENT_DIR’, $_SERVER[‘DOCUMENT_ROOT’] . ‘/mysite/wp_content’ );
    define( ‘WP_CONTENT_URL’, ‘http://localhost/mysite/wp_content’);

    Hosts file localhost mysite.com #local
    # example.com #production

    Results I’m getting:
    With the setup above, I get a blank screen at mysite.com. I assume this is because for some reason it’s pointing to /htdocs/mysite where there is now no index.php.

    If I remove the Virtual Host code from the httpd.conf file, I get XAMPP setup by going to mysite.com which I assume is pointing to xampp/htdocs which has index.php for XAMPP. I can get to my WordPress site either by using mysite/wp_core or localhost/mysite/wp_core, but I can’t seem to get Virtual Host to resolve to mysite.com and see the site.

  4. Figured out my own problem. Human error combined with needing to change settings that pointed to localhost or old directory structure once using the new directory structure.

  5. This is a very helpful post and I recommend it to every developer using wordpress. One thing I’ve encountered while using the custom-content folder is that the login page may end up failing. I’m yet to find the true cause of this (perhaps Otto could explain better) but it seems to get fixed if you define in your wp-config the following option:

    define(‘ADMIN_COOKIE_PATH’, ‘/’);

  6. […] in place that would allow me to checkout code from http & https URLs. Thanks to a very helpful guide by Otto, I managed to setup svn:externals and perform a couple of checkouts and updates directly from the […]

  7. Thanks for the awesome post, Otto. It’s extremely helpful!

    One question—When setting this up on a subfolder install (so the root is example.com/mysite/ and the wp folder would be at example.com/mysite/wp/) what do I need to change in .htaccess? I can get the admin area working with this code, but not the actual site frontend:

    Options -Indexes
    Options +FollowSymlinks
    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^(www.)?example.com$
    RewriteCond %{REQUEST_URI} !^/mysite/wp/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ /mysite/wp/$1 [L]
    RewriteCond %{HTTP_HOST} ^(www.)?example.com$
    RewriteRule ^(/)?$ mysite/wp/index.php [L]


    • I answered my own question after some fiddling around with .htaccess. This is the code that works:

      Options -Indexes
      Options +FollowSymlinks
      RewriteEngine on
      RewriteBase /mysite/
      RewriteCond %{HTTP_HOST} ^(www.)?example.com$
      RewriteCond %{REQUEST_URI} !^/wp/
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^(.*)$ wp/$1
      RewriteCond %{HTTP_HOST} ^(www.)?example.com$
      RewriteRule ^(/)?$ wp/index.php [L]

  8. Great tutorial, exactly what I was looking for. Otto, I ran into an issue that you may want to incorporate into the post. I created my site locally and then committed it to my live server (copied the db too). My site never created an .htaccess file under /wp (because it’s not writable for some reason), and therefore my %postname% permalink structure didn’t work. I fixed it by keeping the versioned .htaccess file you have above in the root dir, and manually placing a simple .htaccess file (not versioned — used code in /wp-admin/options-permalink.php) into /wp via FTP.

    P.S. – Love the Google sign-in — would also love seeing this in a plugin. (+1 for SGC!)

  9. Great post. One question (similar to David’s above): I don’t want the .htaccess WordPress creates in its directory when I use a custom permalinks. I just don’t want to mess around in that directory. Any way to use the root .htaccess file for the permalinks?


  10. […] update consists of three separate processes. Of course, there are those who have tweaked things to create a unified update process. But this is beyond the scope of the casual […]

  11. I notice that is I added this code at the end of “wp-config.php” then the site did not function.

    define( 'WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/custom-content' );
    define( 'WP_CONTENT_URL', 'http://example.com/custom-content');

    If I added at the start everything OK!

    Not sure how far down the file you can add these constants but this should be added to the tutorial.

    • All the defines in that file are at the top. Why would you think that you can add defines to the bottom?

      • Hi Otto,

        Most of the ‘wp-config.php’ file is made of of comments and ‘define’ functions as you say. As long as the additional ‘define’ functions are before this code:

        /** Sets up WordPress vars and included files. */
        require_once(ABSPATH . 'wp-settings.php');

        … Then it works. It would have saved me some time if this was mentioned in the tutorial above. I placed it after this and took a while before I realised this was critical.

        I’m not sure that PHP has any restrictions on where you can place ‘define’ functions does it?

        Great tutorial by the way! Thanks.

  12. Otto, Any idea of the appropriate .htaccess rules top get this to work with multi-site installs? I’m terrible with rewrite rules and can’t seem to make it work. I assume it’s possible but not really sure.

  13. Thank you for your hard work on this tutorial. Having followed it I am almost getting a website to show up, except, all the pages generate a 404 message like this:
    The requested URL /wp/contact-me/ was not found on this server.

    Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.

    I would have expected the URL to say /armllc-content/contact-me as I have made my custom-content directory name armllc-content

    My rewrite rules in the root directory are taken right from your example as shown below:

    # BEGIN WordPress

    Options -Indexes
    RewriteEngine On
    RewriteBase /
    #RewriteRule ^index\.php$ – [L]
    #RewriteCond %{REQUEST_FILENAME} !-f
    #RewriteCond %{REQUEST_FILENAME} !-d
    #RewriteRule . /index.php [L]
    RewriteCond %{HTTP_HOST} ^(www.)?allenrmarshall-consulting-llc.com$
    RewriteCond %{REQUEST_URI} !^/wp/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ /wp/$1
    RewriteCond %{HTTP_HOST} ^(www.)?allenrmarshall-consulting-llc.com$
    RewriteRule ^(/)?$ wp/index.php [L]

    # END WordPress

    Is it possible mod_rewrite is not enabled? How do I determine if it is?

    Thanks for any assistance you may be able to provide….

  14. This page has literally become the bible of automating wordpress upgrade through SVN. Good work Otto!

    I am in the process of implementing. However, there is one part that gets me stumped. And that is the part of moving the files from my own subversion repository to the public directory. What is the best way to accomplish this? Should I SVN checkout directly to the public directory? Or do I check out to my harddrive then upload through FTP? Please elaborate.

    • That’s up to you. I just check it out directly on the host, then use .htaccess rules to block web requests to any of the .svn directories.

      • Can you please, please, elaborate on how you check it out directly to the host? Assuming you are using TortoiseSVN, is it possible to check out to a remote location using ssh? I am not getting that part, and for a beginner, its more difficult than it sounds.

        And how do you go about updating? I assume that:
        1. You delete the previous versions checked out to your webspace via FTP.
        2. You modify svn:externals to get the latest stable version (assuming you are not using trunk)
        3. You check out to the webspace again

  15. […] be used for browsing Subversion repositories and downloading them to your local hard-drive. And one WordPress user has managed to find a convoluted way to automate WordPress updates using Tortoise SVN. He created […]

  16. You missed a hyphen – simple way to check if host has SVN just run “svn –version”

    Very informative post, Otto. Much Appreciated.

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Need to post PHP code? Wrap it in [php] and [/php] tags.