Configuration§
Quick Start§
To run an application on Unit, first set up an application object. Let’s store it in a file to PUT
it into the config/applications
section of Unit’s control API,
available via the control socket at
http://localhost/
:
$ cat << EOF > config.json
{
"type": "php",
"root": "/www/blogs/scripts"
}
EOF
# curl -X PUT --data-binary @config.json --unix-socket \
/path/to/control.unit.sock http://localhost/config/applications/blogs
{
"success": "Reconfiguration done."
}
Unit starts the application process. Next, reference the application object
from a listener object, comprising an IP (or a
wildcard to match any IPs) and a port number, in the config/listeners
section of the API:
$ cat << EOF > config.json
{
"pass": "applications/blogs"
}
EOF
# curl -X PUT --data-binary @config.json --unix-socket \
/path/to/control.unit.sock http://localhost/config/listeners/127.0.0.1:8300
{
"success": "Reconfiguration done."
}
Unit accepts requests at the specified IP and port, passing them to the application process. Your app works!
Finally, check the resulting configuration:
# curl --unix-socket /path/to/control.unit.sock http://localhost/config/
{
"listeners": {
"127.0.0.1:8300": {
"pass": "applications/blogs"
}
},
"applications": {
"blogs": {
"type": "php",
"root": "/www/blogs/scripts/"
}
}
}
You can upload the entire configuration at once or update it in portions. For details of configuration techniques, see below. For a full configuration sample, see here.
Configuration Management§
Unit’s configuration is JSON-based, accessed via the control socket, and entirely manageable over HTTP.
Note
Here, we use curl to query Unit’s control API, prefixing URIs
with http://localhost
as expected by this utility. You can use any
tool capable of making HTTP requests; also, the hostname is irrelevant for
Unit.
To address parts of the configuration, query the control socket over HTTP; URI path segments of your requests to the API must be names of its JSON object members or indexes of its array elements.
You can manipulate the API with the following HTTP methods:
Method | Action |
---|---|
GET | Returns the entity at the request URI as a JSON value in the HTTP response body. |
POST | Updates the array at the request URI, appending the JSON value from the HTTP request body. |
PUT | Replaces the entity at the request URI and returns status message in the HTTP response body. |
DELETE | Deletes the entity at the request URI and returns status message in the HTTP response body. |
Before a change, Unit evaluates the difference it causes in the entire configuration; if there’s none, nothing is done. For example, you can’t restart an updated app by uploading the same configuration it already has.
Unit performs actual reconfiguration steps as gracefully as possible: running tasks expire naturally, connections are properly closed, processes end smoothly.
Any type of update can be done with different URIs, provided you supply the right JSON:
# curl -X PUT -d '{ "pass": "applications/blogs" }' --unix-socket \
/path/to/control.unit.sock http://localhost/config/listeners/127.0.0.1:8300
# curl -X PUT -d '"applications/blogs"' --unix-socket /path/to/control.unit.sock \
http://localhost/config/listeners/127.0.0.1:8300/pass
However, mind that the first command replaces the entire listener, dropping
any other options you could have configured, whereas the second one replaces
only the To minimize typos and effort, avoid embedding JSON payload in your commands;
instead, consider storing your configuration snippets for review and reuse.
Suppose you save your application object as Use it to set up an application called Use it again to set up a development version of the same app called
Toggle the Next, boost the process count for the production app to warm it up a bit: Add a listener for the Plug the Then rewire the listener, adding a URI-based route to the development
version of the app: Next, let’s change the Let’s add a route to the prod app: Otherwise, use To get the complete To obtain the You can save JSON returned by such requests as To drop the listener on Mind that you can’t delete objects that other objects rely on, such as a
route still referenced by a listener: Although Unit is fully dynamic, sometimes you just want to copy an existing
setup without the need for subsequent meddling. Unit’s state
directories are interchangeable, provided
they are used by the same version of Unit that created them, so you can use
a shortcut to replicate a Unit instance. Warning Unit’s state can change its structure between versions and shouldn’t be
edited by external means. On the machine where the reference Unit instance runs, find out
where the state is stored: Double-check that the state location isn’t overridden at startup: Repeat these commands on the second machine to see where the target instance
stores its state. Stop both Unit instances, for example: Note Different stop and start commands may be needed if you use a
non-official installation
method. Copy the reference state directory to the target state directory by
arbitrary means; make sure to include subdirectories and hidden files.
Finally, restart both Unit instances: Note If you run your Unit instances manually, After the restart, the target instance picks up the configuration you’ve
copied to the state directory.pass
value and leaves other options intact.Examples
wiki.json
:{
"type": "python",
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki/"
}
wiki-prod
:# curl -X PUT --data-binary @/path/to/wiki.json \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-prod
wiki-dev
:# curl -X PUT --data-binary @/path/to/wiki.json \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-dev
wiki-dev
app to another source code directory:# curl -X PUT -d '"/www/wiki-dev/"' \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-dev/path
# curl -X PUT -d '5' \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-prod/processes
wiki-prod
app to accept requests at all host
IPs:# curl -X PUT -d '{ "pass": "applications/wiki-prod" }' \
--unix-socket /path/to/control.unit.sock 'http://localhost/config/listeners/*:8400'
wiki-dev
app into the listener to test it:# curl -X PUT -d '"applications/wiki-dev"' --unix-socket /path/to/control.unit.sock \
'http://localhost/config/listeners/*:8400/pass'
$ cat << EOF > config.json
[
{
"match": {
"uri": "/dev/*"
},
"action": {
"pass": "applications/wiki-dev"
}
}
]
EOF
# curl -X PUT --data-binary @config.json --unix-socket \
/path/to/control.unit.sock http://localhost/config/routes
# curl -X PUT -d '"routes"' --unix-socket \
/path/to/control.unit.sock 'http://localhost/config/listeners/*:8400/pass'
wiki-dev
’s URI prefix in the routes
array using its index (0):# curl -X PUT -d '"/development/*"' --unix-socket=/path/to/control.unit.sock \
http://localhost/config/routes/0/match/uri
POST
always adds to the array
end, so there’s no need for an index:# curl -X POST -d '{"match": {"uri": "/production/*"}, \
"action": {"pass": "applications/wiki-prod"}}' \
--unix-socket=/path/to/control.unit.sock \
http://localhost/config/routes/
PUT
with the array’s last index (0 in our sample)
plus one to add the new item at the end:# curl -X PUT -d '{"match": {"uri": "/production/*"}, \
"action": {"pass": "applications/wiki-prod"}}' \
--unix-socket=/path/to/control.unit.sock \
http://localhost/config/routes/1/
config
section:# curl --unix-socket /path/to/control.unit.sock http://localhost/config/
{
"listeners": {
"*:8400": {
"pass": "routes"
}
},
"applications": {
"wiki-dev": {
"type": "python",
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki-dev/"
},
"wiki-prod": {
"type": "python",
"processes": 5,
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki/"
}
},
"routes": [
{
"match": {
"uri": "/development/*"
},
"action": {
"pass": "applications/wiki-dev"
}
},
{
"action": {
"pass": "applications/wiki-prod"
}
}
]
}
wiki-dev
application object:# curl --unix-socket /path/to/control.unit.sock \
http://localhost/config/applications/wiki-dev
{
"type": "python",
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki-dev/"
}
.json
files for
update or review:# curl --unix-socket /path/to/control.unit.sock \
http://localhost/config/ > config.json
*:8400
:# curl -X DELETE --unix-socket /path/to/control.unit.sock \
'http://localhost/config/listeners/*:8400'
# curl -X DELETE --unix-socket /var/run/unit/control.sock \
http://localhost/config/routes
{
"error": "Invalid configuration.",
"detail": "Request \"pass\" points to invalid location \"routes\"."
}
Replicating Unit Configurations
$ unitd -h
--state DIRECTORY set state directory name
default: "/path/to/reference/unit/state"
$ ps ax | grep unitd
...
unit: main v1.25.0 [unitd --state /runtime/path/to/reference/unit/state ... ]
# systemctl stop unit
# systemctl restart unit
--state
can be
used to set the state directory at startup.
Listeners§
To start accepting requests, add a listener object in the
config/listeners
API section. The object’s name uniquely combines a
host IP address and a port that Unit binds to; a wildcard matches any host IPs.
Note
On Linux-based systems, wildcard listeners can’t overlap with other
listeners on the same port due to kernel-imposed rules. For example,
*:8080
conflicts with 127.0.0.1:8080
; this means a listener
can’t be directly reconfigured from *:8080
to 127.0.0.1:8080
or vice versa without deleting it first.
Unit dispatches the requests it receives to destinations referenced by listeners. You can plug several listeners into one destination or use a single listener and hot-swap it between multiple destinations.
Available listener options:
Option | Description |
---|---|
pass | Destination to which the listener passes incoming requests. Possible alternatives:
Note The value is variable-interpolated; if it matches no configuration entities after interpolation, a 404 “Not Found” response is returned. |
tls | Object, defines SSL/TLS settings. |
client_ip | Object, configures client IP address replacement. |
Here, a local listener accepts requests at port 8300 and passes them to the
blogs
app target identified by the
uri
variable. The wildcard listener
on port 8400 relays requests at any host IPs to the main
route:
{
"127.0.0.1:8300": {
"pass": "applications/blogs$uri"
},
"*:8400": {
"pass": "routes/main"
}
}
Also, pass
values can be percent encoded. For example,
you can escape slashes in entity names:
{
"listeners": {
"*:80": {
"pass": "routes/slashes%2Fin%2Froute%2Fname"
}
},
"routes": {
"slashes/in/route/name": []
}
}
SSL/TLS Configuration§
The tls
object provides the following options:
Option | Description |
---|---|
certificate (required) | String or string array, refers to one or more certificate bundles you have uploaded earlier, enabling secure communication via the listener. |
conf_commands | Object, defines the SSL configuration commands to be set for the listener. To provide this option, Unit must be built and run on a system with OpenSSL 1.0.2+: $ openssl version
OpenSSL 1.1.1d 10 Sep 2019
|
session | Object, configures the TLS session cache and tickets for the listener. |
To use a certificate bundle you’ve uploaded earlier,
name it in the certificate
option of the tls
object:
{
"listeners": {
"127.0.0.1:443": {
"pass": "applications/wsgi-app",
"tls": {
"certificate": "bundle"
}
}
}
}
Configuring Multiple Bundles
Since version 1.23.0, Unit supports configuring Server Name Indication
(SNI) on a
listener by supplying an array of certificate bundle names for the
certificate
option value:
{
"*:443": {
"pass": "routes",
"tls": {
"certificate": [
"bundleA",
"bundleB",
"bundleC"
]
}
}
}
If the connecting client sends a server name, Unit responds with the matching certificate bundle. If the name matches several bundles, exact matches trump wildcards; if ambiguity remains, the one listed first is used. If there’s no match or no server name was sent, Unit uses the first bundle on the list.
To set custom OpenSSL configuration commands for a
listener, use the conf_commands
object in tls
:
{
"tls": {
"certificate": "bundle",
"conf_commands": {
"ciphersuites": "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
"minprotocol": "TLSv1.3"
}
}
}
The session
object in tls
configures the session settings of
the listener:
Option | Description |
---|---|
cache_size | Integer, sets the number of sessions in the TLS session cache. The default is |
timeout | Integer, sets the session timeout for the TLS session cache. When a new session is created, it is assigned a lifetime based on its creation time and current timeout value. If a cached session is requested past its lifetime, it is not reused. The default is |
tickets | Boolean, string, or an array of strings; configures TLS session tickets. The default is |
Example:
{
"tls": {
"certificate": "bundle",
"session": {
"cache_size": 10240,
"timeout": 60,
"tickets": [
"k5qMHi7IMC7ktrPY3lZ+sL0Zm8oC0yz6re+y/zCj0H0/sGZ7yPBwGcb77i5vw6vCx8vsQDyuvmFb6PZbf03Auj/cs5IHDTYkKIcfbwz6zSU=",
"3Cy+xMFsCjAek3TvXQNmCyfXCnFNAcAOyH5xtEaxvrvyyCS8PJnjOiq2t4Rtf/Gq",
"8dUI0x3LRnxfN0miaYla46LFslJJiBDNdFiPJdqr37mYQVIzOWr+ROhyb1hpmg/QCM2qkIEWJfrJX3I+rwm0t0p4EGdEVOXQj7Z8vHFcbiA="
]
}
}
}
The tickets
option works as follows:
Boolean values enable or disable session tickets; when enabled, a random session ticket key is used:
{ "session": { "tickets": true } }
A string enables tickets and explicitly sets the session ticket key:
{ "session": { "tickets": "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP" } }
This can be employed to implement ticket reuse in scenarios where the key is shared between servers.
Unit supports AES256 (80-byte keys) or AES128 (48-byte keys); the bytes should be encoded in Base64:
$ openssl rand -base64 48 LoYjFVxpUFFOj4TzGkr5MsSIRMjhuh8RCsVvtIJiQ12FGhn0nhvvQsEND1+OugQ7 $ openssl rand -base64 80 GQczhdXawyhTrWrtOXI7l3YYUY98PrFYzjGhBbiQsAWgaxm+mbkm4MmZZpDw0tkK YTqYWxofDtDC4VBznbBwTJTCgYkJXknJc4Gk2zqD1YA=
An array of strings just like the one above:
{ "session": { "tickets": [ "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP", "Ax4bv/JvMWoQG+BfH0feeM9Qb32wSaVVKOj1+1hmyU8ORMPHnf3Tio8gLkqm2ifC" ] } }
Unit uses these keys to decrypt the tickets submitted by clients who want to recover their session state; the last key is always used to create new session tickets and update the tickets created earlier.
Note
An empty array effectively disables session tickets, same as setting
tickets
tofalse
.
Originating IP Identification§
Unit supports identification of the clients’ originating IPs with the
client_ip
object and its options:
Option | Description |
---|---|
header (required) | String, defines the relevant HTTP header fields to expect in the request. Unit expects them to follow the X-Forwarded-For notation with the field value being a comma- or space-separated list of IPv4 or IPv6 addresses. |
source (required) | String or array of strings, defines address-based patterns for trusted addresses; the replacement occurs only if the source IP of the request is a match. |
recursive | Boolean, controls how the header fields are traversed. The default value is |
Unit proceeds to inspect the header
fields only if the source IP of the
request matches the
source
option.
Consider the following client_ip
configuration:
{
"client_ip": {
"header": "X-Forwarded-For",
"recursive": false,
"source": [
"192.0.2.0/24",
"198.51.100.0/24"
]
}
}
Suppose a request arrives with the following header fields:
X-Forwarded-For: 192.0.2.18
X-Forwarded-For: 203.0.113.195, 198.51.100.178
If recursive
is set to false
(default), Unit chooses the
rightmost address of the last header
field as the originating IP.
In the example, it is set to 198.51.100.178 for requests from 192.0.2.0/24 or
198.51.100.0/24.
If recursive
is set to true
, Unit inspects all header
fields in reverse order. Each is traversed from right to left until the first
non-trusted address; if found, it’s chosen as the originating IP. In the
example above with "recursive": true
, the client IP would be set to
203.0.113.195 because 198.51.100.178 is also trusted; this simplifies working
behind multiple reverse proxies.
Finally, mind that source
can use not only subnets but any
address-based patterns:
{
"client_ip": {
"header": "X-Forwarded-For",
"source": [
"198.51.100.1-198.51.100.254",
"!198.51.100.128/26",
"203.0.113.195"
]
}
}
Routes§
The config/routes
configuration entity defines internal request
routing, receiving requests via listeners and
filtering them through sets of conditions to be processed by apps, proxied to external
servers or load-balanced between them, served
with static content, answered with arbitrary status codes, or redirected.
In its simplest form, routes
can be a single route array:
{
"listeners": {
"*:8300": {
"pass": "routes"
}
},
"routes": [
"..."
]
}
Another form is an object with one or more named route arrays as members:
{
"listeners": {
"*:8300": {
"pass": "routes/main"
}
},
"routes": {
"main": [
"..."
],
"route66": [
"..."
]
}
}
Route Steps§
A route array contains step objects as elements; they accept the following options:
Option | Description |
---|---|
action (required) | Object that defines how matching requests are handled. |
match | Object that defines the step’s conditions to be matched. |
A request passed to a route traverses its steps sequentially:
- If all
match
conditions in a step are met, the step’saction
is performed. - If a step’s condition isn’t met, Unit proceeds to the next step of the route.
- If no steps of the route match, a 404 “Not Found” response is returned.
Warning
If a step omits the match
option, its action
is
performed automatically. Thus, use no more than one such step per
route, always placing it last to avoid potential routing issues.
Ad-Hoc Examples
A basic one:
{
"routes": [
{
"match": {
"host": "example.com",
"scheme": "https",
"uri": "/php/*"
},
"action": {
"pass": "applications/php_version"
}
},
{
"action": {
"share": "/www/static_version/"
}
}
]
}
This route passes all requests to the /php/
subsection of the
example.com
website via HTTPS to the php_version
app. All
other requests are served with static content from the
/www/static_version
directory. If there’s no matching content, a
404 “Not Found” response is returned.
A more elaborate example with chained routes and proxying:
{
"routes": {
"main": [
{
"match": {
"scheme": "http"
},
"action": {
"pass": "routes/http_site"
}
},
{
"match": {
"host": "blog.example.com"
},
"action": {
"pass": "applications/blog"
}
},
{
"match": {
"uri": [
"*.css",
"*.jpg",
"*.js"
]
},
"action": {
"share": "/www/static/"
}
}
],
"http_site": [
{
"match": {
"uri": "/v2_site/*"
},
"action": {
"pass": "applications/v2_site"
}
},
{
"action": {
"proxy": "http://127.0.0.1:9000"
}
}
]
}
}
Here, a route called main
is explicitly defined, so routes
is an object instead of an array. The first step of the route passes all
requests that arrive via HTTP to the http_site
app. The second step
passes all requests that target blog.example.com
to the blog
app. The final step serves requests for certain file types from the
/www/static/
directory. If no steps match, a 404 “Not Found”
response is returned.
Matching Conditions§
Conditions in a route step’s match
object define patterns to be compared to the requests’ properties:
Property | Patterns Are Matched Against | Case‑Sensitive |
---|---|---|
arguments | Parameter arguments supplied with the request’s target query. In
argument names and values, plus signs (+ ) are replaced with
spaces. | Yes |
cookies | Cookies supplied with the request. | Yes |
destination | Target IP address and optional port of the request. | No |
headers | Header fields supplied with the request. | No |
host | Host header field, converted
to lower case and normalized by removing the port number and the
trailing period (if any). | No |
method | Method from the request line, converted to upper case. | No |
scheme | URI scheme.
Currently, only http and https are supported. | No |
source | Source IP address and optional port of the request. | No |
uri | Request target path, normalized by removing the query part, resolving relative path references (“.” and “..”), and collapsing adjacent slashes. | Yes |
Percent Encoding In Arguments and URIs
Names and values in arguments
and values in uri
additionally
support percent encoding. Thus, you
can escape characters which have special meaning in routing (!
is
%21
, *
is %2A
, %
is %25
), or even
target individual bytes. For example, to select an entire class of
diacritic characters such as Ö or Å by their starting byte 0xC3
in
UTF-8:
{
"match": {
"arguments": {
"word": "*%C3*"
}
},
"action": {
"pass": "..."
}
}
This requires mentioning that actual arguments and URIs passed with requests are percent decoded: Unit interpolates all percent-encoded entities in these properties. Thus, the following configuration:
{
"routes": [
{
"match": {
"uri": "/static files/*"
},
"action": {
"share": "/www/data/"
}
}
]
}
Matches this percent-encoded request:
$ curl http://127.0.0.1/static%20files/test.txt -v
> GET /static%20files/test.txt HTTP/1.1
...
< HTTP/1.1 200 OK
...
Match Resolution§
To be a match, the property must meet two requirements:
- If there are patterns without negation (the
!
prefix), at least one of them matches the property value. - No negated patterns match the property value.
Note
The scheme
property accepts no patterns or arrays, but only two
string values: http
or https
.
Here, the URI of the request must fit pattern3
, but should not match
pattern1
or pattern2
.
{
"match": {
"uri": [
"!pattern1",
"!pattern2",
"pattern3"
]
},
"action": {
"pass": "..."
}
}
Formal Explanation
This logic can be described 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:
Additionally, special matching logic is used for arguments
,
cookies
, and headers
. Each of these can be a single object that
lists custom-named properties and their patterns or an array of such objects.
To match a single object, the request must match all properties named in the
object. To match an object array, it’s enough to match any single one of its
item objects. The following condition will match only if the request arguments
include both arg1
and arg2
and they match their patterns:
{
"match": {
"arguments": {
"arg1": "pattern",
"arg2": "pattern"
}
},
"action": {
"pass": "..."
}
}
With an object array, the condition will match if the request’s arguments
include either arg1
or arg2
(or maybe both) that matches the
respective pattern:
{
"match": {
"arguments": [
{
"arg1": "pattern"
},
{
"arg2": "pattern"
}
]
},
"action": {
"pass": "..."
}
}
The following example combines all matching types. Here, host
,
method
, uri
, arg1
and arg2
, either
cookie1
or cookie2
, and either header1
or
header2
and header3
must be matched for the action
to
be taken (host & method & uri & arg1 & arg2 & (cookie1 | cookie2) &
(header1 | (header2 & header3))
):
{
"match": {
"host": "pattern",
"method": "!pattern",
"uri": [
"pattern",
"!pattern"
],
"arguments": {
"arg1": "pattern",
"arg2": "!pattern"
},
"cookies": [
{
"cookie1": "pattern",
},
{
"cookie2": "pattern",
}
],
"headers": [
{
"header1": "pattern",
},
{
"header2": "pattern",
"header3": "pattern"
}
]
},
"action": {
"pass": "..."
}
}
Object Pattern Examples
{
"match": {
"arguments": {
"mode": "strict",
"access": "!full"
}
},
"action": {
"pass": "..."
}
}
This requires mode=strict
and any access
argument other than
access=full
in the URI query.
{
"match": {
"headers": [
{
"Accept-Encoding": "*gzip*",
"User-Agent": "Mozilla/5.0*"
},
{
"User-Agent": "curl*"
}
]
},
"action": {
"pass": "..."
}
}
This matches requests that either use gzip
and identify as
Mozilla/5.0
or list curl
as the user agent.
Pattern Syntax§
Individual patterns can be address-based (source
and
destination
) or string-based (other properties).
String-based patterns must match the property to a character; wildcards or regexes modify this behavior:
- A wildcard pattern may contain any combination of wildcards (
*
), each standing for an arbitrary number of characters:How*s*that*to*you
. - A regex pattern starts with a tilde (
~
):~^\d+\.\d+\.\d+\.\d+
(escaping backslashes is a JSON requirement). Regexes are PCRE-flavored.
String Pattern Examples
{
"match": {
"uri": "~^/data/www/.*\\.php(/.*)?$"
},
"action": {
"pass": "..."
}
}
A regular expression that matches any .php
files within the
/data/www/
directory and its subdirectories. Note the backslashes;
escaping is a JSON-specific requirement.
{
"match": {
"host": "*.example.com"
},
"action": {
"pass": "..."
}
}
Only subdomains of example.com
will match.
{
"match": {
"uri": "/admin/*/*.php"
},
"action": {
"pass": "..."
}
}
Only requests for .php
files located in /admin/
’s
subdirectories will match.
{
"match": {
"host": [
"eu-*.example.com",
"!eu-5.example.com"
]
},
"action": {
"pass": "..."
}
}
Here, any eu-
subdomains of example.com
will match except
eu-5.example.com
.
{
"match": {
"method": [
"!HEAD",
"!GET"
]
},
"action": {
"pass": "..."
}
}
Any methods will match except HEAD
and GET
.
You can also combine certain special characters in a pattern:
{
"match": {
"uri": "!*/api/*"
},
"action": {
"pass": "..."
}
}
Here, any URIs will match except the ones containing /api/
.
{
"match": {
"uri": [
"/articles/*",
"!~/articles/\\d{4}-\\d{2}-\\d{2}"
]
},
"action": {
"pass": "..."
}
}
Here, URIs of any articles that don’t look like YYYY-MM-DD
dates
will match. Again, note the backslashes; this is a JSON requirement.
Address-based patterns define individual IPv4 (dot-decimal or CIDR) or IPv6 (hexadecimal or CIDR) addresses that must exactly match the property value; wildcards and ranges modify this behavior:
- Wildcards (
*
) can only be used to match arbitrary IPs (*:<port>
). - Ranges (
-
) can used with both IPs (in respective notation) and ports (<start_port>-<end_port>
).
Address Pattern Examples
{
"match": {
"source": [
"192.0.2.1-192.0.2.200",
"198.51.100.1-198.51.100.200:8000",
"203.0.113.1-203.0.113.200:8080-8090",
"*:80"
],
"destination": [
"192.0.2.0/24",
"198.51.100.0/24:8000",
"203.0.113.0/24:8080-8090",
"*:80"
]
},
"action": {
"pass": "..."
}
}
This uses IPv4-based matching with wildcards and ranges.
{
"match": {
"source": [
"2001:0db8::-2001:0db8:aaa9:ffff:ffff:ffff:ffff:ffff",
"[2001:0db8:aaaa::-2001:0db8:bbbb::]:8000",
"[2001:0db8:bbbb::1-2001:0db8:cccc::]:8080-8090",
"*:80"
],
"destination": [
"2001:0db8:cccd::/48",
"[2001:0db8:ccce::/48]:8000",
"[2001:0db8:ccce:ffff::/64]:8080-8090",
"*:80"
]
},
"action": {
"pass": "..."
}
}
This uses IPv6-based matching with wildcards and ranges.
{
"match": {
"destination": [
"127.0.0.1",
"192.168.0.1",
"::1",
"2001:0db8:1::c0a8:1"
]
},
"action": {
"pass": "..."
}
}
This matches any of the listed IPv4 or IPv6 addresses.
{
"match": {
"source": [
"192.0.2.1-192.0.2.10",
"!192.0.2.9"
]
},
"action": {
"pass": "..."
}
}
Here, any IPs from the range will match, except for 192.0.2.9
.
{
"match": {
"source": [
"*:80",
"*:443",
"*:8000-8080"
]
},
"action": {
"pass": "..."
}
}
This matches any IPs but limits the acceptable ports.
Handling Actions§
If a request matches all conditions of a
route step or the step itself omits the match
object, Unit handles the
request using the respective action
. The mutually exclusive
action
types are:
Option | Description | Details |
---|---|---|
pass | Destination for the request, identical to a listener’s
pass option. | Listeners |
proxy | Socket address of an HTTP server where the request is proxied. | Proxying |
return | HTTP status code with a context-dependent redirect location. | Instant Responses, Redirects |
share | Directory location that serves the request with static content. | Static Files |
An example:
{
"routes": [
{
"match": {
"uri": "/pass/*"
},
"action": {
"pass": "applications/app"
}
},
{
"match": {
"uri": "~\\.jpe?g$"
},
"action": {
"share": "/var/www/static/",
"fallback": {
"share": "/var/www/static/assets",
"fallback": {
"pass": "upstreams/cdn"
}
}
}
},
{
"match": {
"uri": "/proxy/*"
},
"action": {
"proxy": "http://192.168.0.100:80"
}
},
{
"match": {
"uri": "/return/*"
},
"action": {
"return": 301,
"location": "https://www.example.com"
}
}
]
}
Variables§
While configuring Unit, you can use built-in variables that are replaced by dynamic values in runtime. This enables flexible request processing, making the configuration more compact and straightforward.
Note
Currently, the only place where variables are recognized is the pass
option in listeners and actions. This means you can use them to guide
requests between sets of routes, applications, targets, or upstreams.
Available variables:
Variable | Description |
---|---|
host | Host
header field in
lowercase, without the port number and the trailing period (if any). |
method | Method from the request line. |
uri | Request target path without the query part, normalized by resolving relative path references (“.” and “..”) and collapsing adjacent slashes. The value is percent decoded: Unit interpolates all percent-encoded entities in the request target path. |
To reference a variable, prefix its name with the dollar sign character
($
), optionally enclosing the name in curly brackets ({}
) to
separate it from adjacent text or enhance visibility. Variable names can
contain letters and underscores (_
), so use the brackets if the
variable is immediately followed by these characters:
{
"listeners": {
"*:80": {
"pass": "routes/${method}_route"
}
},
"routes": {
"GET_route": [
{
"action": {
"return": 201
}
}
],
"PUT_route": [
{
"action": {
"return": 202
}
}
],
"POST_route": [
{
"action": {
"return": 203
}
}
]
}
}
At runtime, variables are replaced by dynamically computed values (at your risk!). For example, the listener above targets an entire set of routes, picking individual ones by HTTP verbs that the incoming requests use:
$ curl -i -X GET http://localhost
HTTP/1.1 201 Created
$ curl -i -X PUT http://localhost
HTTP/1.1 202 Accepted
$ curl -i -X POST http://localhost
HTTP/1.1 203 Non-Authoritative Information
$ curl -i --head http://localhost # Bumpy ride ahead, no route defined
HTTP/1.1 404 Not Found
Another obvious usage is employing the URI to choose between applications:
{
"listeners": {
"*:80": {
"pass": "applications$uri"
}
},
"applications": {
"blog": {
"root": "/path/to/blog_app/",
"script": "index.php"
},
"sandbox": {
"type": "php",
"root": "/path/to/sandbox_app/",
"script": "index.php"
}
}
}
This way, we can route requests to applications by request target URIs. A
different approach can route requests between applications by the Host
header field received from the client:
{
"listeners": {
"*:80": {
"pass": "applications/$host"
}
},
"applications": {
"localhost": {
"root": "/path/to/admin_section/",
"script": "index.php"
},
"www.example.com": {
"type": "php",
"root": "/path/to/public_app/",
"script": "index.php"
}
}
}
You can combine variables as you see fit, repeating them or placing them in arbitrary order. This configuration picks application targets by their names and request methods:
{
"listeners": {
"*:80": {
"pass": "applications/app${uri}_${method}"
}
}
}
Instant Responses, Redirects§
You can use route step actions to instantly respond to certain conditions with arbitrary HTTP status codes:
{
"match": {
"uri": "/admin_console/*"
},
"action": {
"return": 403
}
}
The return
action provides the following options:
return (required) | Integer (000-999), defines the HTTP response status code to be returned. |
location | URI, required if the return value implies redirection. |
It is recommended to use the codes according to their semantics; if you use custom codes, make sure that user agents can understand them.
If you specify a redirect code (3xx), supply the destination using the
location
option alongside return
:
{
"action": {
"return": 301,
"location": "https://www.example.com"
}
}
Static Files§
Unit is capable of acting as a standalone web server, serving requests for
static assets from directories you configure; to use the feature, supply the
directory path in the share
option of a route step action:
{
"listeners": {
"127.0.0.1:8300": {
"pass": "routes"
}
},
"routes": [
{
"action": {
"share": "/www/data/static/"
}
}
]
}
The share
action provides the following options:
share (required) | Directory pathname from where the static files are served. |
fallback | Action-like object, used if the requested file can’t be served. |
types | Array of MIME type patterns, used to filter the shared files. |
chroot | Directory pathname that becomes the share’s new root. |
follow_symlinks , traverse_mounts | Booleans, enable or disable symbolic link and mount point
resolution respectively; if
The default for both options is |
Note
To serve the assets, Unit’s router process must be able to access them; thus,
the account this process runs as must have proper permissions assigned. When Unit is installed from the official packages, the process runs as unit:unit
; for
details of other installation methods, see Installation.
Suppose the /www/data/static/
directory has the following structure:
/www/data/static/
├── stylesheet.css
├── html
│ └──index.html
└── js files
└──page.js
In the above configuration, you can request specific files by these URIs:
$ curl http://localhost:8300/html/index.html
$ curl http://localhost:8300/stylesheet.css
$ curl http://localhost:8300/js%20files/page.js
If the request URI specifies only the directory name, Unit attempts to serve an
index.html
file from this directory but doesn’t apply MIME
filtering:
$ curl -vL http://localhost:8300/html/
...
< HTTP/1.1 200 OK
< Last-Modified: Fri, 20 Sep 2019 04:14:43 GMT
< ETag: "5d66459d-d"
< Content-Type: text/html
< Server: Unit/1.25.0
...
Note
Unit’s ETag response header fields use the MTIME-FILESIZE
format,
where MTIME
stands for file modification timestamp and
FILESIZE
stands for file size in bytes, both in hexadecimal.
MIME Filtering§
To filter the files a share
serves by their MIME types, define a types
array of string patterns.
They work like route patterns
but are matched to the MIME type of each file; the request is served only if
it’s a match:
{
"share": "/www/data/static/",
"types": [
"!text/javascript",
"!text/css",
"text/*",
"~video/3gpp2?"
]
}
This sample configuration blocks JS and CSS files with negation but allows all other text-based
MIME types with a wildcard pattern. Additionally, the .3gpp
and
.3gpp2
file types are allowed by a regex pattern.
If the MIME type of a requested file isn’t recognized, it is considered empty
(""
). Thus, the "!"
pattern (“deny empty strings”) can be used
to restrict all file types unknown to Unit:
{
"share": "/www/data/known-types-only/",
"types": [
"!"
]
}
Path Restrictions§
Note
To provide these options, Unit must be built and run on a system with Linux kernel version 5.6+.
The chroot
option effectively confines path resolution of files served
from a share to a new root directory. One notable effect of enabling
chroot
is that symbolic links to absolute pathnames are treated as
relative to the new root; thus, a symlink to /log/app.log
in this
example is resolved as /www/data/log/app.log
:
{
"action": {
"share": "/www/data/static/",
"chroot": "/www/data/"
}
}
The share
path stays as is and won’t be treated as relative; only the
portions that occur after the new root (if any) are affected by the new
behavior. Moreover, any requests for files outside the new root will fail:
{
"action": {
"share": "/www/",
"chroot": "/www/data/"
}
}
In this configuration, a request for /index.xml
will result in status
code 403 because it will be resolved as /www/index.xml
, which is
outside the new root.
{
"action": {
"share": "/www/data/static/",
"follow_symlinks": false,
"traverse_mounts": false
}
}
Here, any request that involves a symlink or a mount point inside
/www/data/static/
will fail; also, if a portion of the share
path is a symlink or a mount point, this configuration will be accepted but
won’t work.
With chroot
set, follow_symlinks
and traverse_mounts
only affect portions of the path after the new root:
{
"action": {
"share": "/www/data/static/",
"chroot": "/www/data/",
"follow_symlinks": false,
"traverse_mounts": false
}
}
Here, Suppose you want to serve files from a share that itself includes a symlink
(say, Let’s create a symlink to If symlink resolution is enabled (with or without Let’s set Now the symlink request fails, which is the desired effect: Lastly, what difference does This request fails because www/
and data/
can be symlinks or mount points, but any
symlinks and mount points beyond them, including the static/
portion,
won’t be resolved.Details
data/
in our example) but disable any symlinks inside the
share. Initial configuration:{
"action": {
"share": "/www/data/static/",
"chroot": "/www/data/"
}
}
/www/data/static/index.html
:$ cat > /www/data/static/index.html <<EOF
> index.html
> EOF
$ ln -s index.html /www/data/static/symlink
chroot
), a request
that targets the symlink works:$ curl http://localhost/index.html
index.html
$ curl http://localhost/symlink
index.html
follow_symlinks
to false
:{
"action": {
"share": "/www/data/static/",
"chroot": "/www/data/",
"follow_symlinks": false
}
}
$ curl http://localhost/index.html
index.html
$ curl http://localhost/symlink
<!DOCTYPE html><title>Error 403</title><p>Error 403.
chroot
make? Let’s remove it:{
"action": {
"share": "/www/data/static/",
"follow_symlinks": false
}
}
"follow_symlinks": false
affects the
entire share, and data/
is a symlink:$ curl http://localhost/index.html
<!DOCTYPE html><title>Error 403</title><p>Error 403.
Fallback Action§
Finally, within an action
, you can supply a fallback
option
beside a share
. It specifies the action to be taken if the requested file can’t be
served from the share
path:
{
"share": "/www/data/static/",
"fallback": {
"pass": "applications/php"
}
}
Serving a file can be impossible for different reasons, such as:
- The request’s HTTP method isn’t
GET
orHEAD
. - The file’s MIME type doesn’t match the
types
array. - The file isn’t found at the
share
path. - The router process has insufficient permissions to access the file or an underlying directory.
In the example above, an attempt to serve the requested file from the
/www/data/static/
directory is made first. Only if the file can’t be
served, the request is passed to the php
application.
If the fallback
itself is a share
, it can also contain a nested
fallback
:
{
"share": "/www/data/static/",
"fallback": {
"share": "/www/data/cache/",
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
}
First, this configuration tries to serve the request from the
One common use case that this feature enables is the separation of requests
for static and dynamic content into independent routes. The following
example relays all requests that target You can reverse this scheme for apps that avoid filenames in dynamic URIs,
listing all types of static content to be served from a If image files should be served locally and other proxied, use the
Another way to combine It forwards explicit requests for PHP files to the app while serving all
other types of files from the share; note that a /www/data/static/
directory; on failure, it queries the
/www/data/cache/
path. Only if both attempts fail, the request is
proxied to an external server.Examples
.php
files to an application
and uses a catch-all static share
with a fallback
:{
"routes": [
{
"match": {
"uri": "*.php"
},
"action": {
"pass": "applications/php-app"
}
},
{
"action": {
"share": "/www/php-app/assets/files/",
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
}
],
"applications": {
"php-app": {
"type": "php",
"root": "/www/php-app/scripts/"
}
}
}
share
in a
match
condition and adding an unconditional application path:{
"routes": [
{
"match": {
"uri": [
"*.css",
"*.ico",
"*.jpg",
"*.js",
"*.png",
"*.xml"
]
},
"action": {
"share": "/www/php-app/assets/files/",
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
},
{
"action": {
"pass": "applications/php-app"
}
}
],
"applications": {
"php-app": {
"type": "php",
"root": "/www/php-app/scripts/"
}
}
}
types
array in the first route step:{
"match": {
"uri": [
"*.css",
"*.ico",
"*.jpg",
"*.js",
"*.png",
"*.xml"
]
},
"action": {
"share": "/www/php-app/assets/files/",
"types": [
"image/*"
],
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
}
share
, types
, and fallback
is
exemplified by the following compact pattern:{
"share": "/www/php-app/assets/files/",
"types": [
"!application/x-httpd-php"
],
"fallback": {
"pass": "applications/php-app"
}
}
match
object isn’t
needed here to achieve this effect.
Proxying§
Unit’s routes support HTTP proxying to socket addresses using the proxy
option of a route step action:
{
"routes": [
{
"match": {
"uri": "/ipv4/*"
},
"action": {
"proxy": "http://127.0.0.1:8080"
}
},
{
"match": {
"uri": "/ipv6/*"
},
"action": {
"proxy": "http://[::1]:8080"
}
},
{
"match": {
"uri": "/unix/*"
},
"action": {
"proxy": "http://unix:/path/to/unix.sock"
}
}
]
}
As the example above suggests, you can use Unix, IPv4, and IPv6 socket addresses for proxy destinations.
Note
The HTTPS scheme is not supported yet.
Load Balancing§
Besides proxying requests to individual servers, Unit can also relay incoming
requests to upstreams. An upstream is a group of servers that comprise a
single logical entity and may be used as a pass
destination for
incoming requests in a listener or a
route.
Upstreams are defined in the eponymous config/upstreams
section of the
API:
{
"listeners": {
"*:80": {
"pass": "upstreams/rr-lb"
}
},
"upstreams": {
"rr-lb": {
"servers": {
"192.168.0.100:8080": {},
"192.168.0.101:8080": {
"weight": 0.5
}
}
}
}
}
An upstream must define a servers
object that lists socket addresses as
server object names. Unit dispatches requests between the upstream’s servers
in a round-robin fashion, acting as a load balancer. Each server object can
set a numeric weight
to adjust the share of requests it receives via
the upstream. In the above example, 192.168.0.100:8080
receives twice
as many requests as 192.168.0.101:8080
.
Weights can be specified as integers or fractions in decimal or scientific notation:
{
"servers": {
"192.168.0.100:8080": {
"weight": 1e1
},
"192.168.0.101:8080": {
"weight": 10.0
},
"192.168.0.102:8080": {
"weight": 10
}
}
}
The maximum weight is 1000000
, the minimum is 0
(such servers
receive no requests), the default is 1
.
Applications§
Each app that Unit runs is defined as an object in the
config/applications
section of the control API; it lists the app’s
language and settings, its runtime limits, process model, and various
language-specific options.
Note
Our official language support packages
include end-to-end examples of application configuration, available for your
reference at /usr/share/doc/<module name>/examples/
after package
installation.
Here, Unit runs 20 processes of a PHP app called blogs
, stored in
the /www/blogs/scripts/
directory:
{
"blogs": {
"type": "php",
"processes": 20,
"root": "/www/blogs/scripts/"
}
}
App objects have a number of options shared between all application languages:
Option | Description |
---|---|
type (required) | Application type: Except with For example, if you have only one PHP module, 7.1.9, it matches
|
limits | Object that accepts two integer options, timeout and
requests . Their values govern the life cycle of an
application process. For details, see
here. |
processes | Integer or object. Integer sets a static number of app processes;
object options The default value is 1. |
working_directory | The app’s working directory. If not set, the Unit daemon’s working directory is used. |
user | Username that runs the app process. If not set, the username configured at build time or at startup to run Unit’s non-privileged processes is used. |
group | Group name that runs the app process. If not set, the user ’s
primary group is used. |
environment | Environment variables to be passed to the application. |
Also, you need to set type
-specific options to run the app. This Python app uses path
and module
:
{
"type": "python 3.6",
"processes": 16,
"working_directory": "/www/python-apps",
"path": "blog",
"module": "blog.wsgi",
"user": "blog",
"group": "blog",
"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"
}
}
Process Management§
Unit has three per-app options that control how the app’s processes behave:
isolation
, limits
, and processes
. Also, you can send a
GET
request to the /control/applications/
API section to
restart an app:
# curl -X GET --unix-socket /path/to/control.unit.sock \
http://localhost/control/applications/app_name/restart
Unit handles the rollover gracefully, allowing the old processes to deal with
the existing requests and starting a new set of processes (as defined by the
processes
option) to accept new
requests.
Process Isolation§
You can use namespace and file system isolation for your apps if Unit’s underlying OS supports them:
$ ls /proc/self/ns/
cgroup mnt net pid ... user uts
The isolation
application option has the following members:
Option | Description | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
namespaces | Object that configures namespace isolation scheme for the application. Available options (system-dependent; check your OS manual for guidance):
All options listed above are Boolean; to isolate the app, set the
corresponding namespace option to | ||||||||||||
uidmap | Array of ID mapping objects; each array item must define the following:
| ||||||||||||
gidmap | Same as uidmap , but configures group IDs instead of user IDs. | ||||||||||||
rootfs | Pathname of the directory to be used as the new file system root for the app. | ||||||||||||
automount | Object that controls mount behavior if {
"isolation": {
"automount": {
"language_deps": false,
"procfs": false,
"tmpfs": false
}
}
}
|
A sample isolation
object that enables all namespaces and sets mappings
for user and group IDs:
{
"namespaces": {
"cgroup": true,
"credential": true,
"mount": true,
"network": true,
"pid": true,
"uname": true
},
"uidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
],
"gidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
]
}
Using Uidmap And Gidmap
The uidmap
and gidmap
options are available only if the
underlying OS supports user namespaces.
If uidmap
is omitted but credential
isolation is enabled,
the effective UID (EUID) of the application process in the host namespace is
mapped to the same UID in the container namespace; the same applies to
gidmap
and GID, respectively. This means that the configuration
below:
{
"user": "some_user",
"namespaces": {
"credential": true
}
}
Is equivalent to the following (assuming some_user
’s EUID and EGID
are both equal to 1000):
{
"user": "some_user",
"namespaces": {
"credential": true
},
"uidmap": [
{
"host": "1000",
"container": "1000",
"size": 1
}
],
"gidmap": [
{
"host": "1000",
"container": "1000",
"size": 1
}
]
}
The rootfs
option confines the app to the directory you provide, making
it the new file system root. To use it, your app should have the corresponding
privilege (effectively, run as root
in most cases).
The root directory is changed before the language module starts the
app, so any path options for the app should be relative to the new root.
Note the path
and home
settings:
{
"type": "python 2.7",
"path": "/",
"home": "/venv/",
"module": "wsgi",
"isolation": {
"rootfs": "/var/app/sandbox/"
}
}
Unit mounts language-specific files and directories to the new root so the app stays operational:
Language | Language-Specific Mounts |
---|---|
Java |
|
Python | Python’s sys.path directories |
Ruby |
|
Request Limits§
The limits
object controls request handling by the app process and has
two integer options:
Option | Description |
---|---|
timeout | Request timeout in seconds. If an app process exceeds this limit while handling a request, Unit alerts it to cancel the request and returns an HTTP error to the client. |
requests | Maximum number of requests Unit allows an app process to serve. If the limit is reached, the process is restarted; this helps to mitigate possible memory leaks or other cumulative issues. |
Example:
{
"type": "python",
"working_directory": "/www/python-apps",
"module": "blog.wsgi",
"limits": {
"timeout": 10,
"requests": 1000
}
}
Application Processes§
The processes
option offers a choice between static and dynamic process
management. If you set it to an integer, Unit immediately launches the given
number of app processes and keeps them without scaling.
To enable a dynamic prefork model for your app, supply a processes
object with the following options:
Option | Description |
---|---|
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 tries to reserve for an app.
When the app is started, spare idle processes are launched;
Unit assigns incoming requests to existing idle processes, forking new
idles to maintain the spare level if max allows. As
processes complete requests and turn idle, Unit terminates extra ones
after idle_timeout . |
idle_timeout | Time in seconds that Unit waits before terminating an idle process
which exceeds spare . |
If processes
is omitted entirely, Unit creates 1 static process. If
an empty object is provided: "processes": {}
, dynamic behavior with
default option values is assumed.
Here, Unit allows 10 processes maximum, keeps 5 idles, and terminates extra idles after 20 seconds:
{
"max": 10,
"spare": 5,
"idle_timeout": 20
}
Note
For details of manual application process restart, see here.
Go§
To run your Go apps on Unit, you need to configure them and modify their source code as suggested below. Let’s start with the app configuration; besides common options, you have:
Option | Description |
---|---|
executable (required) | Pathname of the application, absolute or relative to
working_directory . |
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 source code. In the
import
section, reference the unit.nginx.org/go
package that
you installed or built earlier:
import (
...
"unit.nginx.org/go"
...
)
Note
The package is required only to build the app; there’s no need to install it in the target environment.
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)
...
}
Next, create a Go module and build your application:
$ go mod init example.com/app
go: creating new go.mod: module example.com/app
$ go build -o app app.go
go: finding unit.nginx.org latest
This links the unit-http module to your app and adds it as a
dependency to your go.mod
. The resulting executable works as follows:
- When you run it standalone, the
unit.ListenAndServe
call falls back tohttp
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.
If you update Unit later, update the Go package as well according to your installation method. You’ll also need to rebuild your app with the updated package.
Java§
First, make sure to install Unit along with the Java language module.
Besides common options, you have the following:
Option | Description |
---|---|
webapp (required) | Pathname of the application’s packaged or unpackaged .war file. |
classpath | Array of paths to your app’s required libraries (may list directories
or .jar files). |
options | Array of strings defining JVM runtime options. |
threads | Integer that sets the number of worker threads per app process. When started, each app process creates a corresponding number of threads to handle requests. The default value is |
thread_stack_size | Integer that defines the stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default value is system dependent and can be set with ulimit -s <SIZE_KB>. |
Example:
{
"type": "java",
"classpath": [
"/www/qwk2mart/lib/qwk2mart-2.0.0.jar"
],
"options": [
"-Dlog_path=/var/log/qwk2mart.log"
],
"webapp": "/www/qwk2mart/qwk2mart.war"
}
Note
For Java-based examples, see our Jira, OpenGrok, and Spring Boot howtos or a basic sample.
Node.js§
First, you need to have the unit-http module installed. If it’s global, symlink it in your project directory:
# npm link unit-http
Do the same if you move a Unit-hosted app to a new system where unit-http is installed globally. Also, if you update Unit later, update the Node.js module as well according to your installation method.
Next, to run your Node.js apps on Unit, you need to configure them. Besides common options, you have:
Option | Description |
---|---|
executable (required) | Pathname of the application, absolute or relative to
Supply your #!/usr/bin/env node
Note Make sure to chmod +x the file you list here so Unit can start it. |
arguments | Command line arguments to be passed to the application.
The example below is equivalent to
/www/apps/node-app/app.js --tmp-files /tmp/node-cache . |
Example:
{
"type": "external",
"working_directory": "/www/app/node-app/",
"executable": "app.js",
"user": "www-node",
"group": "www-node",
"arguments": [
"--tmp-files",
"/tmp/node-cache"
]
}
You can run Node.js apps without altering their code, using a loader module we provide with unit-http. Apply the following app configuration, depending on your version of Node.js:
{
"type": "external",
"executable": "/usr/bin/env",
"arguments": [
"node",
"--loader",
"unit-http/loader.mjs",
"--require",
"unit-http/loader",
"app.js"
]
}
{
"type": "external",
"executable": "/usr/bin/env",
"arguments": [
"node",
"--require",
"unit-http/loader",
"app.js"
]
}
The loader overrides the http
and websocket
modules with their
Unit-aware versions and starts the app.
You can also run your Node.js apps without the loader by updating the
application source code. For that, use unit-http
instead of
http
in your code:
var http = require('unit-http');
To use the WebSocket protocol, your app only needs to replace the default
websocket
:
var webSocketServer = require('unit-http/websocket').server;
Perl§
First, make sure to install Unit along with the Perl language module.
Besides common options, you have the following:
Option | Description |
---|---|
script (required) | PSGI script path. |
threads | Integer that sets the number of worker threads per app process. When started, each app process creates a corresponding number of threads to handle requests. The default value is |
thread_stack_size | Integer that defines the stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default value is system dependent and can be set with ulimit -s <SIZE_KB>. |
Example:
{
"type": "perl",
"script": "/www/bugtracker/app.psgi",
"working_directory": "/www/bugtracker",
"processes": 10,
"user": "www",
"group": "www"
}
PHP§
First, make sure to install Unit along with the PHP language module.
Besides common options, you have the following:
Option | Description |
---|---|
root (required) | Base directory of your PHP app’s file structure. All URI paths are relative to this value. |
index | Filename appended to any URI paths ending with a slash; applies if
The default value is |
options | Object that defines the
php.ini location and options. |
targets | Object that defines application sections with custom root , script , and
index values. |
script | Filename of a root -based PHP script that Unit uses to serve all
requests to the app. |
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 | Description |
---|---|
file | Pathname of the php.ini file with PHP configuration directives. |
admin , user | Objects for extra directives. Values in admin are set in
PHP_INI_SYSTEM mode, so the app can’t alter them; user
values are set in PHP_INI_USER mode and may be updated in runtime. |
Directives from php.ini
are overridden by settings supplied in
admin
and user
objects.
Note
Values in options
must be strings (for example,
"max_file_uploads": "4"
, not "max_file_uploads": 4
); for
boolean flags, use "0"
and "1"
only. For details about
PHP_INI_*
modes, see the PHP docs.
Note
Unit implements the fastcgi_finish_request()
function in a
manner similar to PHP-FPM.
Example:
{
"type": "php",
"processes": 20,
"root": "/www/blogs/scripts/",
"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"
}
}
}
Targets§
You can configure up to 254 individual entry points for a single PHP application:
{
"applications": {
"php-app": {
"type": "php",
"targets": {
"foo": {
"script": "foo.php",
"root": "/www/apps/php-app/foo/"
},
"bar": {
"script": "bar.php",
"root": "/www/apps/php-app/bar/"
}
}
}
}
}
Each target is an object that specifies root
and optionally
index
or script
just like a common application does. Targets
can be used by the pass
options in listeners and routes to serve
requests:
{
"listeners": {
"127.0.0.1:8080": {
"pass": "applications/php-app/foo"
},
"127.0.0.1:80": {
"pass": "routes"
}
},
"routes": [
{
"match": {
"uri": "/bar"
},
"action": {
"pass": "applications/php-app/bar"
}
}
]
}
App-wide settings (isolation
, limits
, options
,
processes
) are shared by all targets within the app.
Warning
If you specify targets
, there should be no root
,
index
, or script
defined at the application level.
Python§
First, make sure to install Unit along with the Python language module.
Besides common options, you have the following:
Option | Description |
---|---|
module (required) | Application module name. The module itself is imported just like in
Python. |
callable | Name of the callable in The default value is |
home | Path to the app’s virtual environment.
Absolute or relative to Note The Python version used to run the app depends on the |
path | String or array of strings that represent additional Python module
lookup paths; these values are prepended to sys.path . |
protocol | Hint to tell Unit that the app uses a certain interface; can be
asgi or wsgi . |
targets | Object that defines application sections with custom module and callable
values. |
threads | Integer that sets the number of worker threads per app process. When started, each app process creates a corresponding number of threads to handle requests. The default value is |
thread_stack_size | Integer that defines the stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default value is system dependent and can be set with ulimit -s <SIZE_KB>. |
Example:
{
"type": "python",
"processes": 10,
"working_directory": "/www/store/",
"path": "/www/store/cart/",
"home": "/www/store/.virtualenv/",
"module": "wsgi",
"callable": "app",
"user": "www",
"group": "www"
}
You can provide the callable in two forms. The first one uses WSGI (PEP 333 or PEP 3333):
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield b'Hello, WSGI\n'
The second one, supported for Python 3.5+, uses ASGI:
async def application(scope, receive, send):
await send({
'type': 'http.response.start',
'status': 200
})
await send({
'type': 'http.response.body',
'body': b'Hello, ASGI\n'
})
Note
Legacy two-callable ASGI 2.0 applications were not supported prior to Unit 1.21.0.
Choose either one according to your needs; Unit will attempt to infer your
choice automatically. If automatic inference fails, use the protocol
option to name the interface explicitly.
Targets§
You can configure up to 254 individual entry points for a single Python application:
{
"applications": {
"python-app": {
"type": "python",
"path": "/www/apps/python-app/",
"targets": {
"foo": {
"module": "foo.wsgi",
"callable": "foo"
},
"bar": {
"module": "bar.wsgi",
"callable": "bar"
}
}
}
}
}
Each target is an object that specifies module
and optionally
callable
just like a common application does. Targets can be used by
the pass
options in listeners and routes to serve requests:
{
"listeners": {
"127.0.0.1:8080": {
"pass": "applications/python-app/foo"
},
"127.0.0.1:80": {
"pass": "routes"
}
},
"routes": [
{
"match": {
"uri": "/bar"
},
"action": {
"pass": "applications/python-app/bar"
}
}
]
}
The home
, path
, protocol
, threads
, and
thread_stack_size
settings are shared by all targets in the app.
Warning
If you specify targets
, there should be no module
or
callable
defined at the application level. Moreover, you can’t
combine WSGI and ASGI targets within a single app.
Ruby§
First, make sure to install Unit along with the Ruby language module.
Note
Unit uses the Rack interface to run Ruby scripts; you need to have it installed as well:
$ gem install rack
Besides common options, you have the following:
Option | Description |
---|---|
script (required) | Rack script pathname, including the .ru extension:
/www/rubyapp/script.ru . |
threads | Integer that sets the number of worker threads per app process. When started, each app process creates a corresponding number of threads to handle requests. The default value is |
hooks | Pathname of the .rb file defining the event hooks to be
called during the app’s lifecycle. |
Example:
{
"type": "ruby",
"processes": 5,
"user": "www",
"group": "www",
"script": "/www/cms/config.ru",
"hooks": "hooks.rb"
}
The hooks
script is evaluated when the application starts. If set, it
can define blocks of Ruby code named on_worker_boot
,
on_worker_shutdown
, on_thread_boot
, or
on_thread_shutdown
. If provided, these blocks are called at the
respective points of the application’s lifecycle, for example:
@mutex = Mutex.new
File.write("./hooks.#{Process.pid}", "hooks evaluated")
# Runs once at app load.
on_worker_boot do
File.write("./worker_boot.#{Process.pid}", "worker boot")
end
# Runs at worker process boot.
on_thread_boot do
@mutex.synchronize do
# Avoids a race condition that may crash the app.
File.write("./thread_boot.#{Process.pid}.#{Thread.current.object_id}",
"thread boot")
end
end
# Runs at worker thread boot.
on_thread_shutdown do
@mutex.synchronize do
# Avoids a race condition that may crash the app.
File.write("./thread_shutdown.#{Process.pid}.#{Thread.current.object_id}",
"thread shutdown")
end
end
# Runs at worker thread shutdown.
on_worker_shutdown do
File.write("./worker_shutdown.#{Process.pid}", "worker shutdown")
end
# Runs at worker process shutdown.
Use these hooks to add custom runtime logic to your application.
Note
For Ruby-based examples, see our Ruby on Rails and Redmine howtos or a basic sample.
Settings§
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 | Description |
---|---|
header_read_timeout | 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 | 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 | 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 | 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 | 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). |
static | Object that configures static asset handling, containing a single
object named mime_types . In turn, mime_types
defines specific MIME types as options. An option’s value can be a
string or an array of strings; each string must specify a filename
extension or a specific filename that is included in the MIME type. |
discard_unsafe_fields | Controls the parsing mode of header field names. If set to
The default value is |
Example:
{
"settings": {
"http": {
"header_read_timeout": 10,
"body_read_timeout": 10,
"send_timeout": 10,
"idle_timeout": 120,
"max_body_size": 6291456,
"static": {
"mime_types": {
"text/plain": [
".log",
"README",
"CHANGES"
]
}
},
"discard_unsafe_fields": false
}
}
}
Note
Built-in support for MIME types includes .aac
, .apng
,
.atom
, .avi
, .avif
, avifs
, .bin
,
.css
, .deb
, .dll
, .exe
, .flac
,
.gif
, .htm
, .html
, .ico
, .img
,
.iso
, .jpeg
, .jpg
, .js
, .json
,
.md
, .mid
, .midi
, .mp3
, .mp4
,
.mpeg
, .mpg
, .msi
, .ogg
, .otf
,
.pdf
, .php
, .png
, .rpm
, .rss
,
.rst
, .svg
, .ttf
, .txt
, .wav
,
.webm
, .webp
, .woff2
, .woff
, .xml
,
and .zip
. Built-ins can be overridden, and new types can be added:
# curl -X PUT -d '{"text/x-code": [".c", ".h"]}' /path/to/control.unit.sock \
http://localhost/config/settings/http/static/mime_types
{
"success": "Reconfiguration done."
}
Access Log§
To enable access logging, specify the log file path in the access_log
option of the config
object.
In the example below, all requests will be logged to
/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."
}
The log is written in the Combined Log Format. Example of a log line:
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"
Certificate Management§
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.
Note
For the details of certificate issuance and renewal in Unit, see an example in TLS with Certbot.
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 bundle file to Unit’s certificate storage under a suitable
name (in this case, bundle
):
# curl -X PUT --data-binary @bundle.pem --unix-socket \
/path/to/control.unit.sock http://localhost/certificates/bundle
{
"success": "Certificate chain uploaded."
}
Warning
Don’t use -d
for file upload with curl; this option
damages .pem
files. Use the --data-binary
option when
uploading file-based data to avoid data corruption.
Internally, Unit stores the 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"
}
}
]
}
}
}
Note
You can access individual certificates in your chain, as well as specific alternative names, by their indexes:
# curl -X GET --unix-socket /path/to/control.unit.sock \
http://localhost/certificates/bundle/chain/0/
# curl -X GET --unix-socket /path/to/control.unit.sock \
http://localhost/certificates/bundle/chain/0/subject/alt_names/0/
Next, add the uploaded bundle to a listener; the resulting control API configuration may look like this:
{
"certificates": {
"bundle": {
"key": "<key type>",
"chain": [
"<certificate chain, omitted for brevity>"
]
}
},
"config": {
"listeners": {
"*:443": {
"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
...
* 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 --unix-socket /path/to/control.unit.sock \
http://localhost/certificates/bundle
{
"success": "Certificate deleted."
}
Note
You can’t delete certificate bundles still referenced in your
configuration, overwrite existing bundles using PUT
, or (obviously)
delete non-existent ones.
Full Example§
{
"certificates": {
"example.com": {
"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"
}
}
]
},
"example.org": {
"key": "RSA (4096 bits)",
"chain": [
{
"subject": {
"common_name": "example.org",
"alt_names": [
"example.org",
"www.example.org"
],
"country": "US",
"state_or_province": "CA",
"organization": "Acme, Inc."
},
"issuer": {
"common_name": "intermediate.ca.example.org",
"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.org",
"country": "US",
"state_or_province": "CA",
"organization": "Acme Certification Authority"
},
"issuer": {
"common_name": "root.ca.example.org",
"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,
"static": {
"mime_types": {
"text/plain": [
".log",
"README",
"CHANGES"
]
}
},
"discard_unsafe_fields": false
}
},
"listeners": {
"*:8000": {
"pass": "routes",
"tls": {
"certificate": [
"example.com",
"example.org"
],
"conf_commands" : {
"ciphersuites": "TLS_CHACHA20_POLY1305_SHA256"
},
"session": {
"cache_size": 10240,
"timeout": 60,
"tickets": [
"k5qMHi7IMC7ktrPY3lZ+sL0Zm8oC0yz6re+y/zCj0H0/sGZ7yPBwGcb77i5vw6vCx8vsQDyuvmFb6PZbf03Auj/cs5IHDTYkKIcfbwz6zSU=",
"3Cy+xMFsCjAek3TvXQNmCyfXCnFNAcAOyH5xtEaxvrvyyCS8PJnjOiq2t4Rtf/Gq",
"8dUI0x3LRnxfN0miaYla46LFslJJiBDNdFiPJdqr37mYQVIzOWr+ROhyb1hpmg/QCM2qkIEWJfrJX3I+rwm0t0p4EGdEVOXQj7Z8vHFcbiA="
]
}
}
},
"127.0.0.1:8001": {
"pass": "applications/drive"
},
"*:8080": {
"pass": "upstreams/rr-lb",
"client_ip": {
"header": "X-Forwarded-For",
"source": [
"192.168.0.0.0/16"
]
}
}
},
"routes": [
{
"match": {
"uri": "/admin/*",
"scheme": "https",
"arguments": {
"mode": "strict",
"access": "!raw"
},
"cookies": {
"user_role": "admin"
}
},
"action": {
"pass": "applications/cms"
}
},
{
"match": {
"host": "admin.emea-*.*.example.com",
"source": "*:8000-9000"
},
"action": {
"pass": "applications/blogs/admin"
}
},
{
"match": {
"host": [
"blog.example.com",
"blog.*.org"
],
"source": "*:8000-9000"
},
"action": {
"pass": "applications/blogs/core"
}
},
{
"match": {
"host": "example.com",
"source": "127.0.0.1-127.0.0.254:8080-8090",
"uri": "/chat/*"
},
"action": {
"pass": "applications/chat"
}
},
{
"match": {
"host": "example.com",
"source": [
"198.51.100.0/24:8000",
"203.0.113.0/24:8080-8090"
]
},
"action": {
"pass": "applications/store"
}
},
{
"match": {
"host": "extwiki.example.com"
},
"action": {
"pass": "applications/wiki/external"
}
},
{
"match": {
"host": "intwiki.example.com"
},
"action": {
"pass": "applications/wiki/internal"
}
},
{
"match": {
"uri": "/legacy/*"
},
"action": {
"return": 301,
"location": "https://legacy.example.com"
}
},
{
"match": {
"scheme": "http"
},
"action": {
"proxy": "http://127.0.0.1:8080"
}
},
{
"action": {
"share": "/www/data/static/",
"chroot": "/www/data/",
"traverse_mounts": false,
"follow_symlinks": false,
"types": [
"image/*",
"video/*",
"application/json"
],
"fallback": {
"proxy": "http://127.0.0.1:9000"
}
}
}
],
"applications": {
"blogs": {
"type": "php",
"targets": {
"admin": {
"root": "/www/blogs/admin/",
"script": "index.php"
},
"core" : {
"root": "/www/blogs/scripts/"
}
},
"limits": {
"timeout": 10,
"requests": 1000
},
"options": {
"file": "/etc/php.ini",
"admin": {
"memory_limit": "256M",
"variables_order": "EGPCS",
"expose_php": "0"
},
"user": {
"display_errors": "0"
}
},
"processes": 4
},
"chat": {
"type": "external",
"executable": "bin/chat_app",
"group": "www-chat",
"user": "www-chat",
"working_directory": "/www/chat/",
"isolation": {
"namespaces": {
"cgroup": false,
"credential": true,
"mount": false,
"network": false,
"pid": false,
"uname": false
},
"uidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
],
"gidmap": [
{
"host": 1000,
"container": 0,
"size": 1000
}
],
"automount": {
"language_deps": false,
"procfs": false,
"tmpfs": false
}
}
},
"cms": {
"type": "ruby",
"script": "/www/cms/main.ru",
"working_directory": "/www/cms/",
"hooks": "hooks.rb"
},
"drive": {
"type": "perl",
"script": "app.psgi",
"threads": 2,
"thread_stack_size": 4096,
"working_directory": "/www/drive/",
"processes": {
"max": 10,
"spare": 5,
"idle_timeout": 20
}
},
"store": {
"type": "java",
"webapp": "/www/store/store.war",
"classpath": [
"/www/store/lib/store-2.0.0.jar"
],
"options": [
"-Dlog_path=/var/log/store.log"
]
},
"wiki": {
"type": "python",
"protocol": "asgi",
"targets": {
"internal": {
"module": "internal.asgi"
},
"external": {
"module": "external.asgi"
}
},
"environment": {
"DJANGO_SETTINGS_MODULE": "wiki.settings.prod",
"DB_ENGINE": "django.db.backends.postgresql",
"DB_NAME": "wiki",
"DB_HOST": "127.0.0.1",
"DB_PORT": "5432"
},
"path": "/www/wiki/",
"processes": 10
}
},
"upstreams": {
"rr-lb": {
"servers": {
"192.168.1.100:8080": {},
"192.168.1.101:8080": {
"weight": 2
}
}
}
},
"access_log": "/var/log/access.log"
}
}