Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to install a basic Apache, PHP and MySQL development environment with Homebrew. Mirrored from the Echo & Co. blog.

How to install a basic Apache, PHP and MySQL development environment with Homebrew

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.

Before we begin...

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.

MySQL

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

Apache

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 - &amp;&amp; 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 - &amp;&amp; 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

PHP

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!

DNSMasq

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.

How to configure custom virtual hosts

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.

Troubleshooting

To get logs for each of the services:

  • Apache: $(brew --prefix)/var/log/apache2/error_log, or run httpd -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

Installing other PHP modules

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.

Questions?

Please forward your questions to the comment thread on the original blog post.

@BorisAnthony

This comment has been minimized.

Copy link

commented May 24, 2017

Hi! Running through this now. Just got this warning on "Dupes":
brew tap homebrew/dupes Warning: homebrew/dupes was deprecated. This tap is now empty as all its formulae were migrated.

@jakebellacera

This comment has been minimized.

Copy link
Owner Author

commented Jul 17, 2017

@BorisAnthony looks like it was deprecated. Can you just run the commands below without having to tap homebrew/dupes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.