In this tutorial, you will set up nginx and multiple PHP versions on macOS 15 Sequoia. In the end, you will have a robust, clean, and fast local web development environment on Mac’s Apple Silicon (M1, M2, M3, and the future M4) arm64 chipsets. Here is the software you will install in order:
- Xcode, VS Code & Homebrew *: This step is required to get started.
- openSSL & wget *: This step is required to get started.
- MySQL: This step is not required, but MySQL and PHP are like PB & J.
- PostgreSQL: This step is not required, but I personally use PostgreSQL, so its here.
- Multiple PHP Versions *: This is the meat.
- Xdebug: This is an awesome tool I highly recommend.
- Nginx *: This step is required and is super in-depth, so you get SSL/TLS certs and custom local domains.
- Dnsmasq *: This step is required for those custom domains.
- Mailpit: This tool is excellent (predecessor to MailHog). You will love it if you send emails during local development. It catches emails and provides a nice-looking UI to view them.
- Redis: This is recommended if you are doing Laravel work.
- PHP Log Cleanup: A little CRON script to save disk space.
If you do not want to configure a custom setup like me, check out Laravel Herd and DBngin.
Setup: Xcode, VS Code, & Homebrew
Before starting, you need a few tools installed to take the stress out of the setup process: Xcode, VS Code, and Homebrew.
First, install Xcode. Next, install the CLI tools from the terminal. You will be using the terminal a lot coming up (I like iTerm2).
xcode-select --install
Also, you will need VS Code installed with the code
command in your system path.
Finally, install Homebrew.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
Homebrew will ask you to run a few commands; don’t miss them.
Now that the tools are installed, you can get into the rest of the setup process.
Install OpenSSL & wget on Mac
Install OpenSSL and wget. You will need these before moving forward.
brew install openssl
brew install wget
Install MySQL
Install MySQL.
brew install mysql
brew services start mysql
brew services list
Next, update your my.cnf
.
code /opt/homebrew/etc/my.cnf
# Default Homebrew MySQL server config
[mysqld]
# Only allow connections from localhost
bind-address = 127.0.0.1
mysqlx-bind-address = 127.0.0.1
# Add mode only if needed
sql_mode = "ONLY_FULL_GROUP_BY,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
Now, secure using the password password
and then restart.
mysql_secure_installation
brew services restart mysql
Next, MySQL 8 authentication needs to be updated per user to mysql_native_password
. Note that there is no space before the username and password.
mysql -uroot -ppassword
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
You might have to reset your password security level to LOW
if the above fails for ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
.
mysql> SHOW VARIABLES LIKE 'validate_password%';
mysql> SET GLOBAL validate_password.policy=LOW;
Install Postgres
Install postgresql (not the postgres app).
brew install postgresql
brew services start postgresql
brew services list
psql postgres
Now, you can check your user list.
postgres-# \du
Install Multiple PHP Versions
Now, install multiple PHP versions on your Mac computer. First, don’t use the default homebrew core tap for PHP. Use shivammathur/php.
brew tap shivammathur/php
brew install shivammathur/php/[email protected]
brew install shivammathur/php/[email protected]
brew install shivammathur/php/[email protected]
brew install shivammathur/php/[email protected]
brew install shivammathur/php/[email protected]
Next, set PHP 8.4 as your default php
CLI version.
brew unlink php
brew link --overwrite --force [email protected]
Now, for each version update the php-fpm you will need a unique port. Change the ports of each php-fpm to match its php version number. For example, [email protected]
I use port 9084
.
Also, you will want php-fpm to run with your user account and not _www
.
code /opt/homebrew/etc/php/8.4/php-fpm.d/www.conf
# default
user = _www
group = _www
listen = 127.0.0.1:9000
# change to
user = <your_username>
group = staff
listen = 127.0.0.1:9084
Optionally, before starting php-fpm, if you want to make edits to a php.ini
file now is the time. For example, you might want to increase the upload_max_filesize
and post_max_size
to 10M
.
Again, note the path based on the chipset.
/opt/homebrew/etc/php/8.0/php.ini
/opt/homebrew/etc/php/8.1/php.ini
/opt/homebrew/etc/php/8.2/php.ini
/opt/homebrew/etc/php/8.3/php.ini
/opt/homebrew/etc/php/8.4/php.ini
Once you are ready, start up php-fpm for each version.
brew services start [email protected]
brew services start [email protected]
brew services start [email protected]
brew services start [email protected]
brew services start [email protected]
Check that you have processes running and validate your ports are correct.
sudo lsof -i -n -P|grep php-fpm
Alias multiple PHP versions on macOS
Next, and optionally, add some PHP CLI aliases by adding the following to the bash scripts within your .bashrc
or .zshrc
file. This will give you quick access to a specific version when needed.
# Apple Silicon Chipset
alias php80="/opt/homebrew/opt/[email protected]/bin/php"
alias php81="/opt/homebrew/opt/[email protected]/bin/php"
alias php82="/opt/homebrew/opt/[email protected]/bin/php"
alias php83="/opt/homebrew/opt/[email protected]/bin/php"
alias php84="/opt/homebrew/opt/[email protected]/bin/php"
Once you reload your bash file, you can access each alias from the Mac command line. For example, you can check the exact version of the aliased version of PHP.
php84 -v
Switching Between Multiple PHP Versions
How do I switch between PHP versions? You can add the following to your bash scripts, to make switching between multiple PHP versions on macOS simple:
# Make switching versions easy
function phpv() {
brew unlink php
brew link --overwrite --force "php@$1"
php -v
}
If you want to change the default php
CLI, you can set it using brew or if added, the bash function phpv 8.4
.
# brew
brew unlink php
brew link --overwrite --force [email protected]
# bash function
phpv 8.4
Homebrew Upgrade PHP Errors
As time passes, Homebrew is bound to break your PHP installations. When this happens you can reinstall the PHP version having the error. Keep in mind you may need to reconfigure that version of PHP but I’ve found your php.ini
files remain the same.
brew reinstall shivammathur/php/[email protected]
Install Xdebug
I like xdebug for development with PHPStorm. To install xdebug for each version of php (cli and fpm) run the following commands.
brew link --overwrite --force [email protected]
pecl uninstall -r xdebug
pecl install xdebug
brew link --overwrite --force [email protected]
pecl uninstall -r xdebug
pecl install xdebug
brew link --overwrite --force [email protected]
pecl uninstall -r xdebug
pecl install xdebug
brew link --overwrite --force [email protected]
pecl uninstall -r xdebug
pecl install xdebug
brew link --overwrite --force [email protected]
pecl uninstall -r xdebug
pecl install xdebug
For each version, you have installed update the php.ini
. In our example, [email protected].
code /opt/homebrew/etc/php/8.4/php.ini
You will need to remove the zend_extension="xdebug.so"
that is added to the top of the file by the pecl install process. The new default xdebug port is 9003
– it was port 9000
.
Add the following to the bottom of your php.ini
file.
[xdebug]
zend_extension="xdebug.so"
xdebug.mode=debug
xdebug.client_port=9003
xdebug.idekey=PHPSTORM
When finished adding your xdebug configuration to each version you have installed, kill all the currently running php-fpm processes. This is not wise to do on a production server. This is perfectly fine with a new Mac dev setup.
sudo killall php-fpm
Install Nginx
Install nginx on macOS.
brew install nginx
sudo nginx
Now, test the installation is working.
http://localhost:8080
Now, change the default settings.
code /opt/homebrew/etc/nginx/nginx.conf
At the top of the nginx.conf
file replace #user nobody;
at the top of the file with the following:
user <your_username> staff;
Next, add the following to the http {}
block.
# allow for many servers
server_names_hash_bucket_size 512;
Then, make these updates inside server {}
block.
# From
listen 8080;
server_name localhost;
index index.html;
# To
listen 80;
server_name localhost test.x;
index index.html index.htm index.php;
Next, add a FastCGI gateway to php-fpm on the default server. The latest version of php installed is best. For other servers, you can set the version of PHP to the project requirement.
location ~ \.php$ {
fastcgi_param SCRIPT_FILENAME. $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9084;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
}
Next, add some basic security to your default server.
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
Then add the charset.
charset utf-8;
Now, you might want to allow for large file uploads.
http {
...
client_max_body_size 100M;
}
Next, edit the real index.html
file used by nginx. So, replace the index.html
with an index.php
file. Then, and some php code to make sure everything is working.
mv /opt/homebrew/var/www/index.html /opt/homebrew/var/www/index.php
code /opt/homebrew/var/www/index.php
Make the file show the PHP info.
<?php echo phpinfo(); ?>
Reload nginx.
sudo nginx -s reload
Go to the localhost web address in your browser.
http://localhost
You should see something like the following figure.
To add more servers, you can go to the nginx servers directory as /opt/homebrew/etc/nginx/servers/
, and add them there as individual files. Here is the basic template.
To keep things organized, you will want to create an SSL folder to hold your future SSL certs.
mkdir /opt/homebrew/etc/nginx/ssl/
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /opt/homebrew/etc/nginx/ssl/{{host}}.crt;
ssl_certificate_key /opt/homebrew/etc/nginx/ssl/{{host}}.key;
ssl_ciphers HIGH:!aNULL:!MD5;
# listen 80;
server_name {{host}};
root {{root}};
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9084;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
To add SSL for your nginx server, I use these bash functions to make the process faster. These functions:
- Generates a unique certificate per site.
- Adds the SSL certificate to your Mac’s Keychain.
- Generates the nginx server for the domain with PHP 8.4 the default.
To add a server I use the command nginxcreate my.test.x
but you might want to modify the files to match your setup.
Here are the links to the server templates I created.
alias nginxreload="sudo nginx -s reload"
alias nginxrestart="sudo nginx -s stop && sudo nginx"
alias nginxservers="cd /opt/homebrew/etc/nginx/servers"
alias nginxlist="ll /opt/homebrew/etc/nginx/servers"
# nginxcreate text.x /Users/yourname/Code/laravel/public/
function nginxcreate() {
wget https://gist.githubusercontent.com/kevindees/deb3e2bdef377bbf2ffacbc48dfa7574/raw/b19f38bf9d4dde3a9b4ac2b696ba995bae1af7ce/nginx-server-template-m1.conf -O /opt/homebrew/etc/nginx/servers/$1.conf
sed -i '' "s:{{host}}:$1:" /opt/homebrew/etc/nginx/servers/$1.conf
if [ "$2" ]; then
sed -i '' "s:{{root}}:$2:" /opt/homebrew/etc/nginx/servers/$1.conf
else
sed -i '' "s:{{root}}:$HOME/Sites/$1:" /opt/homebrew/etc/nginx/servers/$1.conf
fi
nginxaddssl $1
nginxrestart
code /opt/homebrew/etc/nginx/servers/$1.conf
}
function nginxaddssl() {
openssl req \
-x509 -sha256 -nodes -newkey rsa:2048 -days 3650 \
-subj "/CN=$1" \
-reqexts SAN \
-extensions SAN \
-config <(cat /System/Library/OpenSSL/openssl.cnf; printf "[SAN]\nsubjectAltName=DNS:$1") \
-keyout /opt/homebrew/etc/nginx/ssl/$1.key \
-out /opt/homebrew/etc/nginx/ssl/$1.crt
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /opt/homebrew/etc/nginx/ssl/$1.crt
}
function nginxedit() {
code /opt/homebrew/etc/nginx/servers/$1
}
Install Dnsmasq
To save yourself the fuss of editing your hosts
file constantly you can use dnsmasq
.
brew install dnsmasq
Then set up a custom hosts TLD *.x (or other official testing TLD like .test
and .localhost
) that point to 127.0.0.1
. Note, that I personally like to use the custom *.x TLD. However, *.test is compliant with IETF RFC 2606, and *.x is not.
(customize the commands as needed)
echo 'address=/.x/127.0.0.1' > /opt/homebrew/etc/dnsmasq.conf
echo '\naddress=/.test/127.0.0.1' >> /opt/homebrew/etc/dnsmasq.conf
echo '\naddress=/.localhost/127.0.0.1' >> /opt/homebrew/etc/dnsmasq.conf
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/x'
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/test'
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localhost'
Now start or restart dnsmasq.
sudo brew services start dnsmasq
Confirm that dnsmasq is working with a ping to each of the TLDs, one at a time.
ping test.x
ping test.test
ping test.localhost
Install Mailpit
I like to have mailpit running as a development mail server to test emails. But, if you don’t want to take this step that is perfectly fine.
# install
brew install mailpit
brew services start mailpit
# start
mailpit
Now, you can access Mailpit at http://localhost:8025/. However, you still need to connect Mailpit to PHP and the mail
mac command used by Postfix (Postfix comes with macOS).
Mailpit listens by default on port 8025 for the web UI, and port 1025 for SMTP.
Mailpit Installation Guide
code /etc/postfix/main.cf
Add the following to the end of the file to connect Mailpit to Postfix.
# Mailpit
myhostname = localhost
relayhost = [127.0.0.1]:1025
Send a test email and check Mailpit.
echo "Test email from Postfix" | mail -s "Test Email" [email protected]
Next, update each php.ini file
with the following, if you have multiple versions of PHP, and then restart php-fpm
. Note, test@localhost
should be used but will be overridden by any PHP scripts that run.
sendmail_path = /opt/homebrew/opt/mailpit/bin/mailpit sendmail test@localhost
sudo killall php-fpm
Install Redis
Install Redis. This will install Redis Server v6.
brew install redis
brew services start redis
redis-server
Optionally, you can update your default dump.rdb
file name in the redis.conf
if you want.
code /opt/homebrew/etc/redis.conf
# The filename where to dump the DB
dbfilename dump.rdb
Setup PHP Log Cleanup
Consider cleaning your log files and a schedule to save some disk space and keep your PHP logs from absorbing all your disk space. I clean my PHP logs by running a CRON job script.
Leave a Reply