NGINX Unit

配置§

应用§

使用 API 在 Unit 配置的 applications 段里面定义一个 JSON 对象,这个 JSON 对象定义了一个应用,以及这个应用的一些特征,包括应用的编程语言、运行的进程数、应用文件存放路径,以及一些与语言相关的配置。

这里有个示例配置:名为 blogs 的应用,运行20个进程,应用文件存放在 /www/blogs/scripts。默认未指定运行文件的话,将使用 index.php

{
    "blogs": {
        "type": "php",
        "processes": 20,
        "root": "/www/blogs/scripts",
        "index": "index.php"
    }
}

监听器§

由于应用将通过 HTTP 进行访问,这样我们至少需要定义一个监听器,监听器相关配置放在 Unit 配置的 listeners 段里。一个监听器包含 IP 和监听端口,IP 可以是完全地址,例如 127.0.0.1:8300 或者使用通配符,例如 *:8300

如下示例,blogs 应用将在8300端口接收请求:

{
    "*:8300": {
        "pass": "applications/blogs"
    }
}

针对每种编程语言的完整配置,可以参见 Application Objects

Configuration Management§

最小化配置§

为了运行一个应用,配置必须包含至少一个监听器和应用,如下所示:

{
    "listeners": {
        "*:8300": {
            "pass": "applications/blogs"
        }
    },

    "applications": {
        "blogs": {
            "type": "php",
            "processes": 20,
            "root": "/www/blogs/scripts",
            "index": "index.php"
        }
    }
}

Creating Objects§

To create a configuration object, specify the JSON data for it in the body of a PUT request. To reduce errors, it makes sense to write the JSON data in a file and specify the file path with the -d option to the curl command.

通过上传 start.json 文件里的 JSON 数据,来创建一个初始的配置:

# curl -X PUT -d @/path/to/start.json  \
       --unix-socket /path/to/control.unit.sock http://localhost/config/

wiki.json 文件中创建了一个名为 wiki 的应用对象:

# curl -X PUT -d @/path/to/wiki.json  \
       --unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki

wiki.json 文件内容如下:

{
    "type": "python",
    "processes": 10,
    "module": "wsgi",
    "user": "www-wiki",
    "group": "www-wiki",
    "path": "/www/wiki"
}

Displaying Objects§

通过 curl 命令来访问特定的 URL,可以显示一个配置对象。

显示完整的配置:

# curl --unix-socket /path/to/control.unit.sock http://localhost/config/
{
    "listeners": {
        "*:8300": {
            "pass": applications/blogs"
        }
    },

    "applications": {
        "blogs": {
            "type": "php",
            "user": "nobody",
            "group": "nobody",
            "root": "/www/blogs/scripts",
            "index": "index.php"
        }
    }
}

显示 wiki 应用的配置对象:

# curl --unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki
{
    "type": "python",
    "processes": 10,
    "module": "wsgi",
    "user": "www",
    "group": "www",
    "path": "/www/wiki"
}

Modifying Objects§

通过使用 curl 命令,并指定包含配置对象的JSON 文件路径为 -d 参数 ,使用 PUT 可以修改配置对象。

修改 wiki-dev 在监听器 *:8400 上的 application 对象:

# curl -X PUT -d '"wiki-dev"' --unix-socket /path/to/control.unit.sock  \
       'http://localhost/config/listeners/*:8400/application'
{
    "success": "Reconfiguration done."
}

修改 blogs 应用的 root 对象为 /www/blogs-dev/scripts

# curl -X PUT -d '"/www/blogs-dev/scripts"'  \
       --unix-socket /path/to/control.unit.sock  \
       http://localhost/config/applications/blogs/root
{
    "success": "Reconfiguration done."
}

Deleting Objects§

使用 curl 命令、 DELETE 请求来访问需要删除对象的 URL。

删除 *:8400 监听器:

# curl -X DELETE --unix-socket /path/to/control.unit.sock  \
       'http://localhost/config/listeners/*:8400'
{
    "success": "Reconfiguration done."
}

Settings Object§

