This post describes how to configure Nginx to serve application locally with
pretty domains such as yourappname.dev/
without using over-engineered solutions,
like pow, trying to shield the user from the complexity with extra,
unnecessary abstraction layers.
The instructions are OSX specific, but they can be easily adjusted to *nix systems.
Nginx
Let's start by making sure there is no Apache process running. On OSX, we can use the following command to turn it off:
sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist
Nginx can be installed in various ways, I'll use homebrew:
brew install nginx
Running Nginx on port 80
(or any port below 1024
) requires sudo
command,
otherwise the launch agent will fail. For ports above 1024
, we can just
symlink the launch script as follows:
ln -sfv /usr/local/opt/nginx/*.plist ~/Library/LaunchAgents
launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
For default HTTP port, we need to change listen
value from 8080
to 80
in
/usr/local/etc/nginx.conf
. We can also slightly adjust server_name
,
so both app/
and localhost/
resolve to 127.0.0.1
.
server {
…
listen 80;
server_name app localhost;
…
}
For ports below 1024 we cannot symlink the launch script. It must be copied
to system's /Library/LaunchAgents
.
sudo cp /usr/local/opt/nginx/homebrew.mxcl.nginx.plist /Library/LaunchAgents
In homebrew.mxcl.nginx.plist
, we have to change UserName
to root
. For
convenience, we can also change Label
to nginx
, which will allow us to write:
launchctl start nginx
instead of:
launchctl stop homebrew.mxcl.nginx
Local DNS
Next step is setting up a local DNS. As it is not possible to use wildcards in
the /etc/hosts
file, we cannot specify something like:
127.0.0.1 *.dev.
To get around this problem, we will install a DNS proxy, called DNSMasq:
brew install dnsmasq
The configuration is stored in dnsmasq.conf
under /usr/local/etc/
.
touch /usr/local/etc/dnsmasq.conf
Inside we put the following line:
address=/.dev/127.0.0.1
It says that all *.dev
sites should be redirected to the local IP, i.e.
127.0.0.1
.
Similar to Nginx process, dnsmasq
must be run by root
.
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
Then, we must configure OSX to use our local system as first DNS server. Go to System Preferences and then Network. For DNS configuration put the loopback IP (i.e. 127.0.0.1) as first followed by usual DNS IP addresses:
127.0.0.1
8.8.8.8
8.8.4.4
Now, if we try to ping some any address ending in .dev
, it should return
127.0.0.1
.
$ ping example.dev
PING example.dev (127.0.0.1): 56 data bytes
Note: On Linux it is possible to automatically prefix DNS server list with
127.0.0.1
. It may be useful if you have to use DNS servers provided by DHCP
and/or you change your network often (e.g. home, office). I haven't found similar
solution for OSX.
Virtual Hosts
For virtual hosts configurations, we will follow the convention of two
directories: sites-enabled
and sites-available
, under /usr/local/etc/nginx
.
cd /usr/local/etc/nginx
mkdir sites-available
mkdir sites-enabled
We must slightly adjust nginx.conf
by adding the following line within http
section:
include sites-enabled/*.dev;
Configuration with Backend
We are ready to specify per-app configuration. Let's take a look at the configuration file template.
upstream NAME {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name NAME.dev;
root PATH_TO_PUBLIC
try_files $uri/index.html $uri.html $uri @app;
location @app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://NAME;
}
}
To make it work, we need to adjust it at least in two places i.e. NAME
and
PATH_TO_PUBLIC
. NAME
can be anything, I put there application's name.
PATH_TO_PUBLIC
specifies where assets are, e.g. for Rails it is the path to
public
folder, for Pyramid, the path to static
folder, etc.
The configuration file can be placed in sites-available
, and then, it must be
linked to sites-enabled
, e.g.
ln -s /usr/local/etc/nginx/sites-available/anapp.dev \
/usr/local/etc/nginx/sites-enabled/anapp.dev
Once linked, we have to restart Nginx process. On OSX, it can be done in the following way:
sudo launchctl stop nginx
sudo launchctl start nginx
Configuration without Backend
An additional, configuration file is not necessary for client-side only
applications. We can set up a dynamic application dispatch by using a default
server
directive, located in /usr/local/etc/nginx/nginx.conf
. Nginx will
look for directories in defined base path that match the name from requested
domain, e.g. appname.dev
will match a directory named appname
inside
/Users/zaiste/dev
from the example below:
server {
listen 80;
server_name app localhost .dev;
set $basepath "/Users/zaiste/dev";
set $domain $host;
if ($domain ~ "^(.*)\.dev$") {
set $domain $1;
}
set $rootpath "${domain}";
if (-d $basepath/$domain/public) {
set $rootpath "${domain}/public";
}
if (-f $basepath/$domain/index.html) {
set $rootpath $domain;
}
root $basepath/$rootpath;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
For now on, we can just create a new directory under /Users/zaiste/dev
with a
HTML file, and it will be automatically picked up by Nginx.
Summary
General solutions are just great: we build it from proven components and they are aimed for wider compatibility. Conventions are also great, but not when they try to simplify what's already simple. Using Nginx for serving applications locally is easy to set up and use. Moreover, the proposed solution is language/technology agnostic, it can be used for Rails, Pyramid, AngularJS or any other web technology.