In which I explain how I roll my code

The Ultimate Guide to Local WordPress Development on a Mac

It’s a bold title, I know. But I have been working to achieve this for about five years now and am finally very satisfied with my configuration. If you’re looking to work on WordPress Themes or Plugins on your local machine, manage your code base with GitHub, and effortlessly deploy updates, you’ve come to the right place.

Local Setup

The first thing you need to do is upgrade to (at least) Mac OS X Sierra 10.12.2 or you’re going to have problems left and right. You should already have done that, though, because you’re a developer and developers don’t like to live with stale operating systems.

After that you need to follow Chris Mallinson’s excellent tutorial, The Perfect Web Development Environment for Your New Mac. (I’m not going to rehash his whole article here because he did such a great job.) You can skip the bit at the end about implementing a custom home page if you want. I didn’t do that and it’s not really important if you already have your own method of navigating your own file system. I use Coda Nova as my IDE and it handles organizing my projects.

I have been screwing with my httpd.conf files for years.

The only tricky part of Chris’ instructions is getting your httpd.conf files right. You may get lucky and have everything work perfectly just by following his rules. I was not lucky and had to make several changes, but that’s probably because I have been screwing with my httpd.conf files for years. If you contact me I’m happy to send you my copies of httpd.conf and httpd-vhosts.conf and they’ll probably work on your machine. I keep an archive of them in my iCloud Drive folder and use the exact same configuration on multiple machines — two Mac Mini desktops and my MacBook Air — with no issues.

After you’ve installed MySQL locally you need to make sure you have the ability to actually use it. is your friend:

/usr/local/mysql/bin/mysqladmin -u root -p'temppassword' password 'newpassword'

Now you can create a database to use for WordPress. I always name mine “wordpress” but you can name it anything really.

In that code the temppassword is what the MySQL install gave you for root. It’s probably annoyingly complex enough to just use that same one again for the newpassword, but you can change it if you want.

Once you’ve gotten that working you’re ready to start really cooking. Download a copy of WordPress, unzip it, and drop it into ~/Sites/wordpress/ first. You’re going to run into some permission issues probably, but you can handle them with:

sudo chown -R _www ~/Sites/wordpress ; sudo chmod -R g+w ~/Sites/wordpress

And you’ll need to make sure you can install Plugins locally by adding this line to your wp-config.php script:

define('FS_METHOD', 'direct');

WordPressAt this point you should be able to hit wordpress.test in your browser and start the WordPress installation process. I know it’s tempting to use localhost as your database host, but you should stick with The database name is going to be the database you added to MySQL a few minutes ago.

GitHub Setup

Okay. So now you have WordPress running locally and you can edit Themes and Plugins without worrying about taking down a production website while you hack. Now it’s time to get serious. Do you have GitHub Desktop yet? If not, grab it and install it. (If you’re some crazy person that does everything via CLI, I have no idea why you’re even reading this since you probably know how to do it all already.)

Let’s work on Theme development first. Create a new GitHub repository and initialize it with a README. I’ll assume you named your repo “Skywalker” because you’re an awesome person and that’s what awesome people do. (Whether you make it public or private is up to you.) Click the big green Clone or download button and choose “Open in Desktop”. You’re going to save Skywalker in ~/Sites/wordpress/wp-content/themes/skywalker and it should only take a zillionth of a second because it’s an empty repo right now.

Okay. Edit your theme. You don’t have to do anything dramatic; just drop a style.css file in there and add a title to get started and save it. Switch to your GitHub Desktop app and it should show that a file has been changed and is ready to commit. But don’t commit yet. We’re just getting to the cool part.

Before you do anything else, SSH into your web server. If you can’t SSH into your web server, you’re out of luck and can’t really continue. You should be able to SSH into your web server, though. If you can’t, tell them you need shell access or else you’re going to switch to a different host! (I recommend DreamHost.) Once you’ve SSHed to your server, make sure it has git installed by running:

which git

If your web server doesn’t have git running, guess what? You need a better web host.

Good to go? Okay … Moving right along.

Download Marko Marković’s Simple PHP Git deploy script and update its config script.

Vault Boy Hacker

  • You can get a value for the SECRET_ACCESS_TOKEN from the handy Random Key Generator. I know you want to use one of the Strong or Fort Knox passwords, but this key needs to be used as a querystring variable, so save yourself a headache and just use one of the CodeIgniter Encryption Keys instead. If you’re working on a WordPress theme for the government or a bank, jump through the hoops to make it Fort Knox safe. Otherwise I wouldn’t be too worried about someone hacking the local pizza place’s website by cracking the code here.
  • Update the REMOTE_REPOSITORY to your own. It’s going to look like
  • For now leave the BRANCH set to master. If you’re rolling your eyes here and know that you want to use a different branch, go nuts. This is supposed to be a tutorial for beginners.
  • Your TARGET_DIR is going to depend on your web host, so I can’t give you much direction here. If you use DreamHost and have WordPress running in the root of your domain, it will probably look like this: /home/DREAMHOST_USERNAME/

