visit
macOS comes with built-in versions of Apache and PHP, and we could easily use those. However, there are a few drawbacks to this approach. We don’t have control over the exact versions used, and the version available might not be up to date. I’ve had problems where OS updates have overwritten my configuration for the built-in Apache server. Finally, running php -v
to check the version of the built-in PHP gives a warning message stating that PHP will be removed from future versions of macOS — in fact, in the upcoming macOS Monterey, PHP . Instead of using the built-in versions, we’ll install Apache and PHP using Homebrew.
Instructions for how to install Homebrew itself can be found on . Assuming Homebrew is installed, all you need to do in order to install Apache and PHP is to run the following command: brew install httpd php
(httpd
refers to the Apache webserver).
A word regarding paths: on a Mac with Apple Silicon, Homebrew will use /opt/homebrew
as its prefix. The prefix is sort of a base directory; a directory under which Homebrew will put various files belonging to the packages it installs. Binary files will go in /opt/homebrew/bin
, configuration files in /opt/homebrew/etc/
, and so on. On an Intel-based Mac, this base directory will likely be /usr/local
instead.
In this article, I’ll refer to paths using a Homebrew prefix of /opt/homebrew
. If your prefix is different, please change the paths accordingly. To find out which base directory Homebrew is using on your machine, run brew --prefix
.
Before we get started on Apache and PHP configuration, let’s touch on the topic of development URLs briefly. I think a nice setup for local development is to use a specific TLD like .test
, accessing my various projects through URLs like my-first-project.test
, my-second-project.test
, and so on. In this guide we’ll simply use the /etc/hosts
file to point our “fake” domains at our local web server. If there’s interest, a future post might cover how to set up a service like to automatically grab all requests to hosts ending with .test
and send them to our local development server.
.test
is a reserved top-level domain and so, using this, you shouldn’t run into problems such as the people using .dev
for development environments did when .dev
TLD.
The /etc/hosts
file provides a convenient way to associate hostnames with IP addresses. Normally, when we visit a URL like //www.git-tower.com/
, the actual IP address of the server has to be determined through something called the Domain Name System, or DNS for short. The /etc/hosts
file gives us an easy way to override this.
For the purposes of this article, let’s say we’re working with a project called my-project
, which we want served at the URL my-project.test
. We’ll add this line at the bottom of /etc/hosts
:
127.0.0.1 my-project.test
After saving the file, visiting my-project.test
in a browser will result in the request being sent to the IP address for our own machine.
Next, let’s get to work on the actual Apache configuration. In my case, the main Apache configuration file is located at /opt/homebrew/etc/httpd/httpd.conf
(again, on an Intel-based Mac, this is likely to be /usr/local/etc/httpd/httpd.conf
). In this file, there are a few changes to make:
Listen 8080
Listen 80
Chances are, you want to run multiple websites on your computer, with several hostnames in /etc/hosts
. However, all of these will hit the same Apache server. To serve multiple sites from one Apache server, Apache can look at the hostname of the incoming request and pass the request to one of multiple virtual hosts. Virtual host support has to be enabled by removing the #
in front of the line below, turning it from a comment into an Apache directive that loads the virtual hosts configuration from the file in question:
#Include /opt/homebrew/etc/httpd/extra/httpd-vhosts.conf
We’ll uncomment another line in order to load the mod_rewrite
module. This module is used to rewrite incoming URLs. For example, many web frameworks use it to enable “pretty URLs”, letting site visitors use URLs like /posts/2021/some-post-title/
while translating them into URLs like /index.php?p=697
for the back-end. This module will likely be useful, so let’s enable it by removing the #
below:
#LoadModule rewrite_module lib/httpd/modules/mod_rewrite.so
Apache comes with configuration for a default site, with a document root of /opt/homebrew/var/www
. We won’t be using this site, as we’ll use virtual hosts instead. As it stands, everything would work, but Apache would emit a warning every time it started, stating that it can’t determine the hostname to use for this default site. We’ll set the hostname on a per-virtual host basis later. However, to silence the warning message, we’ll uncomment the default ServerName
directive in httpd.conf
(or just enter any hostname you want):
ServerName www.example.com:8080
We want PHP to be available on our server. For this, we’ll add another LoadModule
directive after the other ones, loading a module provided by PHP as installed through Homebrew earlier (on an Intel-based Mac, change /opt/homebrew
to /usr/local
):
LoadModule php_module /opt/homebrew/opt/php/lib/httpd/modules/libphp.so
This loads the PHP module. There is some additional configuration required. Let’s add further PHP configuration in a separate file in the extra
directory, just like with the virtual hosts configuration. We need to include it explicitly from the main configuration file, so find the section with Include directives and add this after the last one:
# PHP settings
Include /opt/homebrew/etc/httpd/extra/httpd-php.conf
The rest of our PHP configuration goes in the file /opt/homebrew/etc/httpd/extra/httpd-php.conf
:
<IfModule php_module>
<FilesMatch .php$>
SetHandler application/x-httpd-php
</FilesMatch>
<IfModule dir_module>
DirectoryIndex index.html index.php
</IfModule>
</IfModule>
We start by checking if the PHP module is available, which might seem redundant as we just added it in the httpd.conf
file. However, these files may get edited independently of each other, so let’s follow the convention of the other files in the extra directory and check that the modules we use are available. We’ll look for files ending in .php
, and set their handler to application/x-httpd-php
— a handler provided by the PHP module. A handler in Apache represents an action to take for a file. While most files are just served using a built-in handler, the PHP files have to be interpreted by PHP before being served.
Some guides use the directives here, which take parameters for the file extensions they apply to. These can as they check their configured extensions against every extension of a file. For example, a web application may allow users to upload .jpeg
files. However, if a user uploads a file with a name ending in .php.jpeg
, this file might be executed as PHP if the above directives are used. Therefore, we use the FilesMatch
directive along with . Not that we're likely to encounter these issues when we're running Apache locally for ourselves, but we might as well set things up properly.
The DirectoryIndex
directive makes sure that if a URL for a directory is requested, and the directory contains an index.php
file (or an index.html
file), that file will be served.
We’ve now set up Apache to support PHP and virtual hosts. We still need to add configuration for each virtual host separately. Earlier, we uncommented a line in order to include the /opt/homebrew/etc/httpd/extra/httpd-vhosts.conf
file (/usr/local/etc/httpd/extra/httpd-vhosts.conf
on an Intel-based Mac). Now, let’s edit it to actually configure a virtual host.
<VirtualHost *:80>
ServerName my-project.test
DocumentRoot /path/to/my-project
<Directory /path/to/my-project>
Require all granted
AllowOverride All
</Directory>
</VirtualHost>
This configures a virtual host on port 80. ServerName
sets the hostname for the virtual site. When we visit my-project.test
in the browser, the change we made to /etc/hosts
will make sure the request is sent to the local Apache server. Apache will find the virtual host with the corresponding hostname and serve that site. The DocumentRoot
directive specifies the location on the file system where the files to be served exist. Make sure to change this path, along with the other instance of it in the Directory
directive, to the actual path of your website on your machine!
The Directory
directive has to do with permissions. In the httpd.conf
file, there’s a section which denies access to any resource by default. Access to anything that should be public has to be specifically allowed. So, for the files in our site root, we give everyone access to all resources through the Require
directive. The AllowOverride
directive controls which directives can be overridden in a .htaccess
file. A .htaccess
file can be used for per-directory configuration — many CMSes use this file together with the mod_rewrite
module mentioned earlier to set up their URLs, for example. Here, we allow all directives to be overridden.
That’s all the configuration done! All that remains is to start the server. If you want Apache to start automatically with your computer, you can use Homebrews’ services
command to start the server and enable it for autostart at the same time:
sudo brew services start httpd
sudo brew services stop httpd
will then both turn off the server and disable autostart for it.
Personally, I tend to just use Apache’s built-in “server control interface” to start the server as I need it: sudo apachectl start
will start the server and sudo apachectl stop
will stop it.