WordPress local dev tips: DB & plugins

Running a WordPress site on your local machine is a great way to do development. I’ve taken advantage of this to do development while on flights (and yes, I realize that in about 5 years it’s going to seem positively quaint that there used to be flights without Internet access).

Today, I’d like to tackle two common issues when running a WordPress site locally:

  1. Handling differing database connection details
  2. Handling plugins that can’t or shouldn’t run on a localhost

My assumptions:

  • You have your site in a Git repository
  • You have a working LAMP/MAMP/WAMP/whatever setup.
  • You already know how to do a mysqldump and import that dump to your local machine

Database connection details

Your database user and password are (or should) be different on your localhost than they are on your production environment. One way to handle this is to have wp-config.php not be in version control and have everyone make their own. Boo. Stop taking things out of version control. Another solution I’ve seen is that people modify the wp-config.php and just try to be really careful not to commit their local changes. Again, boo.

Here’s how to do it. Open up your wp-config.php file.

if ( file_exists( dirname( __FILE__ ) . '/local-config.php' ) ) {
  include( dirname( __FILE__ ) . '/local-config.php' );
  define( 'WP_LOCAL_DEV', true ); // We'll talk about this later
} else {
  define( 'DB_NAME',     'production_db'       );
  define( 'DB_USER',     'production_user'     );
  define( 'DB_PASSWORD', 'production_password' );
  define( 'DB_HOST',     'production_db_host'  );
}

Ignore the WP_LOCAL_DEV define… I’ll explain that later.

Now open up your .gitignore file.

/dir-that-contains-wp-config/local-config.php

There. Now you can just create a local-config.php file and put your DB_* defines in there. And thanks to the .gitignore addition, you won’t have to worry about accidentally committing your local config.

But what if you want to override other defines locally? Well, just modify them in wp-config.php like so:

if ( !defined( 'SCRIPT_DEBUG' ) )
  define( 'SCRIPT_DEBUG', false );

And make sure they’re after the local-config.php block. Now you can override these defines as well.

Plugins that are production-only

I’m a big fan of VaultPress from Automattic. But this is not something you want to run on your localhost. Other backup plugins probably fall into this category of “production-only.”

The bad way to do this is to remember to disable this plugin after you import a DB snapshot to your localhost. Boo. Remember WP_LOCAL_DEV that we set earlier? Let’s use it.

I’ve written a quick “must-use” plugin to handle conditional plugin disabling. Get it here, and put it in the mu-plugins directory (create it in your WP content directory if you don’t already have one).

The part you modify is at the bottom:

new CWS_Disable_Plugins_When_Local_Dev( array( 'vaultpress.php' ) );

I’ve jump-started you by putting vaultpress.php in there. Add plugin filenames to that array as necessary. Don’t forget that most plugins are in a subdirectory, so they’ll be in the form plugin-name/plugin-name.php.

Now those plugins won’t be active when doing local dev (based on the presence of local-config.php).

