As explained in our parent article, we love Nginx. In order to create similar working environments on both your local machine as your server(s), we've written 2 sibling articles as close aligned as possible.

OSX comes with Apache pre-installed. Lovely.
Now we need to ignore that. As with every http server flavour, Nginx has its on set of quirks. After years of .htaccess hacking, we're sure you'll find some use in the latter tricks and modifications.


For your local convenience

If you don't have HomeBrew installed, go get it already: homebrew article. If you have, it doesn't hurt to update to the latest version.

$ brew update
$ brew upgrade --all

The actual install of Nginx on OSX takes seconds:
$ brew install nginx

To make sure everything went fine, you can run $ brew doctor

The configuration is located at /usr/local/etc/nginx/nginx.conf, and will need some tweaking if you want to use Nginx as default webserver and maybe add php.

Auto start

We'll want Nginx to listen to the default port 80, which requires root credits. Let's set this up, softlink the plist to the auto start directory and launch a root'ed process.
First check if the /Library/LaunchAgents exists, you might need to create it:

$ mkdir -p /Library/LaunchDaemons
$ sudo cp /usr/local/opt/nginx/*.plist /Library/LaunchDaemons
$ sudo chown root:wheel /Library/LaunchDaemons/homebrew.mxcl.nginx.plist
$ sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.nginx.plist

If you haven't tweaked the config files yet, you can test the process with curl on the default Nginx port.
curl -IL http://localhost:80

Let's abort the service for now. Yes, you can just run nginx, the -s stand for "Signal the master process" and accepts stop, quit, reopen or reload.

$ sudo nginx -s stop

Server-like config

You probably want your local environment behaving as close as possible as your server setup. Let's add some folders first. Be careful with the rights, if you already have /var/www, ignore those actions, or you might have to re-commit ALL your git repos stored there.

$ cd /usr/local/etc/nginx
$ mkdir -p sites-available sites-enabled conf.d global
$ sudo mkdir -p /var/www

$ sudo chown :staff /var/www
$ sudo chmod 775 /var/www

Now let's clean out the main config file. It looks a bit different then the server version.
You can simply clean and replace.

$ sudo nano /usr/local/etc/nginx/nginx.conf
worker_processes  1;

events {
    worker_connections  768;
http {
    include             mime.types;
    default_type        application/octet-stream;
    types_hash_max_size 2048;
    sendfile            on;
    keepalive_timeout   45;
    error_log   /usr/local/var/log/nginx/error.log debug;
    access_log  /usr/local/var/log/nginx/access.log;
    include conf.d/*.conf;
    include sites-enabled/*; 

We configure more project-specific settings next.


Nginx is very suited to proxy your local url's to Node, like Ghost.
You of course need Node installed: frontend article.


You'll probably going to use php along the line. PHP-FPM is our poison of choice, as it is on the server. Prepare the brew, or ignore this install if you're a Node- or Python-head.

$ brew tap homebrew/dupes
$ brew tap josegonzalez/homebrew-php

Install without Apache. This can take a couple of minutes. This article is based on php5.5, your machine might have an alternative version - edit accordingly.

$ brew install --without-apache --with-fpm --with-mysql php55

All you need now is an auto-start, make sure you link the right version (replace the 5.5.xx by your temporary one). Then launch PHP-FPM.

$ ln -s /usr/local/Cellar/php55/5.5.xx/homebrew.mxcl.php55.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.php55.plist

To confirm a responsive port 9000, lsof -Pni4 | grep LISTEN | grep php should put out something like this:

php-fpm   68817 user    6u  IPv4 0x3d7bc2124072d59b      0t0  TCP (LISTEN)
php-fpm   68818 user    0u  IPv4 0x3d7bc2124072d59b      0t0  TCP (LISTEN)
php-fpm   68819 user    0u  IPv4 0x3d7bc2124072d59b      0t0  TCP (LISTEN)
php-fpm   68820 user    0u  IPv4 0x3d7bc2124072d59b      0t0  TCP (LISTEN)

Config Files

We have a couple of boilerplate configuration files for easy vhost addition.


Some global configurations are stored in global/general.conf and included in some of our vhosts. Go ahead and create it.

$ nano /usr/local/etc/nginx/global/general.conf
# Global configuration http file.
# Designed to be included in any server {} block.
listen 80;

rewrite ^(.+)/+$ $1 permanent;

location = /favicon.ico {
        log_not_found off;
        access_log off;

location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;

# Deny all attempts to access hidden files such as .htaccess or .DS_Store
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
        deny all;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|tar|mid|midi|wav|bmp|rtf)$ {
        expires max;
        log_not_found off;
        access_log off;


If you're using PHP, You'll need to pass the scripts to the FastCGI server listening on the port 9000.
This setup varies between your local and server environment. Obviously, ignore this file if you're never going to use PHP. We've added it in the global/laravel.conf file. You might want to store it in conf.d/php-fpm.conf otherwise, for default addition.

server {

    	location ~ \.php$ {
	        try_files      $uri = 404;
	        fastcgi_index  index.php;
	        include        fastcgi.conf;

Important: You should have cgi.fix_pathinfo = 0; in your php.ini at all times.


Laravel and Nginx play well together. This global conf is tailored to be included in all your Laravel (and Lumen) projects as global/laravel.conf.

# Laravel php rules.
# Designed to be included in any server {} block.
# Use alongside global/general.conf
index index.php;
try_files $uri $uri/ @rewrite;
location @rewrite {
    rewrite ^/(.*)$ /index.php?_url=/$1;

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
    root /usr/share/nginx/html;

location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include /usr/local/etc/nginx/fastcgi.conf;


Wordpress is one of the older kids who's actually more used to play with Apache, so we'll need to set up some specific rules to keep it's behaviour predicatble. Nano global/wordpress.conf:

# WordPress single blog rules.
# Designed to be included in any server {} block.
# Use alongside global/general.conf

index index.php;

# Deny access to any files with a .php extension in the uploads directory
location ~* /(?:uploads|files)/.*\.php$ {
        deny all;

location / {
        try_files $uri $uri/ /index.php?$args;

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;


Your projects

Your vhost file is basicly your website directive, stored in sites-available. The configuration varies wildly, depending on your flavour of choice. We've added some examples.

Create project

As you might have guessed, you should start with creating your project root.

$ mkdir /var/www/html/project

Add vhosts

To add a vhost site, it's a good practise to add a file to sites-available and link it in sites-enabled. We'll describe the file's content later on.

$ nano /usr/local/etc/nginx/sites-available/project
$ ln -s /usr/local/etc/nginx/sites-available/project /usr/local/etc/nginx/sites-enabled/project

The possible configurations (to be included in the above):

Static project

Static projects (eg. a Grunt-generated web app) are usually pre-compiled and nimble in use. This should be reflected in the config file too.

server {
    server_name project.local;
    root /var/www/html/project/staging;

    include /usr/local/etc/nginx/global/general.conf;
    index index.html;

    location / {
        try_files $uri $uri.html $uri/ /index.html;

Ghost project

If you want to add a ghost blog, you just need to proxy your site to the running ghost server.
Your file contents will look like this (port 2368 is your default Ghost port):

server {
    listen 80;

    location / {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header HOST $http_host;
            proxy_set_header X-NginX-Proxy true;
            proxy_redirect off;

Laravel project

To set up a Laravel (or Lumen) project, the nginx configuration file needs a couple of extra lines. We use php-fpm in this example.

server {
    root /var/www/html/project/public;

    # Laravel configuration
    include global/general.conf;
    include global/laravel.conf;

Wordpress project

Who hasn't hosted a Wordpress website before, right? The default vhost entry points to the project root (we use project/www) and include the previously explained configurations.

server {
    root /var/www/html/project/www;

    # Wordpress configuration
    include global/general.conf;
    include global/wordpress.conf;

And as always, reload your nginx files.

$ sudo nginx -s reload


Set your custom local url

It’s very simple to point custom made urls to your local environment. Open /etc/hosts as admin and add a name to your liking:

$ sudo nano /etc/hosts
>       project-api.local

Add some aliases

We've enabled a set of services we probably want to start/stop/reload in the future. OSX has aliases for just that.

$ nano /tmp/.aliases
### Service Aliases - Nginx
alias nginx.logs.error='tail -200f /usr/local/var/log/nginx/error.log'
alias nginx.logs.access='tail -200f /usr/local/var/log/nginx/access.log'

### Service Aliases - PHP-FPM
alias php-fpm.start="sudo launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.php55.plist"
alias php-fpm.stop="sudo launchctl unload -w ~/Library/LaunchAgents/homebrew.mxcl.php55.plist"
alias php-fpm.restart='php-fpm.stop && php-fpm.start'

Let's add it to your user profile and reload.

$ cat /tmp/.aliases >> ~/.profile
$ source ~/.profile