How To Set Up Apache and PHP from Homebrew on macOS
Table of Contents
At Tower, we use the Apache web server to host websites like our learning platform, our blog, and our main product site. While this doesn’t mean we have to use Apache to run the sites locally, using a similar stack for development and production is generally a good idea. This means setting up a development environment with Apache. On macOS, which is what we use for the most part, there are quite a few options. We could set up Apache in a virtual machine, perhaps controlled using Vagrant. We could use Docker to run Apache in a container. There are also solutions with graphical user interfaces like MAMP. However, a convenient and simple solution is to just set up Apache running natively in macOS — no wrappers, no virtual machines, no containerization. In this post, we’ll go through how to set up Apache and PHP, using versions installed using the Homebrew package manager for macOS.
Out of the box, macOS comes with a version of Apache. It used to come with PHP too, but this was removed in macOS Monterey. We could easily use the built-in Apache version, but there are a couple of drawbacks with this approach. We don’t have control over the exact version used, and the one available might not be up to date. Also, OS updates occasionally overwrite the configuration for the built-in Apache server. Instead of using the built-in Apache, we’ll install both Apache and PHP using Homebrew. We'll look at how to set up a local development environment using these and, as usual, we’ll try to cover the “why” as well as the “what”; in addition to just presenting the configuration, we'll go over the purpose of each directive and command.
Here are the steps we'll take:
- Installing Apache and PHP
- Configuring Development URLs
- Apache Configuration
- PHP Configuration
- Virtual Host Setup
- Up and Running
- Permissions Postscript
1. Installation
Instructions for how to install Homebrew itself can be found on the official Homebrew website. 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
(here, httpd
refers to the Apache web server).
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
.
2. Development URLs
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 top-level domain 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 dnsmasq 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 Google acquired the .dev
TLD.
The /etc/hosts
file provides a convenient way to associate host names with IP addresses on our own computer. Normally, when we visit a URL like https://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 locally. For the purposes of this article, let’s say we want our project to be 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.
3. Apache Configuration
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
(as mentioned, 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
This line tells Apache to listen for traffic on the port 8080. Accessing ports with numbers lower than 1024 require root privileges and so, listening on port 8080 lets users run Apache without being root. However, as HTTP traffic goes to port 80 by default, we want to listen on that port instead:
Listen 80
Chances are, you want to run multiple websites on your computer, with several hostnames in /etc/hosts
. Requests to any of these hostnames 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
4. PHP Configuration
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:
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 Apache does for the virtual hosts configuration by default. 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 passed through the PHP interpreter before being served.
Some guides use the AddType
or AddHandler
directives here, which take parameters for the file extensions they apply to. These can introduce security issues 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 SetHandler
. 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.
5. Virtual Host Setup
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. Now, let’s edit that file. Start by deleting or commenting out the existing dummy virtual hosts in the file. Otherwise, Apache will emit some warnings on startup, as the document roots for these sites likely don't exist. Then, let's configure an actual 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 sets up 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 in 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>
section, to the actual path of your website on your machine!
The <Directory>
section 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 pretty URLs, for example. Here, we allow all directives to be overridden.
6. Up and Running
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. Hopefully, after starting the server, visiting your chosen hostname in your browser will result in your site being served correctly!
7. Permissions Postscript
There is one more aspect of the setup we need to address. Your PHP site may need to create, delete and modify files and directories under the project root. An introduction to Unix-like file system permissions is outside the scope of this post, but for our purposes it suffices to know that each file in such a file system belongs to one user and one group, and can specify different permissions for its owner, its group, and for everyone else. By default, the project root directory and anything you put inside it is likely to belong to your user and to a group with the same name as your user. The Apache server, on the other hand, runs with its user and group set to www
by default. This may lead to problems as the site code tries to modify files in the project, without having permission to do so. Permissions are a broad topic and we can't cover all potential issues here. However, a description of an often-useful quick fix follows.
It's probably best to keep having the directories and files belong to your user. For example, this let's you edit them in your text editor and create and delete them in Finder as you wish. What we can do is change the group of the directories and files to the one of the webserver, and then give that group the right to modify the files. This is accomplished through the following two commands, which you can run in your terminal if you run into permissions issues:
sudo chown -R :www /path/to/my-project/
sudo chmod -R g+w /path/to/my-project/
The first command sets the group of the project root and all directories and files inside it to www
. The second command makes sure the group has write permissions for those directories and files. As mentioned, there's way more to permissions than this, but for the problems I've run into with this setup, this approach has generally worked well!
That’s about all for today! I hope you found this guide useful. Of course, we’ve only covered Apache and PHP — there are many additions you may want to make to this stack, such as the MySQL database server. If you’re interested in a future article covering this or any other aspect of an Apache-based hosting stack, please let us know!