70 thoughts on “WordPress local dev tips: DB & plugins

  1. I used to do it in very similar way, but for one site I needed a staging environment and then I switched to host-based guessing:

    $config2hosts = array(
    	'production' => array( 'example.org', '/20\d\d.example.org/',),
    	'staging' => array( 'test.example.org', ),
    	'local' => array( '/.local/', ),
    );
    

    And then I have production-config.php, staging-config.php and local-config.php, of which only the local one is out of the version control.

    I know it’s a little bit more complicated, but this way I wanted to keep the staging environment in the version control and couldn’t determine where I am just based on existence of a config file.

    I like the plugin disable plugin and I think it deserves a repository, not only a gist.

  2. This is pretty sweet. I do pretty much the same thing, generally in the form of:

    define( 'SANDBOX', file_exists( dirname( __FILE__ ) . '/local-config.php' ) );
    

    Then I use SANDBOX conditionally in configuration, testing (basically as if it’s an #ifdef block), a mu-plugin that handles some extra things, etc.

    The plugin you wrote, now that’s pretty awesome.

  3. For a while I’ve been using a switch on $_SERVER['HTTP_HOST'] in wp-config.php. I can’t think of the downsides to it, but seeing this different method touted by much more experienced developers makes me wonder. Are there any gotchas to switching on the hostname?

    1. Depending on how you coded it, it could be potential security issue, if your web server responds to arbitrary host names. But if you do it in a switch, so that the filenames you might be including are effectively hardcoded, that should be fine. That’s similar to what Nikolay is doing above, except he’s also supporting regular expression matches.

    2. Thanks Mark. I’m not using file includes, just different constant definitions in each case. One case for each dev server (e.g. local, dev and/or staging), then a default for the live server.

      What might happen if the server responds to arbitrary host names?

    3. Mark, sorry to come back to this, just want to clarify. I use a switch on $_SERVER['HTTP_HOST'] in wp-config.php, but I’m basically just defining the constants with different values in each case (I’m not including any files). I’m assuming the vulnerability you’re talking about would only arise if I was passing $_SERVER['HTTP_HOST'] through to an include or require statement? Or am I missing an aspect of include vulnerabilities here?

    4. Mark, I’m about to do a talk at WordCamp UK, and I’ll be discussing this kind of technique there. I’d love to get word from you on my previous query (comment from June 30th) so I can be sure about the security (or otherwise) of the technique I mentioned. Thanks!

  4. I like that you have git listed up there as your first assumption.

    It bums me out that there are only 3 forks of WP on github. Well, 4 now, I just forked it myself. But still, there ought to be a lot more!

  5. Curious as to why you don’t just skip the code addition and ignore the file as you end up doing anyways.

    what is the benefit of the extra work?

    thanks mark,

    1. n/m. just read up on git (svn guy thus far).

      thanks for the tip mark.

      good looking boy you’ve got there too 😉

  6. I’ve got my production wp-config up a level on my ftp and dev, while I keep my local wp-config in its default location. On the local it finds the first copy and ignores the production. This works out for me as I can still edit both without conflict (as long as I don’t upload the local config accidentally).

    1. The downside of completely separate files is that you can’t test changes to wp-config.php (a syntax error in the production one would take down the whole site).

  7. I have used svn for years and am just getting started with git. Can you recommend a good explanation of how to setup git, so that it tracks one of the official (svn) repositories for WP itself as well as the standard plugins, which I shouldn’t be editing, while giving me a local editable version of my own themes and plugins on the development site with a way to roll them out to the production one. I’m sure there must be a pretty standard way to set all that up, but I can’t find a good explanation. p.s., Good meeting you at WordCamp Phoenix and I was impressed with the professionalism and enthusiasm of all the core team, too.

    1. I’m probably going to turn this comment into a followup blog post. 🙂

      I’m personally not a fan of using SVN externals or Git submodules. Here’s what I have on my personal blog:

      /wp-config.php
      /blog-content/mu-plugins/
      /blog-content/plugins/
      /blog-content/themes/
      /blog-content/uploads/
      /wordpress/
      

      WordPress has three cool, but seldom-used features:

      • WordPress can be installed in a subdirectory.
      wp-config.php can exist “a level up” from the WordPress directory.
      • The WordPress content directory (normally wp-content) can be moved, so that it does not exist within the WordPress directory.

      Put these three things together, and you can create a setup with a completely-unmodified WordPress directory, as I have done above.

      When I want to upgrade WordPress, I do:

      rm -rf wordpress
      svn export http:// core.svn.wordpress.org/trunk/ wordpress
      git add --all wordpress
      git commit -m 'Upgrade WordPress' wordpress
      git push origin master
      

      (The space between http:// and the rest of the URL shouldn’t be there… WordPress.com is trying to autolink the URL and mangling it)

      And then on the server, I do a deploy (using Capistrano). Note that the --all switch for git tells it to remove files that have gone away. That’s why I delete the wordpress directory first.

      There is one downside to this setup. Well, two. First, setting it up is more complicated. Of particular annoyance is how you have to specify your uploads directory using a relative path (relative from wp-content). And you have to set some defines in wp-config.php (more info). Second, some plugins won’t work. If they hardcode wp-content anywhere in their code, they won’t work. This is becoming less and less of a problem, but you still run in to it occasionally.

      As an alternative, you can set it up like this:

      /wp-config.php
      /wordpress/
      

      In this setup, everything mutable lives in /wordpress/wp-content/, like normal. When you want to upgrade WordPress, do:

      rm -rf wordpress/wp-admin/ wordpress/wp-includes/
      svn export --force http:// core.svn.wordpress.org/trunk/ wordpress
      git add --all wordpress
      git commit -m 'Upgrade WordPress' wordpress
      git push origin master
      

      (The space between http:// and the rest of the URL shouldn’t be there… WordPress.com is trying to autolink the URL and mangling it)

      I specifically delete the wp-admin and wp-includes folders to pick up any deleted files in there (root files don’t often get deleted) when I use git‘s --all switch.

      Okay, this comment has gone on for far too long. I’ll turn it into a post. Coming up: how to handle plugin upgrades (hint: not manually).

    2. I set mine up just like you said with /wordpress, /blog-content (not always called this), wp-config.php, the optional local-config.php, and index.php all in the root directory. Then I actually have a bash script that updates wp:

      #!/bin/bash
      
      svnlocation=${1:-'trunk'}
      
      rm -rf wordpress
      svn export http://core.svn.wordpress.org/${svnlocation}/ wordpress
      git add --all wordpress
      git commit -m "Upgrade WordPress to ${svnlocation}" wordpress
      git push origin master
      

      You can call it like this:
      $ ./updatewp.sh
      $ ./updatewp.sh branches/3.2
      $ ./updatewp.sh tags/3.2.1
      The first updates to trunk (default), the others update to the specified branch or tag.

    3. I can’t believe after reading the comment above I still left the link in my code (Doh!). Anyway, that line should look like this without the space:

      svn export http:// core.svn.wordpress.org/${svnlocation}/ wordpress
      
  8. Boo. Stop taking things out of version control

    And thanks to the .gitignore addition, you won’t have to worry about accidentally committing your local config.

    🙂

    One thing I’ve done in the past is crazily parse/eval wp-config-sample.php as my “local-config.php” since that file is always in the repo. Plus, having a local DB with the sample connection details makes spinning up new sites really easy.

    define( 'WP_DEBUG', true );
    array_map( function( $line ) { @eval( $line ); }, preg_grep( '/^define\\s*\\(/m', file( 'path/wp-config-sample.php' ) ) );
    
    1. If you want your local config to be portable, you could check it in, and then use the presence of a .localdev file (git ignored, of course) to trigger its use.

      Of course, that forces everyone who develops on the site to use the same host/db/user/pass on their local machines. I have sites where I’m not the only contractor, and I don’t want to presume to dictate other people’s local setups.

      eval()? You’re a madman. A MADMAN, I say.

  9. Cool to see what other people are doing here. I used the switch/include method when I was working on Magento a lot, since their configs are stored in xml files instead of php files.

    We use environment variables for credentials like this, including things like Amazon S3 keys. That way the config can move from environment to environment and get committed to version control with impunity. It also keeps the credentials out of the hands of 3rd parties — contractors — when we have other people work on our code.

    Example:

    // Set db connection based on the environment
    define('DB_NAME', $_SERVER['DB_NAME']);    // The name of the database
    define('DB_USER', $_SERVER['DB_USER']);     // Your MySQL username
    define('DB_PASSWORD', $_SERVER['DB_PASSWORD']); // ...and password
    define('DB_HOST', $_SERVER['DB_HOST']);    // 99% chance you won't need to change this value
    

    I have a draft on my blog where I talk more about it, perhaps you’ve just inspired me to finish it up. Another thing we do is, when on a development environment we filter all the content to replace urls dynamically. This changes the production URLs to development URLs (e.g., http://www.example.com -> local.example.com). This makes it easy to pull a database snapshot from the live site, load it into a dev environment, and just have it work. It’s been great to use.

    1. I’ve largely transitioned to using the primary domain, and using my hosts file to point it to my localhost.

      Be careful with environment variables… phpinfo() would expose that info, right?

    2. Yeah, “disable_functions = phpinfo” in php.ini is pretty much required for storing credentials as environment variables, I really should have mentioned that.

      I used a hosts file entry for a while, I’ve given myself so many near-heartattacks that I exclusively use a local domain these days. (Exception being when doing something like migrating a server.)

    3. One way to mitigate heart attacks is to have the site print a “You are using the site locally” banner in the WordPress admin when the site is being hosted locally.

  10. Interesting, I use a different way :

    – wp/application/ <- wordpress with wp-config.php for local
    – wp/config/environments.yaml <- a filter file with settings for 7 environments
    – wp/config/environments/files/wp-config.php (and other files) <- files with markup like : ${path_upload}, ${cache}, ${db…

    Then, when I deploy it, via capistrano (ex: cap deploy dev) then this copy to a temporary folder, replaces the files (wp-config.php…) with parameters from the dev environment that is in the yaml file. 🙂

  11. Sweet Mark, thanks. I like the plugin you created a lot, I certainly can find other use for it besides for switching between local and production enviroments. 🙂

  12. Why would you include wp-admin and wp-content under version control?

    I just version the theme and plugins independently and I get a lot less headaches this way.

    1. Sometimes I have to make WordPress core modifications (only when there is no other way) temporarily. And I dislike that externals can be slow and rely on yet another server which could go down.

  13. Great post and Tips for developing locally 2 things to add, 1 of which I haven’t done, but maybe you have.

    1. WP has a check in the background the tries to connect to the internet (I’m assuming an auto update check), but if your not connected to the internet you get an error. I’ve been meaning to add check where if I’m not connected to the internet don’t bother trying.

    2. Have you used WP+Networking+Domain mapping for local development? I’ve been using it, deploying sites and loving it.

    1. oh sorry one more tip…I also sym link my wp-config.php file into my theme folder (since I want to jump in it, sometimes) I just add that to my .gitignore file as well.

  14. Stupid newbie question…

    Can anyone point to a good tute that covers mysqldumps? Been looking but can’t find a good one.

  15. Thanks Mark! These tips are really helping in my 100th iteration of local development.

    I absolutely love the flow you have set up for upgrading wordpress … I’ve been really struggling with how to implement both html5 boilerplate and wordpress into my base startup projects, while maintaining the ability to easily diff and update boilerplate, and upgrade wordpress … I’m drooling at the idea of being able to use your method, BUT, my local dev is multisite with subdomains, so, there is no wp in a directory option.

    Might you have any slick moves that would assist in this particular setup??? 😉

    Currently I’ve just ended up setting up a remote tracking branch for both and pulling them in, hoping in the future to be able to just do a straight fetch merge on core wordpress and a fetch diff merge on boilerplate to accept or reject changes as they apply to my customized version.

    Sad setup I know, but, after running around the office like a crazed monkey for the past month … getting deeper and deeper into the rabbit hole …

  16. Thanks! I just got this up and running and finally can auto-disable W3TC on my local install. This is a huge help to making my local workflow smoother.

    It would make more sense to me however to define the plugins one wants to disable in the local-config.php file rather than modifying the plugin for each plugin one wants to disable. This way the plugin would be the same on every site and I’d just have to tweak local-config.php. Is that scenario even possible?

  17. Thanks for the awesome article. It helped me out a lot. This has been a lot more work than what I thought it was going to be, but it has been worth learning every bit of it. I bookmarked your site and I am going to share it with others people I know that want to run host their own site or would like to learn more.

    Thanks,
    Ricky

  18. Benefit’s Love Your Look makeup to match your lifestyle collection takes the guesswork out of gorgeous. Choose from three unique looks: The Lana light neutral shades, The Gabbi medium neutral shades, and The Betty deep neutral shades. Go for a “lifestyle look” or, be daring and mix and match! All of the beautiful shades for lips, eyes, and cheeks will get you the look you want! You’ll love the hidden surprise, a “beauty fortune” inside every package.

  19. Pingback: Quora
  20. For my reference: can anyone explain why using “option_active_plugins” in the functions.php file with a simple/similar callback function to the mu-plugin above does not work. However when run from the mu-plugins folder it works perfectly?

    1. Because plugins are loaded BEFORE the functions.php file is loaded, which means your code isn’t being processed until after that option has already been retrieved and used. MU plugins are loaded BEFORE regular plugins. Rough order for you up through ‘init’ action:
      * MU Plugins loaded
      * Constants (cookie and SSL) set up
      * Plugins loaded
      * Pluggable files loaded
      * Global query set up
      * Rewrites set up
      * Locale loaded
      * Theme set up (functions.php included)
      * WP initialized
      * Fire ‘init’ action

  21. This is all great and I use something similar for a local -> staging -> production environment. All php require a local wp-config-local.php with environment specific info.

    What’s got me stymied though, is how to keep the content in the DB’s in sync- I periodically dump from production down to the other environments, but it’s a chore. It would be great to be able to stage changes for client review/approval on the staging server (including perhaps DB & content changes) then turnkey push said updates to production.

    A similar question goes unanswered on stackoverflow:
    http://stackoverflow.com/questions/9406237/what-is-the-dummys-way-to-work-on-wordpress-locally-w-versioning-and-migrate

    this claims to have a solution, but this methodology would just nuke any data on the production environment, not keep them synced:
    http://wp.tutsplus.com/tutorials/how-to-sync-a-local-remote-wordpress-blog-using-version-control/

  22. site and I am going to share it with others people I know that want to run host their own site or would like to learn more.

Comments are closed.