Configuration§
Quick Start§
To run an application in 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 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 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 pass
value and leaves other options intact.
Examples§
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 wiki.json
:
{
"type": "python",
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki/"
}
Use it to set up an application called wiki-prod
:
# curl -X PUT --data-binary @/path/to/wiki.json \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-prod
Use it again to set up a development version of the same app called
wiki-dev
:
# curl -X PUT --data-binary @/path/to/wiki.json \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-dev
Toggle the 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
Next, boost the process count for the production app to warm it up a bit:
# curl -X PUT -d '5' \
--unix-socket /path/to/control.unit.sock http://localhost/config/applications/wiki-prod/processes
Add a listener for the 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'
Plug the 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'
Then rewire the listener, adding a URI-based route to the development version of the app:
# 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'
Next, let’s change the 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
Let’s add a route to the prod app: 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/
Otherwise, use 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/
To get the complete 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"
}
}
]
}
To obtain the 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/"
}
You can save JSON returned by such requests as .json
files for update
or review:
# curl --unix-socket /path/to/control.unit.sock \
http://localhost/config/ > config.json
To drop the listener on *:8400
:
# curl -X DELETE --unix-socket /path/to/control.unit.sock \
'http://localhost/config/listeners/*:8400'
Mind that you can’t delete objects that other objects rely on, such as a route still referenced by a listener:
# 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\"."
}
Listeners§
To start serving HTTP requests with Unit, define a listener in the
config/listeners
section of the API. A listener uniquely combines a
host IP (or a wildcard to match all host IPs) and a port that Unit binds to.
Note
On Linux-based systems, wildcard listeners can’t overlap with other
listeners on the same port due to kernel-imposed limitations; for example,
*:8080
conflicts with 127.0.0.1:8080
.
Unit dispatches the requests it receives to applications or routes referenced by listeners. You can plug several listeners into one app or route, or use a single listener for hot-swapping during testing or staging.
Available options:
Option | Description |
---|---|
pass (required) | Qualified app or route name: "pass": "routes/route66" ,
"pass": "applications/qwk2mart" . Mutually exclusive with
application . |
tls | SSL/TLS configuration object. Its only option, certificate ,
enables secure communication via the listener; it must name a
certificate chain that you have configured
earlier. |
application (deprecated) | App name: Warning This option is deprecated. Please update your configurations to use
|
Here, local requests at port 8300
are passed to the blogs
app;
all requests at 8400
follow the main
route:
{
"127.0.0.1:8300": {
"pass": "applications/blogs",
"tls": {
"certificate": "blogs-cert"
}
},
"*:8400": {
"pass": "routes/main"
}
}
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 matching all conditions of a step is passed to the app or the route
that the step specifies.
In its simplest form, routes
can be a single route array:
{
"listeners": {
"*:8300": {
"pass": "routes"
}
},
"routes": [ "simply referred to as routes" ]
}
Another form is an object with one or more named route arrays as members:
{
"listeners": {
"*:8300": {
"pass": "routes/main"
}
},
"routes": {
"main": [ "named route, qualified name: routes/main" ],
"route66": [ "named route, qualified name: routes/route66" ]
}
}
Route Object§
A route array contains step objects as elements; a request passed to a route traverses them sequentially.
Steps have the following options:
Option | Description |
---|---|
action/pass (required) | Route’s destination; identical to pass in a listener. |
match | Object that defines the step conditions.
If you omit |
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": {
"arguments": {
"site_access": "yes"
}
},
"action": {
"pass": "routes/site"
}
},
{
"match": {
"host": "blog.example.com"
},
"action": {
"pass": "applications/blog"
}
}
],
"site": [ "..." ]
}
}
Condition Matching§
To route incoming requests, Unit applies pattern-based conditions to individual request properties:
Option | Description | Case‑Sensitive |
---|---|---|
arguments | Parameter arguments supplied in the request URI. | Yes |
cookies | Cookies supplied with the request. | Yes |
headers | Header fields supplied with the request. | No |
host | Host from the Host header field without port number, normalized
by removing the trailing period (if any). | No |
method | Method from the request line. | No |
uri | URI path without arguments, normalized by decoding the “%XX” sequences, resolving relative path references (“.” and “..”), and compressing adjacent slashes into one. | Yes |
For host
, method
, and uri
, simple matching is used;
other properties use compound matching.
Simple Matching§
A simple property in a match
object is matched against a string pattern
or an array of patterns:
{
"match": {
"simple_property1": "pattern",
"simple_property2": ["pattern", "pattern", "..." ]
},
"action": {
"pass": "..."
}
}
To be a match against the condition, the property must meet two requirements:
- If there are patterns without negation, at least one of them matches the property value.
- No negation-based patterns match the property value.
Patterns must match the value symbol by symbol, with the exception of wildcards
(*
) and negations (!
):
- A wildcard matches zero or more arbitrary characters; wildcards can only
*prefix
exact patterns,suffix*
them,*enclose*
them, orsplit*them
in two. - A negation rejects all matches to the remainder of the pattern; pattern can only start with it.
Note
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:
A few examples:
{
"host": "*.example.com"
}
Only subdomains of example.com
will match.
{
"host": ["eu-*.example.com", "!eu-5.example.com"]
}
Here, any eu-
subdomains of example.com
will match except
eu-5.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 the ones containing /api/
.
Compound Matching§
This type of matching is used for arguments
, cookies
, and
headers
properties.
A compound property is matched against an object with names and patterns or an array of such objects:
{
"match": {
"compound_property1": {
"name1": "pattern",
"name2": ["pattern", "..."]
},
"compound_property2": [
{
"name1": "pattern",
"name2": ["pattern", "pattern", "..."]
},
{
"name1": "pattern",
"name3": ["pattern", "pattern", "..."]
}
]
},
"action": {
"pass": "..."
}
}
To match a single condition object, the request must contain all items explicitly named in the object; their values are matched against patterns in the same manner as property values during simple matching.
To match an object array, it’s sufficient to match any single one of its objects.
A few examples:
{
"arguments": {
"mode": "strict",
"access": "!full"
}
}
This requires mode=strict
and any access
argument other than
access=full
in the URI.
{
"headers": [
{
"Accept-Encoding": "*gzip*",
"User-Agent": "Mozilla/5.0*"
},
{
"User-Agent": "curl*"
}
]
}
This matches all requests that either use gzip
and identify as
Mozilla/5.0
or list curl as the user agent.
Note
You can mix simple and compound properties in a match
condition.
Passing Requests§
To match a step, the request must fit all property conditions listed in it.
If all properties match or you omit match
entirely, 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
.
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.
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 | Working directory for the app. If omitted, working directory of Unit daemon is used. |
user | Username that runs the app process. If omitted, nobody is
used. |
group | Group name that runs the app process. If omitted, 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"
}
}
Processes and Limits§
Apps have two options, limits
and processes
, that control how
an app’s processes are managed by Unit.
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
}
}
Process Management§
The processes
option offers 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 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
}
Go/Node.js§
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 app configuration; besides common options, you have the following:
Option | Description |
---|---|
executable (required) | Pathname of the application, absolute or relative to
For Node.js, supply your #!/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 or built 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 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.
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');
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 point to
directories or .jar files). |
options | Array of strings defining JVM runtime options. |
Example:
{
"type": "java",
"classpath": ["/www/qwk2mart/lib/qwk2mart-2.0.0.jar"],
"options": ["-Dlog_path=/var/log/qwk2mart.log"],
"webapp": "/www/qwk2mart/qwk2mart.war"
}
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. |
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
Default value is |
options | Object that defines php.ini location and options. For details,
see below. |
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.
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"
}
}
}
Python§
First, make sure to install Unit along with the Python language module.
Besides common options, you have the following:
Option | Description |
---|---|
module (required) | 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 | Path to Python’s virtual environment for the
app. Absolute or relative to Note The Python version used to run the app depends on the |
Example:
{
"type": "python",
"processes": 10,
"working_directory": "/www/store/",
"path": "/www/store/cart/",
"home": "/www/store/.virtualenv/",
"module": "wsgi",
"user": "www",
"group": "www"
}
Ruby§
First, make sure to install Unit along with the Ruby language module.
Besides common options, you have the following:
Option | Description |
---|---|
script (required) | Rack script path. |
Example:
{
"type": "ruby",
"processes": 5,
"user": "www",
"group": "www",
"script": "/www/cms/config.ru"
}
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). |
Example:
{
"settings": {
"http": {
"header_read_timeout": 10,
"body_read_timeout": 10,
"send_timeout": 10,
"idle_timeout": 120,
"max_body_size": 6291456
}
}
}
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"
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
Note
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 --unix-socket /path/to/control.unit.sock \
http://localhost/certificates/<bundle>
{
"success": "Certificate chain uploaded."
}
Warning
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"
}
},
]
}
}
}
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 a tls
object to the listener configuration, referencing the
uploaded bundle 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 --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.
Happy SSLing!
Full Example§
{
"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"
}
},
{
"match": {
"arguments": {
"mode": "strict",
"access": "!raw"
},
"cookies": {
"user_hash": "admin_hash"
}
},
"action": {
"pass": "applications/cms_admin"
}
}
]
},
"applications": {
"blogs": {
"type": "php",
"processes": 20,
"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"
}
}
},
"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"
}
}