This guide covers configuring MacOS High Sierra, Mojave, and beyond as a local development server for LAMP-stack projects. This is a high-performance option for running projects locally on a Mac and is useful for PHP and WordPress development.
At the end of this guide, you will be able to create new folders within a special virtual hosts directory such as ‘example.com/’ with the sub-folder ‘public_html/’ and the ‘http://example.com.test’ will automatically be available with apache serving files out of the ‘public_html/’ folder.
Apple already bundles many of the necessary pieces with MacOS including apache and PHP. This guide covers configuring them to support WordPress, as well as installing other dependencies such as mysql/mariadb, and useful tools like sequel-pro and wp-cli.
Self-contained solutions for running a LAMP environment on a Mac can be found in products like MAMP and the free XAMPP. They double up what’s already on your Mac but do have some features that you might find appealing.
Virtual Machines are another option. Tools such as Vagrant are great and extremely useful for more complex projects where customized server configuration(s) are required. However, spinning up an entire virtual server with its own CPU, RAM and disk allocation simply to run WordPress is a huge tax on system resources. It’s often a better idea to reserve VM tools for better-suited applications.
A lighter-weight alternative to full VM’s is container virtualization such as with Docker. This brings its own configuration and deployment challenges that may introduce unnecessary effort if Docker doesn’t play a greater role in a given project.
Confirm your system’s apache version with the command
apachectl -v, and its PHP version with the command
MacOS Sierra and beyond should be running apache 2.4+ and PHP 7+.
Apple’s default configuration requires several changes to create a suitable development environment.
First, make a backup copy of apache’s
sudo cp /etc/apache2/httpd.conf /etc/apache2/httpd.conf.pre-dev-env
Apache httpd.conf configuration
httpd.conf using a text editor such as nano:
sudo nano /etc/apache2/httpd.conf
Each of the following lines are found at different locations within the file. Find each one of them and ensure they are uncommented — delete any preceding
# character so they start with a letter instead:
#LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so #LoadModule rewrite_module libexec/apache2/mod_rewrite.so #LoadModule php7_module libexec/apache2/libphp7.so #Include /private/etc/apache2/extra/httpd-vhosts.conf
If you’re using the nano editor, its “where”
Control+W feature can help you locate each line. It works similar to the “find” command (
Control+F) found in most web browsers and word processors.
When you’re done, use
Control+O to save (output) your changes and
Control+X to exit nano.
The above changes have:
- enabled the module
mod_vhost_aliasto enable dynamic virtual hosts;
- enabled the module
mod_rewritewhich WordPress uses to rewrite URL paths;
- enabled PHP7 support so WordPress’ php scripts can execute; and
- included an extra conf file where we will define a dynamic apache virtualhost for your projects.
Apache dynamic virtual host configuration
In the previous step we referenced a
httpd-vhosts.conf file in
Create a backup of the original file:
sudo cp /private/etc/apache2/extra/httpd-vhosts.conf /private/etc/apache2/extra/httpd-vhosts.conf.pre-dev-env
Now edit that file:
sudo nano /private/etc/apache2/extra/httpd-vhosts.conf
This file is populated by default with a couple of example virtual host definitions: blocks beginning with
<VirtualHost *:80> and ending with
Comment out (i.e. prefix each line with
#) or delete the two example virtual host definitions.
Next, add your own dynamic virtual host definition to the file.
I’ve decided to use
/usr/local/var/www as the base folder to serve projects from. You can modify this to another path if you’d like.
Add the following block:
# Custom configuration for local dev environment <Directory "/usr/local/var/www"> Options Indexes MultiViews FollowSymLinks AllowOverride All Require all granted </Directory> <Virtualhost _default_:80> # Dynamic virtual hosts for local development UseCanonicalName Off VirtualDocumentRoot "/usr/local/var/www/%-2+/public_html" # Show 404's in the apache error log LogLevel info </Virtualhost>
Control+O) and exit (
Control+X) the nano editor.
Explaining the Dynamic Virtual Host definition
httpd-vhosts.conf as modified above, the
<Directory> block is required for Apache 2.4+ and tells Apache that it can serve files from the
/usr/local/var/www folder. This explicitly overrides the rigorous security-focused default specified in
httpd.conf that tells Apache it can’t serve up any file on the hard disk (
<VirtualHost> block defines a
_default_ VirtualHost that listens on port 80.
VirtualDocumentRoot directive tells Apache where to look for the dynamic VirtualHost’s files. The
%-2+ placeholder stands in for “the penultimate (second to last) and all preceding parts” of the server name in the request. Therefore, per the directive, when handling a request for http://example.com.test, Apache will look for that site’s files in /usr/local/var/www/example.com/public_html.
I like to have my project folder names in dev environments mirror my likely production values, such as example.com/ or hypothetical.app/. You may wish to name your project folders differently. You may prefer to use the
%0 placeholder for the entire server name (example.com.test), or
%1 for the first-part only (example).
To learn more about these directives, refer to the docs:
Create the required paths
Finally, ensure the paths that you specified for your project folders exist. In my case:
mkdir -pv /usr/local/var/www
If you have any project build folders, you can add them (or better yet: create symlinks to them) here!
To give you an idea of how this works:
/usr/local/var/www if you were to create the folder
helloworld.com/ and sub-folder
helloworld.com/public_html, and put a basic index.php containing
hello world! inside it, at the end of this tutorial Apache will serve it up when you visit http://helloworld.com.test on your machine.
Smoke-check (config check)
As a smoke-check to make sure you haven’t made any typos or introduced any bugs, run the following command to check the syntax of your apache config files:
sudo apachectl configtest
If you followed all of the above steps carefully, it should output “Syntax OK”.
If everything checks out, restart apache so your latest conf changes can take effect:
sudo apachectl restart
Configuring DNS for local domain names
Now we will solve the problem of directing local requests for http://example.com.test to Apache.
One option is to manually add an entry to
/private/etc/hosts for every one of your projects. For example, you might add the line
127.0.0.1 myproject.test to enable the local URL ‘http://myproject.test’.
We are going to use a more dynamic approach using a DNS server so that all requests for any URL at the
*.test domain will resolve to localhost.
Any DNS server could be employed for this job. We will use dnsmasq, a perfect choice for this type of lightweight local routing.
Follow the steps in my guide Using dnsmasq on MacOS to setup a local domain for development to complete this step.
Customizing PHP settings (php.ini)
MacOS’ PHP uses a default
php.ini file based on
To customize your PHP environment, if a
php.ini file doesn’t already exist at
/private/etc/php.ini, copy the default template to create a main
sudo cp /private/etc/php.ini.default /private/etc/php.ini
Make any changes you wish to
php.ini and restart apache to reload all configuration files:
sudo apachectl restart
If you were to run
phpinfo() in a php file from the web server, you should now see that the Loaded Configuration File property now has the value
A very common tweak to the default PHP configuration is to allow larger file upload sizes. The
upload_max_filesize properties are only a few megs by default. These limits can be raised as you see fit.
Many developers also tweak the
memory_limit settings depending on their project.
Always remember to restart apache after making changes to your PHP configuration.
Installing and configuring mysql server
WordPress and many other PHP apps require a MySQL or compatible database such as MariaDB to store its data.
I prefer to use mariadb, which is available via
brew. Run the following command to install it for your user:
brew install mariadb
This installation method is recommended in the official docs:
Following installation, manually start the server by running:
Running mysql as a service
By default you will need to manually start and stop your mysql/mariadb server.
If you want the server to start when you login to your Mac, use Homebrew’s services feature to have it automatically launch in the background:
brew services start mariadb
Homebrew integrates with MacOS’ launchctl to make this happen the correct way as supported by Apple.
Test logging into mysql via the cli client
Once your server has started, you can test logging in as the root user with the Terminal command:
mysql -u root
quit command to exit the MySQL prompt and return to your Terminal session.
Set a root password and improve security
Even though a default mysql/mariadb configuration only accepts connections from localhost, it is still wise to run the quick interactive
mysql_secure_installation command-line-interface script to set a root password and a few other security-minded options.
Troubleshooting: resolve possible socket incompatibility
There may be a slight configuration mismatch between the defaults of brew’s mysql/mariadb packages and the defaults of MacOS’ built-in PHP mysqli extension. It may be fixed in newer packages.
If try to get a LAMP app such as WordPress to connect to mysql on
localhost and encounter a “Connection Refused” error even when the database settings are confirmed correct (as defined in
wp-config.php for WordPress), then you may need to make a small fix. You can confirm the issue by trying to connect to
127.0.0.1 instead: if it works but
localhost doesn’t then it is a socket-related issue.
For localhost (socket) connections to the database, brew’s package version tends to use the socket file
/tmp/mysql.sock. MacOS’ built-in Apache+PHP mysqli extension assumes the socket file is at
/var/mysql/mysql.sock (i.e. the default setting of its
mysqli.default_socket property). You can verify the value of this property by running
phpinfo(); in a php script and finding it in the list.
To resolve, one could edit
php.ini or another conf file. A simple solution is to create a symlink at
/private/var/mysql/mysql.sock that points to
/tmp/mysql.sock so everything works out:
sudo mkdir -p /var/mysql sudo ln -s /tmp/mysql.sock /private/var/mysql/mysql.sock
/var, a classic folder for unix/linux systems, is symlinked to
/private/var so to avoid a symlink-on-symlink type situation, the above uses the real path.
Note that only socket connections (i.e. database connections made to ‘localhost’) would be impacted by this incompatibility. Connections to ‘127.0.0.1’ force connecting over TCP due to the IP address, bypassing any socket concerns.
Install a database management GUI
If you’d like to use a GUI to help manage your mysql databases, one popular choice is
sequel-pro. This serves the same purpose as the popular phpmyadmin webapp except it runs as a desktop app on your Mac. To install it:
brew cask install sequel-pro
You can now launch it from your Applications folder.
WordPress’ Command-Line-Tools are a huge timesaver for managing both local and remote WordPress installs, and they are essential for writing scripts that automate WordPress deployment, updates, etc.
Install wp-cli for your user with the command:
brew install wp-cli
Confirm your install by running
wp --version in Terminal.
Assuming your project folder includes a
public_html/ folder that contains your WordPress install, and that you have created a database called “DB_NAME”, you can download and setup WordPress with a handful of wp-cli commands:
cd /Users/username/web-projects/my-project.com/public_html wp core download wp config create --dbname=DB_NAME --dbuser=DB_USERNAME --dbpass=DB_PASSWORD --dbhost=localhost wp core install --url=my-project.com --title="My Project" --admin_name="example_admin" --admin_password="example_password" --firstname.lastname@example.org
Check out the command reference and docs/handbook at https://wp-cli.org/
Serving up your project
To make a project available at http://example.com.test you need to:
- create a folder
example.com/in your chosen base directory (e.g.
- create the
- copy project files to
public_html/(manually or as an automated task e.g. with gulp)
- create a symlink from your project location to the chosen base directory
For the symlink option:
ln -s ~/some/project/folder/example.com /usr/local/var/www/example.com
Be sure to replicate the requirement for a
When creating symlinks from elsewhere on your system to
/usr/local/var/www be careful of permissions issues!
MacOS’ Apache is running as the user
_www by default. That user must have at least read permissions for any folder that you symlink to in order to serve up your project files.
It’s important to note that
~/Documents and any folders created within are not accessible to any group or other users. Apple thankfully assumes that you don’t want other users on a system to be able to access your private documents! As a result, Apache’s
_www user will not be able to follow symlinks into the Documents folder belonging to your user.
If you have any web projects in
~/Documents you could save them somewhere else where Apache can read them and symlink to them from there, or you could simply copy your project files over to
Depending on your desired setup, you may find it convenient to change the group ownership of
_www and set the groupID bit (setgid):
sudo chgrp _www /usr/local/var/www sudo chmod g+s /usr/local/var/www
The setgid bit ensures that any new files or folders created underneath
www/ will inherit the group ID of the directory vs. the default behaviour of having it set to the primary group ID of the user that created the file. This measure can help ensure that Apache’s
_www user can access the contents of the
Finally, remember that our particular Apache VirtualHost configuration requires a
public_html/ sub-directory under the
my-project.com folder for web-servable files. To illustrate: an index.php file in
~/web-projects/my-project.com/public_html will be served up to a local web browser that requests the URL http://my-project.com.test. This works because there is a symlink in
my-project.com that points to the folder
Working on a project
In the future, if you haven’t setup apache and mysql server to run automatically as services, you will need to start them:
sudo apachectl start mysql.server start
To stop or restart apache, use the following commands:
sudo apachectl stop sudo apachectl restart
You can stop mysql with:
Remember: if you make any changes to your apache config, such as adding a new VirtualHost, you will need to restart or reload apache for your changes to take effect.
Finally remember that the settings covered in this guide will serve up your projects to any clients on your network. If you want to hide them from everyone on your local coffee shop’s shared wifi, turn off the servers, harden your firewall (via System Preferences), and/or tweak your Apache conf to only serve to localhost.
When you upgrade MacOS, the installer will create versions of your conf files with the suffix ~previous before replacing with its own.
You can also create your own backup copies to be safe.
Following an OS update, if the Apache files are changed, you can compare the new versions with your ~previous versions using the
diff command to make sure there’s no major updates (very unlikely) then restore your customized versions. After restoring your files, remember to restart apache for the settings to take effect:
sudo apachectl restart.
The files customized by following this guide are:
Script to Deploy WordPress
Check out this gist for a bash script to quickly deploy a fresh WordPress install: