This is a step by step guide on how to setup your local development environment to serve a Rails (or any Rack) app with Pow and NGINX over HTTPS.
To begin i’m going to assume you’re using OSX (probably Mountain Lion), HomeBrew and rbenv. For other setups ymmv.
What is Pow?
Pow (a 37signals project) runs as your user on an unprivileged port, and includes both an HTTP and a DNS server. The installation process sets up a firewall rule to forward incoming requests on port 80 to Pow. It also sets up a system hook so that all DNS queries for a special top-level domain (.dev) resolve to your local machine.
For more information on Pow, read the intro or browse the manual.
Why use Pow?
- Easily host multiple Rack apps on your local machine under different domains e.g http://your-app.dev
- Configure local apps to run under SSL (explained below)
- Use the xip.io domain to visit your app from other devices on your local network
- Serve requests with multiple Pow workers
- Easy to configure, customise and works with multiple Rubies (via rbenv or RVM) and Bundler
Installing Pow
Install Pow with this command;
curl get.pow.cx | sh
Next create a symlink in ~/.pow to your app’s base directory like so;
ln -s /full/path/to/your-app ~/.pow/your-app
Zsh Rbenv users
If you are running zsh with rbenv you may need to follow these instructions and add a PATH export to your ~/.powconfig file like so;
export PATH=`brew --prefix rbenv`/shims:`brew --prefix rbenv`/bin:$PATH
Then restart the pow process with;
touch ~/.pow/restart.txt
This should be enough for you to see your app at http://your-app.dev. The next steps assume you have this working.
Installing & configuring NGINX
Install NGINX via brew;
brew install nginx
By default brew will install and configure NGINX to listen on port 8080. We need to run it on port 443 (decrypting SSL and proxy-ing all requests through to our Pow server).
Using this config file we can set up NGINX with some good defaults, and tell it to look for sites in `/usr/local/etc/nginx/sites-enabled`.
mkdir -p /usr/local/etc/nginx/sites-enabled
mkdir -p /usr/local/etc/nginx/sites-available
curl -0 https://gist.github.com/matthutchinson/5815393/raw/9845b99433a0e1ebd2763b264643fe308ea74b4f/nginx.conf > /usr/local/etc/nginx/nginx.conf
Next we create our site configuration in `/usr/local/etc/nginx/sites-available`
curl -0 https://gist.github.com/matthutchinson/5822750/raw/4790d7030d55a955b3c3a90fe2669b81235b95d2/your-app.dev > /usr/local/etc/nginx/sites-available/your-app.dev
Edit this file, setting the root (public) directory and replacing `your-app.dev` throughout. Finally symlink it into sites-enabled;
ln -s /usr/local/etc/nginx/sites-available/your-app.dev /usr/local/etc/nginx/sites-enabled/your-app.dev
Generating an SSL Cert
You might have noticed that the config file you just edited referenced an SSL cert that we have not yet created.
In a tmp directory, let’s use this handy gist to generate it and move the cert files into place;
curl https://gist.github.com/matthutchinson/5815498/raw/9da28acd6bf0ce1666f39cc0351dd5eee764be8b/nginx_gen_cert.rb > /tmp/nginx_gen_cert.rb
ruby /tmp/nginx_gen_cert.rb your-app.dev
rm /tmp/nginx_gen_cert.rb
You should now have SSL cert files for your app properly configured and contained in `/usr/local/etc/nginx/ssl`.
Trying it out
Thats it! To start NGINX (since we are listing on port 443) you need to run it with sudo;
sudo nginx
Visit https://your-app.dev/ now to see your app served via HTTPS.
Controlling things
The web app can be restarted by running `touch tmp/restart.txt` in the base directory. And you can control NGINX from the command line with flags like this;
sudo nginx -s stop
sudo nginx -s reload
Debugging with pry-remote
Since your app is now running in Pow’s own worker processes, to operate a live debugger you will need to use something like pry-remote.
First add the pry and pry-remote gems to your Gemfile (and `bundle install`). Then to introduce a breakpoint use this in your code;
binding.remote_pry
Fire off a request and when it stalls, run this command from your app’s base directory;
bundle exec pry-remote
A connection to the running worker process is established and you should be presented with a regular pry prompt. You can read more about pry-remote and pry here.
Further steps
Your browser may complain about not trusting your new SSL cert — we can fix that!
Restart or open Safari and visit https://your-app.dev. Click ‘Show Certificate’ from the SSL warning dialog. Choose the ‘Trust’ drop-down and select ‘Always Trust’. This adds your newly generated cert to the OSX keychain.
Setting up more sites is easy, just add them with a similar NGINX site config, generate an SSL cert (using the helper script again) and symlink things into place.
You can play with Pow’s configuration (e.g timeouts, workers) by defining ENV variables in ~/.powconfig, for example;
export POW_DOMAINS=dev,test
export POW_DST_PORT=80
export POW_TIMEOUT=300
export POW_WORKERS=3
Any change to ~/.powconfig needs a Pow restart;
touch ~/.pow/restart.txt
I hope this guide has been useful. Comments or questions are always welcome. (Pow artwork by Jamie Dihiansan)
7 comments so far
Adrian Madrid Jun 27, 2013
Where did port 20559 came from? (nginx vhost file)
Matt Jun 27, 2013
Sorry I should have mentioned why. Port 20559 is where the Pow webserver is listening.
When you install Pow, along with the web server, it also installs a local DNS server, that redirects all incoming traffic on port 80 to port 20559. In the setup above, we do the same using NGINX to proxy traffic from 443 to Pow at 20559.
You can browse to http://localhost:20559 to see it
Chris Cressman Jul 18, 2013
Works great. Thanks!
Under “Generating An SSL Cert”, the curl command should be:
curl https://gist.github.com/matthutchinson/5815498/raw/9da28acd6bf0ce1666f39cc0351dd5eee764be8b/nginx_gen_cert.rb >> nginx_gen_cert.rb
Matt Jul 26, 2013
Thanks Chris, i’ve amended the guide!
David Silva Aug 04, 2013
Well done Matt, thanks for this one!
ahmad seleem May 20, 2014
You forgot the log files
ahmad seleem May 21, 2014
I commented on the gist files: trying to fix the logs error.