5 min read

A development/production env with https + NGINX + Unicorn

A development environment should as much similar as possible to a real production environment, otherwise how could you test the correctness of some borderline behaviors like routes based on the subdomain name or functionalities like HTTPS redirect?

Consider this post as the part 2 of the previously Procfile post.

Local Certification Authority (CA SSL / HTTPS)

HTTPS is often used in public relevant web services and almost never in local networks (intranet) or amateur websites. The reason is simple: SSL certs cost per year, webapps works even if without SSL (who cares if somebody sniff your local network).
This is very bad, specially for your users and your data, you should provide https protection since the beginning and if you don't want to pay extra money you can alwais became an authority yourself. The users or the testers will require a little extra step but your data will be safer in your local network or over the web.

Step 1: Create your fake authority

This fake authority will sign every certificate you'll need, openssl should be already present in your operative system, anyway I suggest to manage your fake certs as a your code project. So consider to create a directory with a file like $HOME/Code/CAroot/openssl.cnf

This is my openssl.cnf but is better to use as base the one your system provides, copy it inside CAroot. If You get the error "The commonName field needed to be supplied and was missing" it means that a commonName for you domain must be provided.

A single certificate can be valid for multiple and different domain names but it depends by the browser (e.g the wildcard *.dev works on Safari but in Chrome you have to specify myapp.dev)

# Some default files
mkdir private certs newcerts; touch index.txt serial
echo '00' > serial
# Create CA private and public keys
openssl req -config openssl.cnf -new -x509 -keyout private/myca.key -out certs/myca.crt

Step 2: Generate a valid certificate

You should use a different certificate per website but since this is a test environment and we use the convention that every development website is under the .dev domain, we can protect our custom DNS domain with a custom valid certificate for *.dev area

# My app server keys, remember *.dev localhost as commonName
openssl genrsa -des3 -out private/server.key 4096
# password-less key (to not type pwd at every server restart) and certificate request
openssl rsa -in private/server.key -out private/server.key
openssl req -config openssl.cnf -new -key private/server.key -out certs/server.csr
# The CA signs the request
openssl ca -config openssl.cnf -policy policy_anything -out certs/server.crt -infiles certs/server.csr
mv newcerts/01.pem newcerts/server.pem

Install nginx via apt-get or brew and unicorn via gem

This is a fast static web server, the configuration is simple and very similar to the Apache one. Nginx should run with sudo on port 80 (443 for https) of your development machine.

sudo nginx

Normally a computer respond to 1 ip so all your virtual hosts will share the same certs.

# This is example contains the bare mininum to get nginx going with
# Unicorn or Rainbows! servers. Generally these configuration settings
# are applicable to other HTTP application servers (and not just Ruby
# ones), so if you have one working well for proxying another app
# server, feel free to continue using it.
#
# The only setting we feel strongly about is the fail_timeout=0
# directive in the "upstream" block. max_fails=0 also has the same
# effect as fail_timeout=0 for current versions of nginx and may be
# used in its place.
#
# Users are strongly encouraged to refer to nginx documentation for more
# details and search for other example configs.
# you generally only need one nginx worker unless you're serving
# large amounts of static files which require blocking disk reads
worker_processes 1;
# # drop privileges, root is needed on most systems for binding to port 80
# # (or anything < 1024). Capability-based security may be available for
# # your system and worth checking out so you won't need to be root to
# # start nginx to bind on 80
user nobody nogroup; # for systems with a "nogroup"
# user nobody nobody; # for systems with "nobody" as a group instead
# Feel free to change all paths to suite your needs here, of course
pid /tmp/nginx.pid;
error_log /tmp/nginx.error.log;
events {
worker_connections 1024; # increase if you have lots of clients
accept_mutex off; # "on" if nginx worker_processes > 1
# use epoll; # enable for Linux 2.6+
# use kqueue; # enable for FreeBSD, OSX
}
http {
include mime.types;
default_type application/octet-stream;
access_log /tmp/nginx.access.log combined;
sendfile on;
tcp_nopush on; # off may be better for *some* Comet/long-poll stuff
tcp_nodelay off; # on may be better for some Comet/long-poll stuff
gzip on;
gzip_http_version 1.0;
gzip_proxied any;
gzip_min_length 500;
gzip_disable "MSIE [1-6]\.";
upstream app_server {
server 127.0.0.1:3000 fail_timeout=0;
}
# global certs per ip but a cert can sign *.mysite.dev mysite.dev
ssl_certificate /CAroot/certs/server.crt;
ssl_certificate_key /CAroot/private/server.key;
server {
listen 443; #HTTPS
server_name myapp.dev;
ssl on;
# enable one of the following if you're on Linux or FreeBSD
# listen 80 default deferred; # for Linux
# listen 80 default accept_filter=httpready; # for FreeBSD
# If you have IPv6, you'll likely want to have two separate listeners.
# One on IPv4 only (the default), and another on IPv6 only instead
# of a single dual-stack listener. A dual-stack listener will make
# for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"
# instead of just "10.0.0.1") and potentially trigger bugs in
# some software.
# listen [::]:80 ipv6only=on; # deferred or accept_filter recommended
client_max_body_size 4G;
# ~2 seconds is often enough for most folks to parse HTML/CSS and
# retrieve needed images/icons/frames, connections are cheap in
# nginx so increasing this is generally safe...
keepalive_timeout 5;
# path for static files
root /myapp/public;
# Prefer to serve static files directly from nginx to avoid unnecessary
# data copies from the application server.
#
# try_files directive appeared in in nginx 0.7.27 and has stabilized
# over time. Older versions of nginx (e.g. 0.6.x) requires
# "if (!-f $request_filename)" which was less efficient:
# http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127
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://app_server;
}
# Rails error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /myapp/public;
}
}
}

Everytime you change nginx.conf rememeber to restart the daemon (or sudo nginx -s stop )

For further optimizations like unicorn.sock instead of http://127.0.0.1:3000 see here.

Install unicorn

Unicorn is a ruby specific web server, you could also run other application servers like Thin or Webrick, the important is to keep the application server on the same port specified in the nginx.conf file.

Ta daahh, https://myapp.dev with a valid certificate

As normal user run "foreman start -f Procfile.dev", You should have a complete Procfile as we have seen previosly and your development (almost production) environment is ready. The first time you could see an error but if you declare that you trust of myca.crt you won't see that error anymore.

Your app with https and a realistic domain

# comprehensive Procfile.dev for myapp
redis: redis-server config/redis.development.conf
postgres: postgres -D /usr/local/var/postgres
web: bundle exec unicorn -p3000
tail: tail -f log/development.log
guard: guard --no-bundler-warning
#echo: echo $ECHO_TEST

You don't have to worry anymore to keep Mysql, Posgres, MongoDB, Redis, guard (Live Reload), coffeescript and sCSS compilation,.. running when You don't use them in development, because every Ruby or Node.JS projects and probably others can easly recreate the needed environment via your Procfile or Procfile.dev
This is very important to keep the development environment synchronized in the team or switching easly from a project to another one without leaving daemons and ports busy.