Configure Apache and PHP-FPM on macOS Catalina

Overview

I finally have Altoplace running on my Pair Networks VPS. Over the last few months, I have experimented with different hosting arrangements and WordPress configurations. Now it’s time to start creating content to see if I can create a viable site. To that end, I want to start by writing about my efforts to create a local website development environment on my Mac. My environment may not be perfect and will change over time, but it’s working for me. As always, I welcome comments. I am not an expert; the purpose of my posts is to describe my experience and what I have learned. Hopefully, you may find something helpful here.

As a foundation, I have chosen Homebrew to install most of the software that I am using. Please go to their site for installation instructions. It’s not the only game in town. Apple does provide a web development environment, but their software may be (slightly) out-of-date or hard to configure. MacPorts is another good option. After trying out both, I had a hard time deciding, but finally chose Homebrew for it’s popularity and ease of use.

Using the Apache Event Module

There are multiple sites that describe how to install Apache and PHP on a Mac using Homebrew. But most of them describe using Apache with the pre-fork Multi-Processing Module (MPM). That’s understandable since Homebrew installs Apache with the pre-fork module active. But in the Linux world, the current, default module is the event module. This allows you to run PHP as a separate process, which is called on as needed. With the pre-fork module, PHP is built in (with mod_php) and is executed all the time even if PHP is not required to fulfill the request.

Running Apache with the pre-fork module is probably just fine while working with a local development environment. But I wanted to learn how to use it with the event module, running PHP as a separate process. By the way, you should check out “macOS 10.15 Catalina Apache Setup: Multiple PHP Versions.” This is one of the best posts that I have seen about setting up a local website development environment. It has inspired much of what I have done to setup my local development environment. It also uses Homebrew to install most of the software.

This post is just about Apache and PHP; I will write separate posts about MySQL and other tools.

Install Apache with the brew command

Let’s get started; the first step is to install Apache, using Homebrew. Please install Homebrew and verify your installation if you haven’t done so.

First, we verified that Homebrew is ready to go. Next, we installed Apache. Finally, we ran a couple of commands to check the installation. Note, use the which command to verify that the Homebrew version of Apache is first in your PATH (you will have to restart your terminal session). The last command shows that Apache was installed with the prefork module enabled.

Great. Now, let’s start it up and test it with Safari:

You should see It Works! in your Safari browser. Note, that you had to specify the non-standard 8080 port number as part of the URL. That’s because Homebrew does not like using sudo, which is required if you want to start up Apache with the standard port 80 configuration. That’s called a privileged port and requires root privileges. In addition to the above test that Apache is running, I like to be able to verify that the http processes are running. It’s nice to check when you start Apache and at various times if you want to check things like start time, elapsed time, and the memory footprint. To that end, I wrote a shell script, which I named a2info:

A couple of notes. You want to make sure that the script is executable, chmod +x ~/bin/a2info, and make sure that your local bin directory is in your PATH:

I like putting my local bin directory at the beginning of my PATH. I am currently using the macOS version of zsh; I defined my PATH in my local .zshrc file:

As you may have guessed, this is how we stop Apache:

Switching Apache to the event module and port 80

Using port 80 …

Next, I want to change my Apache configuration to use the event module and port 80. To start and stop Apache when using privilege ports, sudo has to be used. Homebrew is designed to not use sudo. You will see examples of it being used, but in my experience, the brew services … command does not play nicely with sudo. Therefore, I wrote my own shell script to start and stop Apache.

Make sure that you have stopped Apache with the above brew services command. I wrote a shell script called a2ctl to start, stop, or restart Apache:

As noted by the Usage comment, it is very easy to use. Once started, Apache will start automatically when the macOS system is restarted. If stopped, Apache will not start when the system restarts. If stopped or running, restart will (re)start Apache. I also added the a2info functionality. Now that we have our new Apache start/stop script, lets update our Apache configuration to use port 80:

You should see It works! again.

Switching to the event module …

Make the following changes to switch Apache to the event module:

We commented out the prefork module and uncommitted the event module. Uncommenting the Include httpd-mpm.conf file is optional, but it allows updating the number of httpd event servers, etc. This is the default event MPM configuration defined in this file:

Change Document Root to the Local User

Because we are setting up a Development environment (don’t do this on a production system), we will move our Document Root to our local user Sites directory. Make the following changes to httpd.conf (changing user to your local user):

By the way, if something doesn’t go right, be sure to check the Apache error log at /usr/local/var/log/httpd/error_log. For the “started OK” case you should see a line that says “resuming normal operations.”

Let’s test our new local user root directory. Create a index.html in your ~/Sites directory and test it in your Safari browser:

You should see Our Local User Web Root in your browser (you may need to refresh your browser). If all is good, we can move on to installing and configuring PHP-FPM.

