464 lines
15 KiB
Markdown
464 lines
15 KiB
Markdown
+++
|
|
template = "article.html"
|
|
title = "Hosting different kinds of apps on nginx"
|
|
date = 2014-10-15T10:55:00+02:00
|
|
description = "An introduction to nginx as a web server and reverse proxy, covering how to host static sites, PHP applications, and Node.js apps."
|
|
|
|
[taxonomies]
|
|
tags = ["nginx", "web"]
|
|
+++
|
|
|
|
## Engine what?
|
|
|
|
Nginx (engine-x) is a web server and reverse proxy for web and mail protocols
|
|
(HTTP, HTTPS, SMTP, POP3 and IMAP). It has been first released in 2004, and its
|
|
usage keeps growing ever since (according to
|
|
[Netcraft](http://news.netcraft.com/archives/2014/08/27/august-2014-web-server-survey.html),
|
|
it was hosting 14.47% of active sites in August 2014).
|
|
|
|
It's capable of hosting many kinds of applications:
|
|
|
|
- static HTML pages
|
|
- PHP, using [PHP-FPM](http://en.wikipedia.org/wiki/PHP#PHPFPM)
|
|
- Ruby on Rails and any kind of Rack-based Ruby application, using
|
|
[Phusion Passenger](https://www.phusionpassenger.com/)
|
|
- proxying requests to another webserver (e.g. a software launching its own web
|
|
server, like [Kodi](http://xbmc.org/))
|
|
|
|
<!--more-->
|
|
|
|
## Set up the bases
|
|
|
|
The architecture described in this post is pretty simple:
|
|
|
|
- a default virtual host (vhost) for the top-level domain name, also catching
|
|
requests to unknown sub-domains
|
|
- different applications hosted on sub-domains
|
|
- some vhosts will be HTTPS-only, some will offer it without being mandatory
|
|
- enabling or disabling a vhost must be easy
|
|
|
|
### Installing nginx
|
|
|
|
Nginx uses static modules, enabled or disabled at compile-time. It's important
|
|
to decide what you need before installing nginx. The only non-default module
|
|
used in this post is Passenger, needed to host Rack-based applications.
|
|
Everything else will work without it.
|
|
|
|
Nginx works on any decent \*nix. It's probably available in your OS
|
|
repositories. If it's not, please refer to the
|
|
[official installation guide](http://wiki.nginx.org/Install). On Archlinux,
|
|
a package is available on
|
|
[AUR](https://aur.archlinux.org/packages/nginx-passenger) including the
|
|
Passenger module:
|
|
|
|
`yaourt -S nginx-passenger`
|
|
|
|
### Configuration
|
|
|
|
Once nginx is installed, we need to configure a basic configuration. I'll refer
|
|
to the configuration root directory as `$CONFDIR`. It's usually `/etc/nginx/`.
|
|
|
|
Note that nginx needs to be restarted to reflect any configuration change.
|
|
|
|
#### Directory structure
|
|
|
|
To ease the configuration, we'll split it across three folders:
|
|
|
|
- `$CONFDIR` will contain all the general files (PHP configuration, main nginx
|
|
configuration file…)
|
|
- `$CONFDIR/ssl` will contain the SSL certificates
|
|
- `$CONFDIR/vhosts` will contain our vhosts definitions
|
|
|
|
#### Main configuration file
|
|
|
|
Here's the basic configuration file we'll start with:
|
|
|
|
{{ filename(body="$CONFDIR/nginx.conf") }}
|
|
|
|
```nginx
|
|
worker_processes auto;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
fastcgi_send_timeout 600s;
|
|
fastcgi_read_timeout 600s;
|
|
include mime.types;
|
|
default_type application/octet-stream;
|
|
sendfile on;
|
|
keepalive_timeout 0;
|
|
gzip on;
|
|
index index.html index.htm;
|
|
client_max_body_size 2048m;
|
|
|
|
server {
|
|
listen 0.0.0.0;
|
|
server_name enoent.fr;
|
|
|
|
access_log /var/log/nginx/localhost.access_log;
|
|
error_log /var/log/nginx/localhost.error_log info;
|
|
|
|
root /srv/http/localhost;
|
|
}
|
|
}
|
|
```
|
|
|
|
This file sets up an nginx instance with some decent settings (enable gzip, use
|
|
`index.html` or `index.htm` as default index pages…), and defines our default
|
|
vhost. It answers to every request targeting the hostname _enoent.fr_. It will
|
|
serve static pages found in `/srv/http/localhost`.
|
|
|
|
## SSL support
|
|
|
|
As mentioned earlier, we'll have two SSL behaviours depending on the vhost:
|
|
|
|
- SSL is offered, but not mandatory (vhost answers to both HTTP and HTTPS)
|
|
- SSL is offered, and mandatory (vhost answers on HTTPS, and redirect to HTTPS
|
|
when it receives a request on HTTP)
|
|
|
|
We will need two files to define these two behaviours. One of them will have
|
|
to be included in every vhost, depending on the SSL politic we want for this
|
|
specific vhost.
|
|
|
|
### Shared configuration
|
|
|
|
Here we go for the first configuration file:
|
|
|
|
{{ filename(body="$CONFDIR/ssl_opt.conf") }}
|
|
|
|
```nginx
|
|
ssl_certificate_key /etc/nginx/ssl/ssl-decrypted.key;
|
|
add_header Strict-Transport-Security max-age=31536000;
|
|
ssl_prefer_server_ciphers on;
|
|
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:RC4-SHA:AES256-GCM-SHA384:AES256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
keepalive_timeout 70;
|
|
```
|
|
|
|
You can obviously adapt this file to your specific needs. It defines:
|
|
|
|
- the SSL key used (`/etc/nginx/ssl/ssl-decrypted.key`)
|
|
- a default max-age header
|
|
- a list of accepted SSL ciphers
|
|
- session, cache and keepalive durations
|
|
|
|
The other file will define the exact same settings, adding just one directive:
|
|
the SSL is mandatory. Instead of copy and paste all of this, here's what we
|
|
can do:
|
|
|
|
{{ filename(body="$CONFDIR/ssl.conf") }}
|
|
|
|
```nginx
|
|
include ssl_opt.conf;
|
|
ssl on;
|
|
```
|
|
|
|
### Enabling SSL for a vhost
|
|
|
|
To enable SSL on a vhost, we'll need to make three or four modifications to the
|
|
vhost definition, depending on the SSL policy.
|
|
|
|
#### Non-mandatory SSL
|
|
|
|
If the SSL is not mandatory, we'll need to:
|
|
|
|
- enable listening on port 443 in addition to the default 80
|
|
- choose the certificate we want to use
|
|
- include the SSL policy file
|
|
|
|
Here's how it translates, for our first vhost defined earlier:
|
|
|
|
{{ filename(body="$CONFDIR/nginx.conf (server block only)") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0:80;
|
|
listen 0.0.0.0:443 ssl;
|
|
server_name enoent.fr;
|
|
|
|
access_log /var/log/nginx/localhost.access_log;
|
|
error_log /var/log/nginx/localhost.error_log info;
|
|
|
|
root /srv/http/localhost;
|
|
|
|
ssl_certificate /etc/nginx/ssl/enoent.fr.crt;
|
|
include ssl_opt.conf;
|
|
}
|
|
```
|
|
|
|
#### Mandatory SSL
|
|
|
|
If the SSL is mandatory, we'll need to:
|
|
|
|
- enable listening on port 443 __instead of__ the default 80
|
|
- choose the certificate we want to use
|
|
- include the SSL policy file
|
|
- redirect HTTP requests to HTTPS
|
|
|
|
And here's the result for our first vhost:
|
|
|
|
{{ filename(body="$CONFDIR/nginx.conf (server block only)") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0:80;
|
|
server_name enoent.fr;
|
|
rewrite ^ https://$server_name$request_uri? permanent;
|
|
}
|
|
|
|
server {
|
|
listen 0.0.0.0:443 ssl;
|
|
server_name enoent.fr;
|
|
|
|
access_log /var/log/nginx/localhost.access_log;
|
|
error_log /var/log/nginx/localhost.error_log info;
|
|
|
|
root /srv/http/localhost;
|
|
|
|
ssl_certificate /etc/nginx/ssl/enoent.fr.crt;
|
|
include ssl.conf;
|
|
}
|
|
```
|
|
|
|
The first `server` block is here to do the redirection, as our inital server
|
|
only listens on port 443.
|
|
|
|
## Virtual hosts
|
|
|
|
As we saw in the [SSL](#ssl-support) part, we can define as many `server` blocks
|
|
as we want. Each of them is able to respond to requests targeting different
|
|
hostnames or ports. We also saw earlier the `include` directive, allowing us to
|
|
include a file in another.
|
|
|
|
With this in mind, it's pretty simple to set up a vhost pool from which we can
|
|
enable or disable some of them easily. Simply put a file per vhost in a
|
|
directory, and include it to enable the corresponding vhost, or remove the
|
|
include to disable it.
|
|
|
|
Here are some templates for different virtual hosts, each one containing only
|
|
the minimum (no SSL-specific settings, for example).
|
|
|
|
### Static HTML
|
|
|
|
We already saw earlier how to define a virtual host when we set up our main
|
|
`nginx.conf` file:
|
|
|
|
{{ filename(body="$CONFDIR/vhosts/static_html.conf") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0;
|
|
server_name enoent.fr;
|
|
|
|
access_log /var/log/nginx/localhost.access_log;
|
|
error_log /var/log/nginx/localhost.error_log info;
|
|
|
|
root /srv/http/localhost;
|
|
}
|
|
```
|
|
|
|
The only interesting directive here is the `root` one. It will map the root of
|
|
the web server to this local folder. A request for
|
|
`http://enoent.fr/my_awesome_page.html` will return the content of
|
|
`/srv/http/localhost/my_awesome_page.html`.
|
|
|
|
### Reverse proxy
|
|
|
|
A reverse proxy may be useful when you have a web server already running, and
|
|
want to expose it somewhere else. Let's say we have a NAS on our local network,
|
|
its web ui being accessible on `http://nas.local:8080`, and we want to expose it
|
|
on `http://nas.enoent.fr`, on the default HTTP port:
|
|
|
|
{{ filename(body="$CONFDIR/vhosts/reverse_proxy.conf") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0;
|
|
server_name nas.enoent.fr;
|
|
|
|
access_log /var/log/nginx/nas.access_log;
|
|
error_log /var/log/nginx/nas.error_log info;
|
|
|
|
location / {
|
|
proxy_headers_hash_max_size 1024;
|
|
proxy_headers_hash_bucket_size 128;
|
|
proxy_pass http://nas.local:8080;
|
|
}
|
|
}
|
|
```
|
|
|
|
The `location /` block here defines a behaviour for all requests matching
|
|
`nas.enonet.fr/*`. In our case, that's all of them, as we only have one
|
|
`location` block.
|
|
|
|
Inside of it, we have some settings for our reverse proxy (maximum headers
|
|
size), and the really interesting part: the `proxy_pass` entry, which defines
|
|
where are redirected the incoming requests.
|
|
|
|
### PHP
|
|
|
|
To allow PHP applications to work, we'll need a PHP interpreter. More
|
|
specifically, we'll use [PHP-FPM](http://php-fpm.org/). PHP-FPM is a FastCGI PHP
|
|
processor. It's a daemon listening on a socket, waiting for PHP scripts, and
|
|
returning the PHP output. The configuration of PHP-FPM is out of this article
|
|
scope, but we'll need to have it running, and note where it can be acceded (a
|
|
local Unix socket, or a TCP socket, either remote or local).
|
|
|
|
We need to define a behaviour for PHP files, telling nginx how to process them:
|
|
|
|
{{ filename(body="$CONFDIR/php.conf") }}
|
|
|
|
```nginx
|
|
location ~ ^(.+\.php)(.*)$ {
|
|
include fastcgi_params;
|
|
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
|
|
fastcgi_split_path_info ^(.+\.php)(.*)$;
|
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
|
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
|
|
}
|
|
```
|
|
|
|
This file specifies how files with a `.php` extension will be processed. Nginx
|
|
will split the arguments and filename, and pass them to the PHP-FPM socket,
|
|
which here is listening on the Unix socket at `/run/php-fpm/php-fpm.sock`. For a
|
|
TCP socket, the line 3 would need to be changed to something like this:
|
|
|
|
{{ filename(body="$CONFDIR/php.conf - TCP socket") }}
|
|
|
|
```nginx
|
|
location ~ ^(.+\.php)(.*)$ {
|
|
include fastcgi_params;
|
|
fastcgi_pass 127.0.0.1:9000;
|
|
fastcgi_split_path_info ^(.+\.php)(.*)$;
|
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
|
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
|
|
}
|
|
```
|
|
|
|
Next, to define a vhost hosting some PHP scripts, we simply need to include this
|
|
file:
|
|
|
|
{{ filename(body="$CONFDIR/vhosts/php.conf") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0;
|
|
server_name my-awesome-php-app.enoent.fr;
|
|
|
|
access_log /var/log/nginx/my-awesome-php-app.access_log;
|
|
error_log /var/log/nginx/my-awesome-php-app.error_log info;
|
|
|
|
root /srv/http/localhost;
|
|
include php.conf;
|
|
}
|
|
```
|
|
|
|
### Rack
|
|
|
|
Rack-based applications need [Passenger](https://www.phusionpassenger.com/) to
|
|
work. Passenger is pretty similar to PHP-FPM, but its configuration with nginx
|
|
is easier. Note that it needs to be built in nginx.
|
|
|
|
To enable it, we need to tweak our `http` block in `$CONFDIR/nginx.conf` to
|
|
specify our Passenger root directory and path to the `ruby` executable:
|
|
|
|
{{ filename(body="$CONFDIR/nginx.conf") }}
|
|
|
|
```nginx
|
|
worker_processes auto;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
fastcgi_send_timeout 600s;
|
|
fastcgi_read_timeout 600s;
|
|
include mime.types;
|
|
default_type application/octet-stream;
|
|
sendfile on;
|
|
keepalive_timeout 0;
|
|
gzip on;
|
|
index index.html index.htm;
|
|
client_max_body_size 2048m;
|
|
|
|
passenger_root /usr/lib/passenger;
|
|
passenger_ruby /usr/bin/ruby;
|
|
}
|
|
```
|
|
|
|
Once this is done, to set up a Rack vhost, we just need to enable Passenger on
|
|
it, and define which environment we want to use for Rails applications:
|
|
|
|
{{ filename(body="$CONFDIR/vhosts/rack.conf") }}
|
|
|
|
```nginx
|
|
server {
|
|
listen 0.0.0.0;
|
|
server_name rack-app.enoent.fr;
|
|
|
|
access_log /var/log/nginx/rack-app.access_log;
|
|
error_log /var/log/nginx/rack-app.error_log info;
|
|
|
|
root /srv/http/rack-app/public;
|
|
|
|
passenger_enabled on;
|
|
rails_env production;
|
|
}
|
|
```
|
|
|
|
Note that the directory set as `root` must match the `public` directory of your
|
|
Rack application.
|
|
|
|
### Using all of these templates
|
|
|
|
Once we have written our vhosts definition files in `$CONFDIR/vhosts`, enabling
|
|
or disabling one is really easy. We just need to include the corresponding file
|
|
in the `http` block of our `$CONFDIR/nginx.conf` file:
|
|
|
|
{{ filename(body="$CONFDIR/nginx.conf") }}
|
|
|
|
```nginx
|
|
worker_processes auto;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
fastcgi_send_timeout 600s;
|
|
fastcgi_read_timeout 600s;
|
|
include mime.types;
|
|
default_type application/octet-stream;
|
|
sendfile on;
|
|
keepalive_timeout 0;
|
|
gzip on;
|
|
index index.html index.htm;
|
|
client_max_body_size 2048m;
|
|
|
|
passenger_root /usr/lib/passenger;
|
|
passenger_ruby /usr/bin/ruby;
|
|
|
|
include vhosts/static_html.conf;
|
|
include vhosts/reverse_proxy.conf;
|
|
include vhosts/php.conf;
|
|
include vhosts/rack.conf;
|
|
}
|
|
```
|
|
|
|
Obviously, if we don't include any Rack vhost, we don't need the lines 20 and
|
|
21 as they are Passenger-specific.
|
|
|
|
We can name our vhosts files whatever we like, and create as many as we need.
|
|
Having the general configuration split in reusable files allows an easy
|
|
maintenance. When deploying a new PHP application, we just need to include
|
|
`php.conf`, and not think "where is my PHP-FPM listening again?". It just works.
|