Unit has a global settings configuration object that stores instance-wide preferences. Its http option fine-tunes the handling of HTTP requests from the clients:

Option

描述

header_read_timeout (optional)

Maximum number of seconds to read the header of a client’s request. If Unit doesn’t receive the entire header from the client within this interval, it responds with a 408 Request Timeout error.

The default value is 30.

body_read_timeout (optional)

Maximum number of seconds to read data from the body of a client’s request. It limits the interval between consecutive read operations, not the time to read the entire body. If Unit doesn’t receive any data from the client within this interval, it responds with a 408 Request Timeout error.

The default value is 30.

send_timeout (optional)

Maximum number of seconds to transmit data in the response to a client. It limits the interval between consecutive transmissions, not the entire response transmission. If the client doesn’t receive any data within this interval, Unit closes the connection.

The default value is 30.

idle_timeout (optional)

Maximum number of seconds between requests in a keep-alive connection. If no new requests arrive within this interval, Unit responds with a 408 Request Timeout error and closes the connection.

The default value is 180.

max_body_size (optional)

Maximum number of bytes in the body of a client’s request. If the body size exceeds this value, Unit responds with a 413 Payload Too Large error and closes the connection.

The default value is 8388608 (8 MB).

示例:

{
    "settings": {
        "http": {
            "header_read_timeout": 10,
            "body_read_timeout": 10,
            "send_timeout": 10,
            "idle_timeout": 120,
            "max_body_size": 6291456
        }
    }
}

监听器对象§

Option

描述

application

App name: "application": "qwk2mart". Mutually exclusive with pass.

警告

This object is deprecated. Please update your configurations to use pass instead.

pass Qualified app or route name: "pass": "routes/route66", "pass": "applications/qwk2mart". Mutually exclusive with application.
tls (optional) SSL/TLS configuration. Set its only option, certificate, to enable secure communication via the listener. The value must reference a certificate chain that you have uploaded earlier. For details, see SSL/TLS and Certificates.

Example:

{
    "pass": "applications/blogs",
    "tls": {
        "certificate": "blogs-cert"
    }
}

Routes§

Unit configuration offers a routes object to enable elaborate internal routing between listeners and apps. Listeners pass requests to routes or directly to apps. Requests are matched against route step conditions; a request fully matching a step’s condition is passed to the app or the route that the step specifies.

The routes object may contain a single anonymous route array:

{
     "listeners": {
         "*:8300": {
             "pass": "routes"
         }
     },

     "routes": [ "simply referred to as routes" ]
}

Another option is one or more named route arrays:

{
     "listeners": {
         "*:8300": {
             "pass": "routes/main"
         }
     },

     "routes": {
         "main": [ "named route, qualified name: routes/main" ],
         "route66": [ "named route, qualified name: routes/route66" ]
     }
}

Route Object§

Route array contains anonymous objects, or steps; a request passed to a route traverses them sequentially. Steps have the following options:

Option

描述

match (optional)

Object that defines the step condition.

  • If the request fits the match condition, the step’s pass is followed.
  • If the request doesn’t match a step, Unit proceeds to the next step of the route.
  • If the request doesn’t match any steps, a 404 “Not Found” response is returned.

See below for condition matching details.

action and pass Route’s destination; identical to pass in a listener. If you omit match, requests are passed unconditionally; to avoid issues, use no more than one such step per route, placing it last.

An example:

{
    "routes": [
        {
            "match": {
                "host": "example.com",
                "uri": "/admin/*"
            },

            "action": {
                "pass": "applications/php5_app"
             }
        },
        {
            "action": {
                "pass": "applications/php7_app"
             }
        }
     ]
}

A more elaborate example with chained routes:

{
    "routes": {
        "main": [
            {
                "match": {
                    "host": [ "www.example.com", "example.com" ]
                },

                "action": {
                    "pass": "routes/site"
                }
            },
            {
                "match": {
                    "host": "blog.example.com"
                },

                "action": {
                    "pass": "applications/blog"
                }
            }
        ],

        "site": [ "..." ]
    }
}

Condition Matching§