Install and Configure PHP-FPM

We have configured Apache to work with the event module and our local Sites document root. Now we will make the Apache configuration changes to talk to PHP-FPM. We will enable the mod_proxy_fcgi and mod_proxy modules along with httpd.conf changes to enable FastCGI support. You can read more about how this works at the Apache Module mod_proxy_fcgi website. Finally, we will install and configure PHP-FPM.

Configure Apache to work with PHP-FPM

Again, we will edit httpd.conf with your favorite text editor. Make the following changes:

Restart Apache with the above changes:

Again, check the Apache error log if it doesn’t start up OK. At this point Apache is ready to run PHP. We don’t have to touch it again. We can install PHP, change PHP versions, etc., and Apache will keep talking to the currently running PHP version. So let’s move on to our PHP install.

Setting up PHP-FPM to work with Apache

As of this writing, the newest version of PHP is version 7.4. However, my Pair Networks VPS is still using version 7.3. The following instructions will describe how to install and configure PHP version 7.3 as the default version of PHP. I will also install version 7.4. We could use the brew services command to start and stop the PHP-FPM service. But I opted to write a shell script to start and stop PHP-FPM. I can also easily switch PHP versions with my shell script. I will also update the PHP-FPM configuration file to use a Unix socket to talk to Apache (which is ready to go on the Apache side). The PHP brew installation defaults to using a TCP/IP socket, but the Unix socket approach has less overhead. You can read about what’s the difference between a Unix socket and a TCP/IP socket.

Let’s frist install PHP versions 7.3 and 7.4:

The first brew command installed PHP version 7.3 and a bunch of dependencies. The second install command installed the latest production version of PHP, which is currently version 7.4. Finally, the third brew command switched the default version of PHP to version 7.3. Note: Be sure to restart your terminal session after completing the brew link command. Then you can verify your PHP version, and I also showed how to start the PHP-FPM services with the brew services command. For now, stop PHP-FPM with the brew services stop php@7.3 command. We need to update the PHP-FPM configuration file, so that it will be able to talk to Apache.

Starting and Stopping PHP-FPM

You can continue using the brew services command to start and stop PHP-FPM. I prefer having a little more control for starting and stopping, including verifying that PHP-FPM actually did start. I have seen cases where the brew services command will report a successful start, but PHP-FPM actually did not start (or died immediately). I also wanted to implement a convenient way to switch PHP versions. Therefore, I wrote a shell script called p7ctl to start, stop, and restart PHP-FPM. I also included an info action to get information about the running PHP-FPM processes. But before we get into the details about p7ctl, let’s update the PHP-FPM configuration for Apache (and some additional tweaks).

First, change directory to /usr/local/etc/php/7.3/php-fpm.d. The default PHP-FPM configuration is in www.conf. You will want to save a copy of that file in a safe place because it has a lot of useful comments in it, but we are going to filter out all the commented lines and make our Unix proxy changes and point to our local user (which you need to update):

Now we are ready to test that PHP-FPM is working with Apache. You will need to first stop (if not stopped already) and then start PHP-FPM with the brew services command. Next, we will create a simple myinfo.php PHP script to test that everything is working:

You should see the PHP info screen with lots and lots of information about your running PHP installation. Note that the Server API shows FPM/FastCGI. If all is good, then we are about done with the exception of optionally using my p7ctl PHP control script. Also, don’t forget to make the same www.conf changes for PHP-FPM 7.4.

I actually showed how you can manually switch PHP-FPM services. You can actually start different PHP-FPM versions without doing the unlink and link commands. But I prefer keeping the Command Line Version of PHP (php -v) in sync with the PHP-FPM version. Also, I don’t know if there could be other side affects from not unlinking and linking.

Using p7ctl instead of the brew services command

Again, I wrote a shell script, p7ctl, to have a little finer control for stopping and starting PHP-FPM. Plus, I can easily use it to switch the running version of PHP-FPM (currently, version 7.3 or 7.4). You simply stop the running version and start the new version. Here is my p7ctl shell script:

Here are some examples of how to use it (first, be sure to run brew services stop php@7.3 if you previously started PHP-FPM with the brew services start command):

Setting up Virtual Hosts

At this point, Apache and PHP-FPM is working with our Mac localhost. The last item that we want to configure is our virtual hosts environment. We want to be able to create testing host domains that are local to our Mac. For example, I have created a local test domain called altoplace.tst.

