This guide will walk you through the steps required to install a basic Apache, PHP and MySQL development environment using homebrew. Basically, all you'll need to do is copy the commands below into Terminal. Copy one block at a time.
NOTE: this guide is mirrored from Echo & Co.'s blog in case the original blog post or the website decides to go down. I've shared this guide around many times to colleagues and friends. Please give Alan Ivey (@alanthing) all of the credit for publishing this really helpful guide.
We're going to need to install homebrew
, a super awesome package manager for OS X. Installation instructions are available on the homebrew website.
Then, before we do anything else, update homebrew and then install homebrew/services
to manage our Apache, PHP, MySQL and other daemons more easily.
brew update
brew tap homebrew/services
Now we're ready to begin.
Let's start by installing MySQL:
# Install mysql via homebrew
brew install -v mysql
# Copy default mysql config and change configuration
cp -v $(brew --prefix mysql)/support-files/my-default.cnf $(brew --prefix)/etc/my.cnf
cat >> $(brew --prefix)/etc/my.cnf <<'EOF'
# Echo & Co. changes
max_allowed_packet = 1073741824
innodb_file_per_table = 1
EOF
# Uncomment sample innodb_buffer_pool_size to improve performance
sed -i '' 's/^#[[:space:]]*\(innodb_buffer_pool_size\)/\1/' $(brew --prefix)/etc/my.cnf
# Start the mysql service
brew services start mysql
# Launch MySQL's secure installation script to set up root user
$(brew --prefix mysql)/bin/mysql_secure_installation
Next up, Apache!
# First, stop the built-in Apache
sudo launchctl unload /System/Library/LaunchDaemons/org.apache.httpd.plist 2>/dev/null
# Then, install Apache (httpd22) via homebrew and mod_fastcgi module to get Apache to play nicely with PHP-FPM
brew tap homebrew/dupes
brew install -v homebrew/apache/httpd22 --with-brewed-openssl --with-mpm-event
brew install -v homebrew/apache/mod_fastcgi --with-brewed-httpd22
# To prevent problems with any previous mod_fastcgi setups (we'll put this back later)
sed -i '' '/fastcgi_module/d' $(brew --prefix)/etc/apache2/2.2/httpd.conf
# Tell Apache to use mod_fastcgi with our virtual hosts
(export USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') ; export MODFASTCGIPREFIX=$(brew --prefix mod_fastcgi) ; cat >> $(brew --prefix)/etc/apache2/2.2/httpd.conf <<EOF
# Echo & Co. changes
# Load PHP-FPM via mod_fastcgi
LoadModule fastcgi_module ${MODFASTCGIPREFIX}/libexec/mod_fastcgi.so
<IfModule fastcgi_module>
FastCgiConfig -maxClassProcesses 1 -idle-timeout 1500
# Prevent accessing FastCGI alias paths directly
<LocationMatch "^/fastcgi">
<IfModule mod_authz_core.c>
Require env REDIRECT_STATUS
</IfModule>
<IfModule !mod_authz_core.c>
Order Deny,Allow
Deny from All
Allow from env=REDIRECT_STATUS
</IfModule>
</LocationMatch>
FastCgiExternalServer /php-fpm -host 127.0.0.1:9000 -pass-header Authorization -idle-timeout 1500
ScriptAlias /fastcgiphp /php-fpm
Action php-fastcgi /fastcgiphp
# Send PHP extensions to PHP-FPM
AddHandler php-fastcgi .php
# PHP options
AddType text/html .php
AddType application/x-httpd-php .php
DirectoryIndex index.php index.html
</IfModule>
# Include our VirtualHosts
Include ${USERHOME}/Sites/httpd-vhosts.conf
EOF
)
# Use ~/Sites/httpd-vhosts.conf to configure our virtual hosts.
mkdir -pv ~/Sites/{logs,ssl}
touch ~/Sites/httpd-vhosts.conf
(export USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') ; cat > ~/Sites/httpd-vhosts.conf <<EOF
#
# Listening ports.
#
#Listen 8080 # defined in main httpd.conf
Listen 8443
#
# Use name-based virtual hosting.
#
NameVirtualHost *:8080
NameVirtualHost *:8443
#
# Set up permissions for VirtualHosts in ~/Sites
#
<Directory "${USERHOME}/Sites">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Allow from all
</IfModule>
</Directory>
# For http://localhost in the users' Sites folder
<VirtualHost _default_:8080>
ServerName localhost
DocumentRoot "${USERHOME}/Sites"
</VirtualHost>
<VirtualHost _default_:8443>
ServerName localhost
Include "${USERHOME}/Sites/ssl/ssl-shared-cert.inc"
DocumentRoot "${USERHOME}/Sites"
</VirtualHost>
#
# VirtualHosts
#
## Manual VirtualHost template for HTTP and HTTPS
#<VirtualHost *:8080>
# ServerName project.dev
# CustomLog "${USERHOME}/Sites/logs/project.dev-access_log" combined
# ErrorLog "${USERHOME}/Sites/logs/project.dev-error_log"
# DocumentRoot "${USERHOME}/Sites/project.dev"
#</VirtualHost>
#<VirtualHost *:8443>
# ServerName project.dev
# Include "${USERHOME}/Sites/ssl/ssl-shared-cert.inc"
# CustomLog "${USERHOME}/Sites/logs/project.dev-access_log" combined
# ErrorLog "${USERHOME}/Sites/logs/project.dev-error_log"
# DocumentRoot "${USERHOME}/Sites/project.dev"
#</VirtualHost>
#
# Automatic VirtualHosts
#
# A directory at ${USERHOME}/Sites/webroot can be accessed at http://webroot.dev
# In Drupal, uncomment the line with: RewriteBase /
#
# This log format will display the per-virtual-host as the first field followed by a typical log line
LogFormat "%V %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedmassvhost
# Auto-VirtualHosts with .dev
<VirtualHost *:8080>
ServerName dev
ServerAlias *.dev
CustomLog "${USERHOME}/Sites/logs/dev-access_log" combinedmassvhost
ErrorLog "${USERHOME}/Sites/logs/dev-error_log"
VirtualDocumentRoot ${USERHOME}/Sites/%-2+
</VirtualHost>
<VirtualHost *:8443>
ServerName dev
ServerAlias *.dev
Include "${USERHOME}/Sites/ssl/ssl-shared-cert.inc"
CustomLog "${USERHOME}/Sites/logs/dev-access_log" combinedmassvhost
ErrorLog "${USERHOME}/Sites/logs/dev-error_log"
VirtualDocumentRoot ${USERHOME}/Sites/%-2+
</VirtualHost>
EOF
)
# Set up self-signed TLS certificates for HTTPS development
(export USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') ; cat > ~/Sites/ssl/ssl-shared-cert.inc <<EOF
SSLEngine On
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
SSLCertificateFile "${USERHOME}/Sites/ssl/selfsigned.crt"
SSLCertificateKeyFile "${USERHOME}/Sites/ssl/private.key"
EOF
)
openssl req \
-new \
-newkey rsa:2048 \
-days 3650 \
-nodes \
-x509 \
-subj "/C=US/ST=State/L=City/O=Organization/OU=$(whoami)/CN=*.dev" \
-keyout ~/Sites/ssl/private.key \
-out ~/Sites/ssl/selfsigned.crt
# Start the Apache (httpd22) service
brew services start httpd22
# Finally, to get port 80 and 443 to work on OSX 10.11+
sudo bash -c 'export TAB=$'"'"'\t'"'"'
cat > /Library/LaunchDaemons/co.echo.httpdfwd.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
${TAB}<key>Label</key>
${TAB}<string>co.echo.httpdfwd</string>
${TAB}<key>ProgramArguments</key>
${TAB}<array>
${TAB}${TAB}<string>sh</string>
${TAB}${TAB}<string>-c</string>
${TAB}${TAB}<string>echo "rdr pass proto tcp from any to any port {80,8080} -> 127.0.0.1 port 8080" | pfctl -a "com.apple/260.HttpFwdFirewall" -Ef - && echo "rdr pass proto tcp from any to any port {443,8443} -> 127.0.0.1 port 8443" | pfctl -a "com.apple/261.HttpFwdFirewall" -Ef - && sysctl -w net.inet.ip.forwarding=1</string>
${TAB}</array>
${TAB}<key>RunAtLoad</key>
${TAB}<true/>
${TAB}<key>UserName</key>
${TAB}<string>root</string>
</dict>
</plist>
EOF'
sudo launchctl load -Fw /Library/LaunchDaemons/co.echo.httpdfwd.plist
We'll be using PHP-FPM instead of mod_php. This is better because PHP-FPM is much more portable and can be updated or changed whenever you feel like it. The following is for PHP 7.0. If you'd like to use 5.3, 5.4, 5.5 or 5.6, simply change the "7.0" and "php70" values below appropriately.
brew install -v homebrew/php/php70
# Change PHP settings
(export USERHOME=$(dscl . -read /Users/`whoami` NFSHomeDirectory | awk -F"\: " '{print $2}') ; sed -i '-default' -e 's|^;\(date\.timezone[[:space:]]*=\).*|\1 \"'$(sudo systemsetup -gettimezone|awk -F"\: " '{print $2}')'\"|; s|^\(memory_limit[[:space:]]*=\).*|\1 512M|; s|^\(post_max_size[[:space:]]*=\).*|\1 200M|; s|^\(upload_max_filesize[[:space:]]*=\).*|\1 100M|; s|^\(default_socket_timeout[[:space:]]*=\).*|\1 600|; s|^\(max_execution_time[[:space:]]*=\).*|\1 300|; s|^\(max_input_time[[:space:]]*=\).*|\1 600|; $a\'$'\n''\'$'\n''; PHP Error log\'$'\n''error_log = '$USERHOME'/Sites/logs/php-error_log'$'\n' $(brew --prefix)/etc/php/7.0/php.ini)
# Fix pecl and pear permission problems
chmod -R ug+w $(brew --prefix php70)/lib/php
# Install optional optcache extension to improve PHP performance
brew install -v php70-opcache
/usr/bin/sed -i '' "s|^\(\;\)\{0,1\}[[:space:]]*\(opcache\.enable[[:space:]]*=[[:space:]]*\)0|\21|; s|^;\(opcache\.memory_consumption[[:space:]]*=[[:space:]]*\)[0-9]*|\1256|;" $(brew --prefix)/etc/php/7.0/php.ini
# Start PHP-FPM
brew services start php70
Optional: At this point, if you want to switch between PHP versions, you'd want to:
brew services stop php70 && brew unlink php70 && brew link php54 && brew services start php54
. No need to touch the Apache configuration at all!
The end result here is that any DNS request ending in .dev reply with the IP address 127.0.0.1:
# Install and configure dnsmasq
brew install -v dnsmasq
echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
echo 'listen-address=127.0.0.1' >> $(brew --prefix)/etc/dnsmasq.conf
echo 'port=35353' >> $(brew --prefix)/etc/dnsmasq.conf
# Start dnsmasq
brew services start dnsmasq
# With dnsmasq running, configure OSX to use your local host for DNS queries ending in .dev
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'
sudo bash -c 'echo "port 35353" >> /etc/resolver/dev'
To test, the command ping -c 3 fakedomainthatisntreal.dev
should return results from 127.0.0.1. If it doesn't work right away, try turning WiFi off and on (or unplug/plug your ethernet cable), or reboot your system.
You shouldn't need to edit the Apache configuration or edit /etc/hosts for new local development sites. Simply create a directory in ~/Sites and then reference "http://" + that foldername + ".dev/" in your browser to access it.
Protip: if you don't want to place sites in the ~/Sites folder, simply symlink the external folder to the ~/Sites folder:
ln -s /absolute/path/to/custom/php/application ~/Sites/hostname
Just don't forget to change "hostname" to the .dev domain (without the .dev part) you want to use to point to the app.
To get logs for each of the services:
- Apache:
$(brew --prefix)/var/log/apache2/error_log
, or runhttpd -DFOREGROUND
and look for output - PHP-FPM:
$(brew --prefix)/var/log/php-fpm.log
- MySQL:
$(brew --prefix)/var/mysql/$(hostname).err
- DNSMasq: no log file, run
dnsmasq --keep-in-foreground
and look for output
The homebrew/php
repository has a number of modules. Simply run the command brew search php70-
(remember to replace the version number with the version you installed) to find other available modules.
Please forward your questions to the comment thread on the original blog post.
@BorisAnthony looks like it was deprecated. Can you just run the commands below without having to tap
homebrew/dupes
?