The match condition in a step comprises request property names and corresponding patterns:

{
    "match": {
        "request_property1": "pattern",
        "request_property2": ["pattern", "pattern", "..." ]
    },

    "action": {
        "pass": "..."
     }
}

To fit a step’s condition, the request must match all properties listed in it. Available options:

Option

描述

host Request host from the Host header field without port number, normalized by removing the trailing period (if any); case-insensitive.
method Request method from the request line; case-insensitive.
uri Request URI path without arguments, normalized by decoding the “%XX” sequences, resolving relative path references (”.” and ”..”), and compressing adjacent slashes into one; case-sensitive.

Patterns must be exact matches; they also support wildcards (*) and negations (!):

  • A wildcard matches zero or more arbitrary characters; pattern can start with it, end with it, or both.
  • A negation restricts specific patterns; pattern can only start with it.

To be a match against the patterns listed in a condition, the property must meet two requirements:

  • If there are patterns without negation, at least one of them matches.
  • No negation-based patterns match.

注解

This type of matching can be explained with set operations. Suppose set U comprises all possible values of a property; set P comprises strings that match any patterns without negation; set N comprises strings that match any negation-based patterns. In this scheme, the matching set will be:

UP \ N if P ≠ ∅
U \ N if P = ∅

A few examples:

{
    "host": "*.example.com"
}

Only subdomains of example.com will match.

{
    "host": ["*.example.com", "!www.example.com"]
}

Here, any subdomains of example.com will match except www.example.com.

{
    "method": ["!HEAD", "!GET"]
}

Any methods will match except HEAD and GET.

You can also combine special characters in a pattern:

{
    "uri": "!*/api/*"
}

Here, any URIs will match except ones containing /api/.

If all properties match or you omit the condition, Unit routes the request where pass points to:

{
    "match": {
        "host": [ "*.example.com", "!php7.example.com" ],
        "uri": [ "/admin/*", "/store/*" ],
        "method": "POST"
    },

    "action": {
        "pass": "applications/php5_app"
     }
}

Here, all POST requests for URIs prefixed with /admin/ or /store/ within any subdomains of example.com (except php7) are routed to php5_app.

应用对象§

每个应用对象都有一些可以指定的通用选项,不管是何种应用类型。

通用选项如下:

Option

描述

type

Type of the application: external (Go and Node.js), java, perl, php, python, or ruby.

Except with external, you can detail the runtime version: "type": "python 3", "type": "python 3.4", or even "type": "python 3.4.9rc1". Unit searches its modules and uses the latest matching one, reporting an error if none match.

For example, if you have installed only one PHP 7 module, 7.1.9, it will match "php", "php 7", "php 7.1", and "php 7.1.9". If you install two PHP modules, 7.0.2 and 7.0.23, and prefer to use 7.0.2, set "type": "php 7.0.2". If you supply "php 7", PHP 7.0.23 will be used as the latest version available.

limits (optional) An object that accepts two integer options, timeout and requests. Their values restrict the life cycle of an application process. For details, see Request Limits.
processes (optional)

An integer or an object. Integer value configures a static number of application processes. Object accepts dynamic process management settings: max, spare, and idle_timeout. For details, see Process Management.

The default value is 1.

working_directory (可选)

应用的工作目录。如果没指定的情况下,将使用 Unit 守护进程的工作目录。

user (可选)

运行应用的用户名。没指定的情况下,将使用 nobody

group (可选)

运行应用的组名。没指定的情况下,将使用用户的主组名。

environment (optional) Environment variables to be used by the application.

示例:

{
    "type": "python 3.6",
    "processes": 16,
    "working_directory": "/www/python-apps",
    "path": "blog",
    "module": "blog.wsgi",
    "user": "blog",
    "group": "blog",
    "limits": {
        "timeout": 10,
        "requests": 1000
    },

    "environment": {
        "DJANGO_SETTINGS_MODULE": "blog.settings.prod",
        "DB_ENGINE": "django.db.backends.postgresql",
        "DB_NAME": "blog",
        "DB_HOST": "127.0.0.1",
        "DB_PORT": "5432"
    }
}