For each virtual host, we need a DNS entry, so that we’re able to enter that name into our Safari (or your favorite) browser on your Mac. We could add an entry for each virtual host to our /etc/hosts file, but instead of doing that, we will install a lightweight DNS server on our Mac that will resolve any domain name ending in txt (or test, or whatever local TLD you choose). Of course, don’t pick at real TLD. Also, I understand that the current version of Google Chrome forces all .dev domains to use SSL, so .dev might not be a good choice. We are going to install and use dnsmasq, again using Homebrew. Also, I will show you my shell script, dnsctl, that I use to start and stop dnsmasq. Dnsmasq requires root privileges, and as I noted before, the brew services command does not play well with the sudo command.

Using a lightweight DHCP and caching DNS server, dnsmasq

Run the following commands to install and configure dnsmasq:

The last three lines creates our .tst domain. Actually, I made this a bit more complicated than needed. You could have just created a one line dnsmasq.conf file that contains address=/.tst/127.0.0.1. None of the other configuration options in this file are turned on for our simple application, but I wanted to keep the file as is in case I choose to make other configuration changes in the future.

At this point, we are ready to start the dnsmasq service. However, it needs to be started with root privileges, so I am going to again create my own shell script to start and stop dnsmasq. I called it dnsctl:

The following shows how to start up dnsmasq with the dnsctl shell script:

Try it out! You can ping any domain that you can dream of (if it ends in .tst). The dnsctl script doesn’t change file permissions like the sudo brew services … command will do (when using sudo). Also, dnsutl tries to verify that the service successfully started (or stopped). I have seen cases where the brew services command said that the service started, but it actually didn’t (or died right away).

Creating our First Virtual Host

We first need to make some additional changes to our Apache configuration file and restart Apache:

The above configuration changes enable the vhosts module and, while we were at it, the rewrite module. Now, we can add our virtual hosts to httpd-vhosts.conf:

Let me try to explain my changes. Since we turned on virtual hosts, I added our top-level ~/Sites directory as a virtual host. I defined my new altoplace.tst virtual host. And I added a special virtual host, imac1.local, to my vhosts file. The imac1.local domain is my Mac domain that is reachable from any system on my local network. It also points to my top-level ~/Sites directory. You can now add an index.html file and a info.php file to your new vhost directory (e.g., ~/Sites/altoplace.tst) and test that your new Virtual Host is working (http://altoplace.tst).

Setting up SSL (TLS)

SSL is the last topic that I am going to discuss in this post. It adds the ability to test SSL enabled virtual hosts (e.g., https://altoplace.tst). This is an important topic since the use of SSL is highly encouraged and is inexpensive to do. These days, you will see a new term, TLS, replacing SSL. You can read about SSL/TLS at the SSL link that I just provided. They are both protocols for establishing authenticated and encrypted links between networked computers.

Updating httpd.conf and extra/httpd-ssl.conf to support SSL

We just need to make a few more changes to our Apache httpd.conf and extra/httpd-ssl.conf configuration files to support the use of SSL:

We just uncommented two LoadModule lines to enable SSL. We also uncommented and updated the httpd-ssl.conf include file (don’t try to restart Apache yet). We changed the Homebrew defined SSL port number (8443) back to the default SSL port number (443). We still need to create the SSL certificate and key files before restarting Apache.

Updating our Virtual Hosts to use SSL

We need to update our Virtual Hosts configuration to add SSL-enabled virtual hosts to our Apache configuration:

Again, we still are not ready to restart Apache. We will now create the local SSL certificate and key files referenced in the httpd-vhosts.conf and extra/httpd-ssl.conf files.

Creating the local SSL certificates and keys

We could use OpenSSL to create a self-signed certificate. But instead, we are going to use mkcert to create our SSL certificates. It is very easy to use to create locally trusted development certificates. We can use Homebrew to install mkcert. Here’s how to install and use mkcert:

The nss formal provides libraries that Firefox needs for using SSL. We ran mkcert -install one time to install a locally trusted certificate in our system trust store. Then we created a certificate for localhost. The last two examples shows how easy it is to create additional certificates for locally hosted domains. Of course, you will use your own local development domains. At this point, you are ready to restart Apache and test your newly SSL-enabled domains. I would suggest running the Apache configuration test first just to verify that you didn’t make any configuration errors. I certainly did find errors the first time around:

We ran the Apache configuration test, restarted Apache, and for good measure, we verified that our httpd processes were running (and had just started). If you encounter any errors, be sure to check your Apache error log (/usr/local/var/log/httpd/error_log).

Conclusion

At this point, you should have a basic Apache/PHP-FPM working environment, supporting SSL-enabled virtual hosts. You can start testing website environments, such as Grav, that don’t require a MySQL database. I will write a different post about setting up MySQL to complete our local development LAMP stack (but for the Mac instead of Linux) environment. Then I will write about how I installed WordPress using WP-CLI. All coming later. Please stay tuned …

1 thought on “Configure Apache and PHP-FPM on macOS Catalina”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.