You’ll need to FTP – or, better, sFTP – these two files to your web server somewhere. It doesn’t matter where, as long as they’re accessible to the outside world.

You also need the contents of your public SSH key (usually from your web server. SSH into your server and run this to generate a key if you don’t have a way to get one from your web host:

ssh-keygen -t rsa

Doing that will likely drop into a /.ssh/ folder. (You can use vi to get to it. You just need to copy its contents.)

While you’re still SSHed into your web server, try to SSH from it to GitHub to make sure you can. You need to do this to “authorize” your web server to connect to GitHub, too. Run this command:


You should get a response saying something along the lines of, “That worked but you can’t do it,” which is confusing but okay. If it doesn’t work, you’re going to need to talk to someone at your web host and / or at GitHub to figure out why your server can’t talk to GitHub. I’ll assume it works so you can move to the next step.

Go back to GitHub, click on your repository’s Settings tab to get to the Deploy keys panel. You’re going to add the contents of the file you just created as a deploy key. (You can name it whatever you want, as far as I can tell the name doesn’t matter.) One snafu here, though: You can only use a single deploy key on a single repository. So if you want to work on two separate themes on the same server, you need to either generate a new user on your server for each one and then generate a new SSH key for each user or you need to drop your entire wp-content directory into the repo. (That’s what I did, but I don’t recommend it because then you’re going to have to deal with git-ignoring a ton of files, since all your WordPress media uploads and any other Plugins are in that folder, too.)

Switch to the Webhooks panel and add a new webhook.


  • Your “Payload URL” is the full path to the deploy.php script you uploaded, along with a querystring containing the SECRET_ACCESS_TOKEN you added to the deploy-config.php script. Just append ?sat=SECRET_ACCESS_TOKEN to the file.
  • The “Content type” should be application/json
  • I have my webhooks set to “Send me everything”, although you can limit it to just the push event if you want to minimize how frequently it gets pinged.
  • Yes, you want to make it active.

When you are done and click the green “Add webhook” button, GitHub will send a test ping to your web server’s deploy.php script and tell you the results right there.

Wrapping Up

If everything has gone smoothly to this point, you should be able to switch back to your GitHub Desktop app and run a commit. (I have mine set to always commit and sync. Under Edit check the “Automatically sync after committing” option.) When you commit, your files are pushed to GitHub which triggers the webhook which calls the deploy script which pulls your files from your repo and pushes them to your web server and BOOM!, now your new Skywalker theme is in production. How cool is that?!

Bonus Points

You can have different webhooks for different branches of your repository. So let’s say you have three branches: dev, beta, and master. On your web server you would then have deploy-dev.php, deploy-beta.php, and deploy.php, each with its own unique SECRET_ACCESS_TOKEN and each hitting its own unique config file with a different TARGET_DIR updating the appropriate folder. Since you can selectively assign permissions to branches — assuming you have an “Organization” GitHub account — that means you could have some developer working on the dev branch and only allowed to push to your dev folder. Then you make your dev folder serve up your dev subdomain and now you don’t have to worry about a developer accidentally pushing something into production.

If you use Slack you can configure a different webhook to send a message to a Slack #channel any time someone commits, which is handy if you’re working on a big project.


I cannot promise that this will work perfectly in all situations, but here are the contents of my httpd-vhosts.conf file. I’ve stripped the comments. (Updated April 23, 2018)

<Directory "/Users/YOUR_USERNAME/Sites">
  Options Indexes MultiViews FollowSymLinks
  AllowOverride All
  Order allow,deny
  Allow from all

<Virtualhost *:80>
  VirtualDocumentRoot "/Users/YOUR_USERNAME/Sites"
  ServerName home.test
  UseCanonicalName Off

<Virtualhost *:80>
  VirtualDocumentRoot "/Users/YOUR_USERNAME/Sites/%1"
  ServerName sites.test
  ServerAlias *.test
  UseCanonicalName Off

<Virtualhost *:80>
  VirtualDocumentRoot "/www/sites/%-7+"
  ServerName xip
  ServerAlias *
  UseCanonicalName Off



Post the first comment:

I'll never share your email address and it won't be published.

What Is This? is the personal weblog of me, David Vincent Gagne. I've been publishing here since 1999, which makes this one of the oldest continuously-updated websites on the Internet.

A few years ago I was trying to determine what cocktails I could make with the alcohol I had at home. I searched the App Store but couldn't find an app that would let me do that, so I built one.


You can read dozens of essays and articles and find hundreds of links to other sites with stories and information about Ernest Hemingway in The Hemingway Collection.