Depending on the type of the application, you may need to configure a number of additional options. In the example above, Python-specific options path and module are used.

Process Management and Limits§

Application process behavior in Unit is described by two configuration options, limits and processes.

Request Limits§

The limits object accepts two options:

Option

描述

timeout (optional) Request timeout in seconds. If an application process exceeds this limit while processing a request, Unit terminates the process and returns an HTTP error to the client.
requests (optional) Maximum number of requests Unit allows an application process to serve. If this limit is reached, Unit terminates and restarts the application process. This allows to mitigate application memory leaks or other issues that may accumulate over time.

Process Management§

The processes option offers choice between static and dynamic process management model. If you provide an integer value, Unit immediately launches the given number of application processes and maintains them statically without scaling.

Unit also supports a dynamic prefork model for processes that is enabled and configured with the following parameters:

Option

描述

max

Maximum number of application processes that Unit will maintain (busy and idle).

The default value is 1.

spare

Minimum number of idle processes that Unit will reserve for the application when possible. When Unit starts an application, spare idle processes are launched. As requests arrive, Unit assigns them to existing idle processes and forks new idle ones to maintain the spare level if max permits. When processes complete requests and turn idle, Unit terminates extra ones after a timeout.

The default value is 0. The value of spare cannot exceed max.

idle_timeout

Number of seconds for Unit to wait before it terminates an extra idle process, when the count of idle processes exceeds spare.

The default value is 15.

If processes is omitted entirely, Unit creates 1 static process. If empty object is provided: "processes": {}, dynamic behavior with default parameter values is assumed.

In the following example, Unit tries to keep 5 idle processes, no more than 10 processes in total, and terminates extra idle processes after 20 seconds of inactivity:

{
    "max": 10,
    "spare": 5,
    "idle_timeout": 20
}

Go/Node.js Applications§

To run your Go or Node.js applications in Unit, you need to configure them and modify their source code as suggested below. Let’s start with the application configuration:

Option

描述

executable (required)

Pathname of the application, absolute or relative to working_directory.

For Node.js, supply your .js pathname and start the file itself with a proper shebang:

#!/usr/bin/env node
arguments Command line arguments to be passed to the application. The example below is equivalent to /www/chat/bin/chat_app --tmp-files /tmp/go-cache.

Example:

{
    "type": "external",
    "working_directory": "/www/chat",
    "executable": "bin/chat_app",
    "user": "www-go",
    "group": "www-go",
    "arguments": ["--tmp-files", "/tmp/go-cache"]
}

Before applying the configuration, update the application itself.

Modifying Go Sources§

In the import section, reference the "nginx/unit" package that you have installed earlier:

import (
    ...
    "nginx/unit"
    ...
)

In the main() function, replace the http.ListenandServe call with unit.ListenAndServe:

func main() {
    ...
    http.HandleFunc("/", handler)
    ...
    //http.ListenAndServe(":8080", nil)
    unit.ListenAndServe(":8080", nil)
    ...
}

The resulting application works as follows:

  • When you run it standalone, the unit.ListenAndServe call falls back to http functionality.
  • When Unit runs it, unit.ListenAndServe communicates with Unit’s router process directly, ignoring the address supplied as its first argument and relying on the listener’s settings instead.

Modifying Node.js Sources§

First, you need to have the unit-http package installed. If it’s global, symlink it in your project directory:

# npm link unit-http

Do the same if you move a Unit-hosted application to a new system where unit-http is installed globally.

Next, use unit-http instead of http in your code:

var http = require('unit-http');

If your application uses the Express framework, rewire it like this:

#!/usr/bin/env node

const {
  createServer,
  IncomingMessage,
  ServerResponse,
} = require('unit-http')

require('http').ServerResponse = ServerResponse
require('http').IncomingMessage = IncomingMessage

const express = require('express')

const app = express()

app.get('/', (req, res) => {
  res.set('X-Unit-Type', 'Absolute')
  res.send('Hello, Unit!')
})

createServer(app).listen()

