This guide is intended for developers who want to write and run PHP on their local Mac. It is expected that you have some familiarity with Terminal commands, as well as opening and saving text files with an editor of your choice.
Available from the Apple Developer Center, the Xcode Command Line Tools are all that Homebrew needs to build and install software on the Mac. The Xcode app is only needed if you intend to also to OS X or iOS development.
Installing Homebrew is as easy as running the installer script right from the Homebrew GitHub repo:
ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
Then, to validate your Homebrew setup, run:
brew doctor
The ideal output from brew doctor
is Your system is ready to brew.
If you see that, you're ready to continue. If you see otherwise, follow the instructions provided by Homebrew and re-run brew doctor
until it is satisfied.
Installing nginx with Homebrew is the simplest of Homebrew commands:
brew install nginx
Homebrew's nginx details instruct you to setup the launchctl start/stop script as a plist file buried in your home directory. We're going to put them elsewhere, and here's why:
If you want nginx to run on the standard web port of 80 instead of a high port number like 8080, you'll need to run the nginx master process as root. Ports under 1024 are privileged ports, and require root privileges to bind to for listening. Thankfully, nginx's process model is such that the master process does not actually handle web requests, but only manages worker processes; If a malformed web request were to compromise an nginx process, it would be that of a worker process, and the scope of any damage would be limited to the domain of the user the worker is running as (i.e. your user, and not root, thus protecting the OS).
So, with that in mind, let's create a system level place for nginx's plist to reside, and then symlink the provided plist into that directory:
sudo mkdir -p /Library/LaunchDaemons
sudo cp /usr/local/opt/nginx/*.plist /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
sudo chmod 644 /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
Later, you'll encounter LaunchAgents and you may wonder how they differ. A good overview can be found in the launchd documentation, but the short answer is:
Location | Purpose |
---|---|
~/Library/LaunchAgents | Agents that run as your user when you log in |
/Library/LaunchAgents | Agents that run when anyone logs in |
/Library/LaunchDaemons | Agents that run with root privileges at boot |
/System/Library/LaunchAgents | System-wide agents that run with root privileges when anyone logs in |
/System/Library/LaunchDaemons | System-wide agents that run with root privileges at boot |
Like most UNIX services, nginx has a configuration file that lets you change almost any aspect of its behavior. Here's a simple configuration file that will start worker processes as your user. Drop the contents of this file in /usr/local/etc/nginx/nginx.conf
, making sure to replace "collin" with the output of the whoami
command as run in your Terminal:
user collin staff;
worker_processes 2;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type text/plain;
server_tokens off;
sendfile on;
tcp_nopush on;
keepalive_timeout 10;
client_max_body_size 256m;
gzip on;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/css text/javascript application/json application/x-javascript text/xml application/xml application/xml+rss;
index index.html index.php;
include sites/*.conf;
}
The last line of that configuration file tells nginx to include all files ending in .conf
in the sites
directory. After configuring nginx to communicate with PHP, we'll setup such a site.
Also create a place for nginx logs to be written to:
rm -f /usr/local/var/log/nginx
mkdir -p /usr/local/var/log/nginx/
We'll use that location for logs for each site.
For the individual details about each site, create a directory to store the configuration files we will create later:
mkdir /usr/local/etc/nginx/sites
Similarly, create a place in your home directory to hold the content for each site:
mkdir -p ~/Sites
We will also be installing PHP shortly, and nginx worker processes need to know how to route PHP requests to the php-fpm process that we will set up. Drop the contents of this file into /usr/local/etc/nginx/php.conf
:
fastcgi_intercept_errors on;
location ~ \.php$
{
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_read_timeout 60;
fastcgi_buffers 256 16k;
fastcgi_buffer_size 32k;
fastcgi_pass unix:/usr/local/var/run/php-fpm.sock;
fastcgi_index index.php;
}
Note that the location
block handles requests that end in .php
. Combined with the fastcgi_pass
parameter, which references a UNIX socket, we are able to send PHP requests to a socket where php-fpm will be listening when we set it up shortly. The majority of the other options add parameters to the request that nginx will send to PHP. You may recognize them as global variables that PHP offers access to, and this is how request related parameters get from nginx to PHP.
To start nginx, invoke launchctl
with sudo
, since nginx's master process runs as root. It will spawn worker processes as the user specified in nginx.conf
.
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
Similarly, to stop nginx, run the same command, replacing load
with unload
:
sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
To verify that nginx started correctly, open the Activity Monitor application, choose All Processes from the top-right menu, and filter by "nginx". You should see one nginx process running as root (the master process) and two workers running as your user.
The standard PHP version maintained by Homebrew isn't nearly as full-featured as the one maintained by josegonzalez on GitHub, so we'll use his version:
brew tap homebrew/dupes
brew tap josegonzalez/homebrew-php
brew install php54 --with-fpm
That last brew install
step will take some time; PHP takes a while to build from source, so if your Terminal appears stuck at make
for a spell, that's why. Go make a sandwich or something.
If you'd also like to be able to manipulate images with Imagick, do step-by-step debugging, or work with MongoDB, you can also install those modules:
brew install php54-imagick
brew install php54-xdebug
brew install php54-mongo
You can run brew search php54
to see a full list of other modules that can be installed and used.
As with nginx, PHP also has a startup script to install:
mkdir -p ~/Library/LaunchAgents
ln -sfv /usr/local/opt/php54/*.plist ~/Library/LaunchAgents
Note that this time, unlike with nginx, we're putting the plist into a directory in your home folder, so all of PHP's processes will run as your user. No root-level privileges are required, since it will be configured to listen on a UNIX socket.
Here's a shortened (seriously) PHP configuration file you can drop into /usr/local/etc/php/5.4/php.ini
:
[PHP]
engine = On
short_open_tag = Off
asp_tags = Off
precision = 14
y2k_compliance = On
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 17
allow_call_time_pass_reference = Off
safe_mode = Off
safe_mode_gid = Off
safe_mode_include_dir =
safe_mode_exec_dir =
safe_mode_allowed_env_vars = PHP_
safe_mode_protected_env_vars = LD_LIBRARY_PATH
disable_functions =
disable_classes =
expose_php = On
max_execution_time = 30
max_input_time = 60
memory_limit = 512M
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
error_log = /usr/local/var/log/php.error.log
variables_order = "GPCS"
request_order = "GP"
register_globals = Off
register_long_arrays = Off
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 256M
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 256M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60
[Date]
[filter]
[iconv]
[intl]
[sqlite]
[sqlite3]
[Pcre]
[Pdo]
[Pdo_mysql]
pdo_mysql.cache_size = 2000
pdo_mysql.default_socket=/tmp/mysql.sock
[Phar]
[Syslog]
define_syslog_variables = Off
[mail function]
SMTP = localhost
smtp_port = 25
mail.add_x_header = On
[SQL]
sql.safe_mode = Off
[MySQL]
mysql.allow_local_infile = On
mysql.allow_persistent = On
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port = 3306
mysql.default_socket = /tmp/mysql.sock
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off
[MySQLi]
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket = /tmp/mysql.sock
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
[mysqlnd]
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off
[OCI8]
[PostgresSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
[bcmath]
bcmath.scale = 0
[browscap]
[Session]
session.save_handler = files
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.entropy_length = 0
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"
[MSSQL]
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatability_mode = Off
mssql.secure_connection = Off
[Assertion]
[COM]
[mbstring]
[gd]
[exif]
[Tidy]
tidy.clean_output = Off
[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
soap.wsdl_cache_limit = 5
[sysvshm]
[ldap]
ldap.max_links = -1
[mcrypt]
[dba]
[xdebug]
When built using the --with-fpm
option like we've done, PHP will be built with the php-fpm process manager. Like nginx, it runs a set of processes to handle FastCGI requests that nginx sends their way over that UNIX socket. Now it's time to set up the PHP side of that logic. Drop the following file into /usr/local/etc/php/5.4/php-fpm.conf
, again replacing "collin" with your username:
[global]
daemonize = no
error_log = /usr/local/var/log/php-fpm.error.log
[www]
user = collin
group = staff
listen = /usr/local/var/run/php-fpm.sock
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
Worth noting:
daemonize
is set tono
because launchd expects processes to remain in the foreground so it can control them, while launchd runs in the backgroundlisten
is set to the same location as the socket nginx is sending to in itsphp.conf
To start php-fpm, invoke launchctl
without sudo
, since php-fpm does not need to run as root:
launchctl load ~/Library/LaunchAgents/homebrew-php.josegonzalez.php54.plist
Similarly, to stop nginx, run the same command, replacing load
with unload
:
launchctl unload ~/Library/LaunchAgents/homebrew-php.josegonzalez.php54.plist
You can follow this set of steps anytime you want to add another local website.
Create a directory to hold the site content (PHP code, CSS, images, etc.):
mkdir -p ~/Sites/example/public
Also create an nginx configuration file to inform nginx about that site. Here's one for an example website. Drop the following file into /usr/local/etc/nginx/sites/example.conf
, replacing "collin" with your username (like we did with the base nginx configuration above):
server
{
listen 80;
server_name local.example.com;
root /Users/collin/Sites/example/public;
access_log /usr/local/var/log/nginx/example.access_log.txt;
error_log /usr/local/var/log/nginx/example.error_log.txt;
location /
{
autoindex on;
index index.php;
try_files $uri $uri/ /index.php?q=$uri&$args;
}
include php.conf;
}
Note that the last line of that file includes php.conf
from the main nginx config directory. That will enable this site to use PHP content.
To be able to visit this site, your browser needs to know what IP address the name "local.example.com" maps to. We'll manually edit /etc/hosts
(sudo privileges required) to add this line at the top:
127.0.0.1 local.example.com
127.0.0.1 is a universal reference to "this computer", so we're telling the browser that local.example.com is this computer right here, being served by nginx.
Create a new file at ~/Sites/example/public/index.php
with some basic PHP content:
<?php
phpinfo();
Because we changed nginx configurations, and it doesn't dynamically reload them, we need to restart nginx using the stop/start commands covered earlier:
sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
Then visit local.example.com and you should see a huge phpinfo page with all the details of your PHP installation.