Smokes your problems, coughs fresh air.

Untangling WordPress’ core files from your local customizations

Since version 2.6, WordPress can be installed in its own directory, separate from your customizations and everthing. Needless to say, this makes upgrading a whole lot easier.

If, in the pre-2.6 days, you wanted to fetch your WordPress updates through SVN, the docs would advice you to do an svn checkout from the official WP SVN repo in your working dir and then do an svn update whenever you want to update WordPress. This works because svn update leaves local modifcations alone. However, this means that you’ll be unable to commit your local changes (configuration, themes, plugins, etc.) if you choose this route.

I used my own subversion repository for my blogs and thus had to upgrade the old fashioned way with each release (although I prefer diff/patch over rm/cp). (I could have used vendor branches, but, clearly, I hadn’t thought about that at the time.) This was pretty much a royal pain in the ass, so I was glad when I could move WordPress into a separate directory with its 2.6 release.

This process consisted of removing everything except wp-content/, wp-config.php, .htaccess. (I also kept robots.txt, favicon.ico and some other personal files.) Then, I added the current WordPress release as an svn:external.

svn propset svn:externals 'wp-factory' .

.htaccess changes

In the WordPress codex, it is then suggested to copy index.php to the root dir and to change it to require wp-factory/wp-blog-header.php instead of ./wp-blog-header.php. I preferred adding some mod_rewrite voodoo of my own to .htaccess, so I did:

<IfModule mod_rewrite.c>
# This way I don't need directory indices
RewriteRule ^$ /wp-factory/index.php [L]
# This way WordPress can manage its own block without doing any harm
RewriteRule ^index.php /wp-factory/index.php [L]
# Allow easier access to /wp-factory/wp-admin/
RewriteRule ^wp-admin http://%{HTTP_HOST}/wp-factory/wp-admin/ [L,R=301]

The middle rule performs most of the magic. It redirects all the requests to /index.php to the factory default index.php. This means that I can let WordPress pretend that index.php does live in the root, so I don’t have to modify the rewrite rules that are managed by WordPress itself:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress 

wp-config.php changes

In Giving WordPress Its Own Directory in the WordPress Codex, it is suggested to change the “siteurl” and “home” options through the administration panel. In my case they would have to be changed to “” and“. I couldn’t do this because I override these with WP_SITEURL and WP_HOME in my wp-config.php. This is because I configured WordPress to support a development environment separate from the live production environment.

Ignoring the customizations for my development environment, these are the relevant settings in wp-config.php:

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

BTW: I really like it how WordPress disables the form controls for siteurl and home when you override these settings in wp-config.php. Kudos for that, devs!

Next time: git

In the end, this is all quite a bit of pain to compensate for what is essentially a version management problem. That’s why, on my newer projects, I’m now using git which makes forking and tracking an upstream repo absolutely trivial. 🙂

A few references


  1. Rowan Rodrik

    While installing a development environment for WordPress at YTEC, I noticed that there are some oddities when trying to use this method when the whole website has to site in a “subdirectory” (in this case http://localhost/~rowan/). I could get mostly everything working, except that after setting up pretty permalinks, some things would result in me getting a 404 produced by WordPress.

    /~rowan/2010/05/04/post-slug would go wrong, but when I added a prefix such as “blog” or “nieuws”, the post’s permalink would work. Pages would be successfully resolved (for example, /~rowan/trainers worked) but subpages (such as /~rowan/trainers/aissa wouldn’t).

    Anyway, I now have a decent development environment with its very own vhost just like I’m used to, so WordPress is happy again. 😉 I don’t think I even care enough about this bug to report it.

  2. Rowan Rodrik


    I now use the version name in the name of the external with a symbolic link called “wp” to the current version in use.

    svn propget svn:externals .


    Below I have the rsync stuff that I think you should use. Review it first; I didn’t test; it’s a combination of the Makefile and the Rakefile of two different WP installs (Aihato’s and this one).

    rsync --verbose --progress --recursive --delete --links - \
          --filter='merge ./rsync-upload-filters' \
          --filter='exclude /wp' \
          ${HOME}/ \

    # The same, but now the link is also synced
    rsync --verbose --progress --recursive --delete --links - \
          --filter='merge ./rsync-upload-filters' \
          ${HOME}/ \

    Dit is mijn rsync-upload-filters:

    exclude rsync-upload-filters
    exclude .svn/
    exclude /Makefile
    exclude /dump.sql
    protect /uploads/

    Als je de uploads gaat terugsyncen binnen je working copy of ze zelfs in de repo in wilt checken, moet je misschien ook nog “exclude /uploads/” toevoegen.

    Ok, that was weird. I only just noticed that I reversed back to Dutch.

    You’ll know how to do backups of the uploads. I just use the same rsync options with source and destination reversed. Database backups I do like this:

    MYSQLDUMP="mysqldump --port=3306 --host=halfgaar.db --user=dbuser --password=oimsovulnerable --routines --opt"
    ssh $SSH_CREDS "$MYSQLDUMP exampledb > $REMOTE_DIR/exampledb.mysql"
    scp $SSH_CREDS:$REMOTE_DIR/exampledb.mysql $LOCAL_DIR

    Hier is ook nog hoe ik de development db reload:

    mysql --user=root --password=osoinsecure -e 'DROP DATABASE example'
    mysql --user=root --password=osoinsecure -e 'CREATE DATABASE example'
    mysql --user=root --password=osoinsecure example < example.mysql

    Het is een beetje een hodgepodge van losse info, want voor ieder project regel ik alles anders en dat dan vaak binnen de working copy en dan heb ik op Butler ook nog een losse backup script om al mijn NFSN databases te backuppen. Ik zou er nog een heel verhaal over kunnen houden wat voor een puinzooi ik hier heb, maar ik houd het er maar bij om te zeggen dat als je nog iets nodig hebt of iets onduidelijk is, je het maar even moet laten weten.

    Ik zet trouwens de uploads directory altijd direct binnen de htdocs, dus niet binnen wp-content zoals WP zelf graag doet.

    Verder kun je de door WP gegenereerde .htaccess veilig negeren. Hier is de .htaccess van de Aihato site:

    php_value short_open_tag off
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^$ /wp/index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /wp/index.php [L]

    Dit is simpeler dan hoe ik het voor deze blog heb gedaan, waar ik extra voodoo nodig had omdat WP zijn eigen shit niet wilde verwijderen.

    PS: Van Sjura moet ik zeggen dat ik deze comment grotendeels op de pot heb geproduceerd. 😉

  3. Rowan Rodrik

    Another approach is to use funionfs, a union filesystem for FUSE. I got this idea from

© 2024 BigSmoke

Theme by Anders NorenUp ↑