Java Application§

对象

描述

classpath Array of paths to your app’s required libraries (may point to directories or .jar files).
options Array of strings defining JVM runtime options.
webapp Pathname of the application’s packaged or unpackaged .war file.

示例:

{
    "type": "java",
    "classpath": ["/www/qwk2mart/lib/qwk2mart-2.0.0.jar"],
    "options": ["-Dlog_path=/var/log/qwk2mart.log"],
    "webapp": "/www/qwk2mart/qwk2mart.war"
}

Perl 应用§

Option

描述

script

PSGI 脚本路径。

示例:

{
    "type": "perl",
    "script": "/www/bugtracker/app.psgi",
    "working_directory": "/www/bugtracker",
    "processes": 10,
    "user": "www",
    "group": "www"
}

PHP 应用§

Option

描述

index (optional)

Filename appended to any URI paths ending with a slash; applies if script is omitted.

Default value is index.php.

options (optional) Object that defines php.ini location and options. For details, see below.
root Base directory of your PHP app’s file structure. All URI paths are relative to this value.

script (可选)

Filename of a PHP script; if set, Unit uses this script to serve any requests to this application. Relative to root.

The index and script options enable two modes of operation:

  • If script is set, all requests to the application are handled by the script you provide.
  • Otherwise, the requests are served according to their URI paths; if script name is omitted, index is used.

You can customize php.ini via the options object:

Option

描述

file Pathname of the php.ini file.
admin, user Objects with PHP configuration directives. Directives in admin are set in PHP_INI_SYSTEM mode; it means that your application can’t alter them. Directives in user are set in PHP_INI_USER mode; your application is allowed to update them in runtime.

Directives from php.ini are applied first; next, admin and user objects are applied.

注解

Provide string values for any directives you specify in options (for example, "max_file_uploads": "64" instead of "max_file_uploads": 64). For flags, use "0" and "1" only. For more information about PHP_INI_* modes, see the PHP documentation.

Example:

{
    "type": "php",
    "processes": 20,
    "root": "/www/blogs/scripts",
    "index": "index.php",
    "user": "www-blogs",
    "group": "www-blogs",

    "options": {
        "file": "/etc/php.ini",
        "admin": {
            "memory_limit": "256M",
            "variables_order": "EGPCS",
            "expose_php": "0"
        },
        "user": {
            "display_errors": "0"
        }
    }
}

Python 应用§

Option

描述

module WSGI module name. To run the app, Unit looks for an application callable in the module you supply; the module itself is imported just like in Python.

path (可选)

Additional lookup path for Python modules; this string is inserted into sys.path.
home (optional)

Path to Python virtual environment for the application. You can set this value relative to the working_directory of the application.

Note: The Python version used by Unit to run the application is controlled by the type of the application. Unit doesn’t use command line Python interpreter within the virtual environment due to performance considerations.

示例:

{
    "type": "python 3.6",
    "processes": 10,
    "working_directory": "/www/store/",
    "path": "/www/store/cart/",
    "home": "/www/store/.virtualenv/",
    "module": "wsgi",
    "user": "www",
    "group": "www"
}

Ruby 应用§

Option

描述

script

Rack 脚本路径。

示例:

{
    "type": "ruby",
    "processes": 5,
    "user": "www",
    "group": "www",
    "script": "/www/cms/config.ru"
}

访问日志§

在配置对象中使用 access_log 参数可以配置访问日志文件存放路径。

以下示例将所有请求日志文件放到了 /var/log/access.log

# curl -X PUT -d '"/var/log/access.log"'  \
       --unix-socket /path/to/control.unit.sock  \
       http://localhost/config/access_log
{
    "success": "Reconfiguration done."
}

日志格式为 Combined Log Format,以下是一行示例:

127.0.0.1 - - [21/Oct/2015:16:29:00 -0700] "GET / HTTP/1.1" 200 6022 "http://example.com/links.html" "Godzilla/5.0 (X11; Minix i286) Firefox/42"

SSL/TLS and Certificates§

To set up SSL/TLS access for your application, upload a .pem file containing your certificate chain and private key to Unit. Next, reference the uploaded bundle in the listener’s configuration. After that, the listener’s application becomes accessible via SSL/TLS.

First, create a .pem file with your certificate chain and private key:

# cat cert.pem ca.pem key.pem > bundle.pem

注解

Usually, your website’s certificate (optionally followed by the intermediate CA certificate) is enough to build a certificate chain. If you add more certificates to your chain, order them leaf to root.

Upload the resulting file to Unit’s certificate storage under a suitable name:

# curl -X PUT --data-binary @bundle.pem 127.1:8443/certificates/<bundle>

    {
        "success": "Certificate chain uploaded."
    }

警告

Don’t use -d for file upload; this option damages .pem files. Use the --data-binary option when uploading file-based data with curl to avoid data corruption.

Internally, Unit stores uploaded certificate bundles along with other configuration data in its state subdirectory; Unit’s control API maps them to a separate configuration section, aptly named certificates:

{
    "certificates": {
        "<bundle>": {
            "key": "RSA (4096 bits)",
            "chain": [
                {
                    "subject": {
                        "common_name": "example.com",
                        "alt_names": [
                            "example.com",
                            "www.example.com"
                        ],

                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme, Inc."
                    },

                    "issuer": {
                        "common_name": "intermediate.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Certification Authority"
                    },

                    "validity": {
                        "since": "Sep 18 19:46:19 2018 GMT",
                        "until": "Jun 15 19:46:19 2021 GMT"
                    }
                },

                {
                    "subject": {
                        "common_name": "intermediate.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Certification Authority"
                    },

                    "issuer": {
                        "common_name": "root.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Root Certification Authority"
                    },

                    "validity": {
                        "since": "Feb 22 22:45:55 2016 GMT",
                        "until": "Feb 21 22:45:55 2019 GMT"
                    }
                },
            ]
        }
    }
}

注解

You can access individual certificates in your chain, as well as specific alternative names, by their indexes:

# curl -X GET 127.1:8443/certificates/<bundle>/chain/0/
# curl -X GET 127.1:8443/certificates/<bundle>/chain/0/subject/alt_names/0/

Next, add a tls object to your listener configuration, referencing the uploaded bundle’s name in certificate:

{
    "listeners": {
        "127.0.0.1:8080": {
            "pass": "applications/wsgi-app",
            "tls": {
                "certificate": "<bundle>"
            }
        }
    }
}

The resulting control API configuration may look like this:

{
    "certificates": {
        "<bundle>": {
            "key": "<key type>",
            "chain": ["<certificate chain, omitted for brevity>"]
        }
    },

    "config": {
        "listeners": {
            "127.0.0.1:8080": {
                "pass": "applications/wsgi-app",
                "tls": {
                    "certificate": "<bundle>"
                }
            }
        },

        "applications": {
            "wsgi-app": {
                "type": "python",
                "module": "wsgi",
                "path": "/usr/www/wsgi-app/"
            }
        }
    }
}

Now you’re solid. The application is accessible via SSL/TLS:

# curl -v https://127.0.0.1:8080
    ...
    * TLSv1.2 (OUT), TLS handshake, Client hello (1):
    * TLSv1.2 (IN), TLS handshake, Server hello (2):
    * TLSv1.2 (IN), TLS handshake, Certificate (11):
    * TLSv1.2 (IN), TLS handshake, Server finished (14):
    * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
    * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
    * TLSv1.2 (OUT), TLS handshake, Finished (20):
    * TLSv1.2 (IN), TLS change cipher, Client hello (1):
    * TLSv1.2 (IN), TLS handshake, Finished (20):
    * SSL connection using TLSv1.2 / AES256-GCM-SHA384
    ...

Finally, you can DELETE a certificate bundle that you don’t need anymore from the storage:

# curl -X DELETE 127.1:8443/certificates/<bundle>

    {
        "success": "Certificate deleted."
    }

注解

You can’t delete certificate bundles still referenced in your configuration, overwrite existing bundles using PUT, or (obviously) delete non-existent ones.

Happy SSLing!

完整示例§

{
    "certificates": {
        "bundle": {
            "key": "RSA (4096 bits)",
            "chain": [
                {
                    "subject": {
                        "common_name": "example.com",
                        "alt_names": [
                            "example.com",
                            "www.example.com"
                        ],

                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme, Inc."
                    },

                    "issuer": {
                        "common_name": "intermediate.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Certification Authority"
                    },

                    "validity": {
                        "since": "Sep 18 19:46:19 2018 GMT",
                        "until": "Jun 15 19:46:19 2021 GMT"
                    }
                },

                {
                    "subject": {
                        "common_name": "intermediate.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Certification Authority"
                    },

                    "issuer": {
                        "common_name": "root.ca.example.com",
                        "country": "US",
                        "state_or_province": "CA",
                        "organization": "Acme Root Certification Authority"
                    },

                    "validity": {
                        "since": "Feb 22 22:45:55 2016 GMT",
                        "until": "Feb 21 22:45:55 2019 GMT"
                    }
                }
            ]
        }
    },

    "config": {
        "settings": {
            "http": {
                "header_read_timeout": 10,
                "body_read_timeout": 10,
                "send_timeout": 10,
                "idle_timeout": 120,
                "max_body_size": 6291456
            }
        },

        "listeners": {
            "*:8300": {
                "pass": "applications/blogs",
                "tls": {
                    "certificate": "bundle"
                }
            },

            "*:8400": {
                "pass": "applications/wiki"
            },

            "*:8500": {
                "pass": "applications/go_chat_app"
            },

            "127.0.0.1:8600": {
                "pass": "applications/bugtracker"
            },

            "127.0.0.1:8601": {
                "pass": "routes/cms"
            },

            "*:8700": {
                "pass": "applications/qwk2mart"
            }
        },

        "routes" {
            "cms": [
                {
                    "match": {
                        "uri": "!/admin/*"
                    },
                    "action": {
                        "pass": "applications/cms_main"
                    }
                },

                {
                    "action": {
                        "pass": "applications/cms_admin"
                    }
                }
            ]
        },

        "applications": {
            "blogs": {
                "type": "php",
                "processes": 20,
                "root": "/www/blogs/scripts",
                "index": "index.php",
                "limits": {
                    "timeout": 10,
                    "requests": 1000
                },

                "options": {
                    "file": "/etc/php.ini",
                    "admin": {
                        "memory_limit": "256M",
                        "variables_order": "EGPCS",
                        "expose_php": "0"
                    },

                    "user": {
                        "display_errors": "0"
                    }
                }
            },

            "wiki": {
                "type": "python",
                "processes": 10,
                "path": "/www/wiki",
                "module": "wsgi",
                "environment": {
                    "DJANGO_SETTINGS_MODULE": "blog.settings.prod",
                    "DB_ENGINE": "django.db.backends.postgresql",
                    "DB_NAME": "blog",
                    "DB_HOST": "127.0.0.1",
                    "DB_PORT": "5432"
                }
            },

            "go_chat_app": {
                "type": "external",
                "user": "www-chat",
                "group": "www-chat",
                "working_directory": "/www/chat",
                "executable": "bin/chat_app"
            },

            "bugtracker": {
                "type": "perl",
                "processes": {
                    "max": 10,
                    "spare": 5,
                    "idle_timeout": 20
                },

                "working_directory": "/www/bugtracker",
                "script": "app.psgi"
            },

            "cms_main": {
                "type": "ruby",
                "processes": 5,
                "script": "/www/cms/main.ru"
            },

            "cms_admin": {
                "type": "ruby",
                "processes": 1,
                "script": "/www/cms/admin.ru"
            },

            "qwk2mart": {
                "type": "java",
                "classpath": ["/www/qwk2mart/lib/qwk2mart-2.0.0.jar"],
                "options": ["-Dlog_path=/var/log/qwk2mart.log"],
                "webapp": "/www/qwk2mart/qwk2mart.war"
            }
        },

        "access_log": "/var/log/access.log"
    }
}