diff --git a/config/_default/config.toml b/config/_default/config.toml index c7e20c100..812739793 100644 --- a/config/_default/config.toml +++ b/config/_default/config.toml @@ -47,7 +47,8 @@ enableGitInfo = true "taxonomyTerm" ] taxonomiesExcludedFromSitemap = ["tags", "categories", "doctypes"] - + unitversion= "1.34.1" + unitversionv= "v1.34.1" #logo = "" # Version lists; used by the versions shortcode diff --git a/content/includes/unit/howto_change_ownership.md b/content/includes/unit/howto_change_ownership.md new file mode 100644 index 000000000..36fa9d9e3 --- /dev/null +++ b/content/includes/unit/howto_change_ownership.md @@ -0,0 +1,18 @@ +Run the following command (as root) so Unit can access the application +directory (If the application uses several directories, run the command for +each one): + +```console +# chown -R unit:unit /path/to/app/ # User and group that Unit's router runs as by default +``` + + +{{< note >}} +The **unit:unit** user-group pair is available only with +[official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +, Docker [images]({{< relref "/unit/installation.md#installation-docker" >}}), +and some [third-party repos]({{< relref "/unit/installation.md#installation-community-repos" >}}). Otherwise, account names may differ; run the `ps aux | grep unitd` command to be sure. +{{< /note >}} + +For further details, including permissions, see the +[security checklist]({{< relref "/unit/howto/security.md#secutiry-apps" >}}). diff --git a/content/includes/unit/howto_install_app.md b/content/includes/unit/howto_install_app.md new file mode 100644 index 000000000..acd12fb6e --- /dev/null +++ b/content/includes/unit/howto_install_app.md @@ -0,0 +1,2 @@ +Install {{ app }}'s [app-link]. Here, we install it at **/path/to/app/**; use +a real path in your configuration. diff --git a/content/includes/unit/howto_install_prereq.md b/content/includes/unit/howto_install_prereq.md new file mode 100644 index 000000000..7018bd54e --- /dev/null +++ b/content/includes/unit/howto_install_prereq.md @@ -0,0 +1 @@ +Install and configure {{ app }}'s [app-preq]. diff --git a/content/includes/unit/howto_install_unit.md b/content/includes/unit/howto_install_unit.md new file mode 100644 index 000000000..8bf3f5bea --- /dev/null +++ b/content/includes/unit/howto_install_unit.md @@ -0,0 +1 @@ +Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a {{ mod }} language module. diff --git a/content/includes/unit/howto_upload_config.md b/content/includes/unit/howto_upload_config.md new file mode 100644 index 000000000..b218b7b51 --- /dev/null +++ b/content/includes/unit/howto_upload_config.md @@ -0,0 +1,14 @@ +Assuming the JSON above was added to +`config.json`. Run the following command as root: + +```console +# curl -X PUT --data-binary @config.json --unix-socket \ + /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/ # Path to the config section in Unit's control API +``` + +{{< note >}} +The [control socket]({{< relref "/unit/installation.md#configuration-socket" >}}) path may vary; run +`unitd -h` or see +[Startup and shutdown]({{< relref "/unit/howto/source.md#source-startup" >}}) for details. +{{< /note >}} diff --git a/content/includes/unit/version.md b/content/includes/unit/version.md new file mode 100644 index 000000000..4b06b5bed --- /dev/null +++ b/content/includes/unit/version.md @@ -0,0 +1 @@ +1.34.1 \ No newline at end of file diff --git a/content/unit/_index.md b/content/unit/_index.md new file mode 100644 index 000000000..a809a67b0 --- /dev/null +++ b/content/unit/_index.md @@ -0,0 +1,7 @@ +--- +title: NGINX Unit +description: A lightweight web app server that combines several layers of the typical application stack into a single component. +url: /nginx-unit/ +cascade: + logo: "NGINX-Unit-product-icon-RGB.png" +--- \ No newline at end of file diff --git a/content/unit/about.md b/content/unit/about.md new file mode 100644 index 000000000..14db002bc --- /dev/null +++ b/content/unit/about.md @@ -0,0 +1,25 @@ +--- +title: About NGINX Unit +weight: 100 +toc: true +--- + +# Universal web app server + +NGINX Unit is a lightweight and versatile application runtime that provides the +essential components for your web application as a single open-source server: +running application code (including WebAssembly), serving static assets, +handling TLS and request routing. + +Unit was created by [nginx](https://nginx.org/en/) team members from scratch to +be highly efficient and fully configurable at runtime. You can read the details +about the latest release in the [news]({{< relref "/unit/news/">}}) section. + +- See a quickstart [guide](https://github.com/nginx/unit/) on our GitHub page. +- Browse the [Changelog]({{< relref "/unit/changes/">}}) or see the release notes in the [Releases and announcements]({{< relref "/unit/news/">}}) archive. +- Check out the discussion of our [key features]({{< relref "/unit/keyfeatures.md">}}) for further + details. +- Peek at our future plans with a GitHub-based [roadmap](https://github.com/orgs/nginx/projects/1). + + +Watch the entire NGINX Unit tutorial course in the [NGINX YouTube channel](https://www.youtube.com/playlist?list=PLGz_X9w9raXdV3vuPUu0kKBSBjG9rPaUf). diff --git a/content/unit/certificates.md b/content/unit/certificates.md new file mode 100644 index 000000000..4861ca946 --- /dev/null +++ b/content/unit/certificates.md @@ -0,0 +1,193 @@ +--- +title: SSL/TLS certificates +weight: 800 +toc: true +--- + +The **/certificates** section of the [control API]({{< relref "/unit/controlapi.md" >}}) +handles TLS certificates that are used with Unit's +[listeners]({{< relref "/unit/configuration.md#configuration-listeners">}}). +To set up SSL/TLS for a listener, upload a **.pem** file with your certificate +chain and private key to Unit, and name the uploaded bundle in the listener's configuration; next, the listener can be accessed via SSL/TLS. + +{{< note >}} +For the details of certificate issuance and renewal in Unit, +see an example in [TLS with Certbot]({{< relref "/unit/howto/certbot.md" >}}). +{{< /note >}} + + +First, create a **.pem** file with your certificate chain and private key: + +```console +cat cert.pem ca.pem key.pem > bundle.pem # Leaf certificate file | CA certificate file | Private key file | Arbitrary certificate bundle's filename +``` + +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**), running the following command as root: + +```console +# 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. +{{< /warning >}} + +Internally, Unit stores the uploaded certificate bundles along with other configuration data in its **state** subdirectory; the +[control API]({{< relref "/unit/controlapi.md" >}}) +exposes some of their properties as **GET**-table JSON using **/certificates**: + +```json +{ + "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 2022 GMT", + "until": "Jun 15 19:46:19 2025 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 2023 GMT", + "until": "Feb 21 22:45:55 2026 GMT" + } + } + ] + } + } +} +``` + +{{< note >}} +Access array items, such as individual certificates in a chain, +and their properties by indexing, running the following commands as root: + +```console +# curl -X GET --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/bundle/chain/0/ # Certificate bundle name +``` + +```console +# curl -X GET --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/bundle/chain/0/subject/alt_names/0/ # Certificate bundle name +``` +{{< /note >}} + +Next, add the uploaded bundle to a +[listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}). +the resulting control API configuration may look like this: + +```json +{ + "certificates": { + "bundle": { + "key": "", + "chain": [ + "" + ], + "comment_bundle": "Certificate bundle name" + } + }, + + "config": { + "listeners": { + "*:443": { + "pass": "applications/wsgi-app", + "tls": { + "certificate": "bundle", + "comment_certificate": "Certificate bundle name" + } + } + }, + + "applications": { + "wsgi-app": { + "type": "python", + "module": "wsgi", + "path": "/usr/www/wsgi-app/" + } + } + } +} +``` + +All done; +the application is now accessible via SSL/TLS: + +```console +$ curl -v https://127.0.0.1 # Port 443 is conventionally used for HTTPS connections + ... + * 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, running the following command as root: + +```console +# curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/bundle # Certificate bundle name + +{ + "success": "Certificate deleted." +} +``` + +{{< note >}} +You can't delete certificate bundles still referenced in your configuration, +overwrite existing bundles using **put**, or delete non-existent ones. +{{< /note >}} diff --git a/content/unit/changes.md b/content/unit/changes.md new file mode 100644 index 000000000..f53aefdd8 --- /dev/null +++ b/content/unit/changes.md @@ -0,0 +1,1216 @@ +--- +title: Changelog +weight: 900 +toc: true +--- + +```text +Changes with Unit 1.34.1 10 Jan 2025 + + *) Bugfix: fix instability issues due to OpenTelemetry (OTEL) support. + + *) Bugfix: fix issues with building OpenTelemetry (OTEL) support on + various platforms, including macOS. + + +Changes with Unit 1.34.0 19 Dec 2024 + + *) Feature: initial OpenTelemetry (OTEL) support. (Disabled by default). + + *) Feature: support for JSON formatted access logs. + + *) Bugfix: tweak the Perl language module to avoid breaking scripts in + some circumstances. + + +Changes with Unit 1.33.0 17 Sep 2024 + + *) Feature: make the number of router threads configurable. + + *) Feature: make the listen(2) backlog configurable. + + *) Feature: add Python application factory support. + + *) Feature: add experimental chunked request body support. (Disabled by + default). + + *) Feature: add fuzzing via oss-fuzz. + + *) Feature: add "if" option to the "match" object. + + *) Feature: show list of loaded language modules in the /status + endpoint. + + *) Feature: Unit ships with a new Rust based CLI application "unitctl". + + *) Feature: the wasm-wasi-component language module now inherits the + processes environment. + + *) Change: under systemd unit runs in forking mode (once again). + + *) Change: if building with njs, version 0.8.3 or later is now required. + + *) Change: Unit now builds with -std=gnu11 (C11 with GNU extensions). + + *) Change: Unit now creates the full directory path for the PID file and + control socket. + + *) Change: build system improvements, including pretty printing the make + output and enabling various make variables to influence the build + process (see: make help). + + *) Change: better detection of available runnable CPUs on Linux. + + *) Change: default listen(2) backlog on Linux now defaults to Kernel + default. + + *) Bugfix: don't modify REQUEST_URI. + + *) Bugfix: fix a crash when interrupting a download via a proxy. + + *) Bugfix: wasm-wasi-component application process hangs after receiving + restart signal from the control endpoint. + + *) Bugfix: njs variables accessed with a JS template literal should not + be cacheable. + + *) Bugfix: properly handle deleting arrays of certificates. + + *) Bugfix: don't create the $runstatedir directory which triggered an + Alpine packaging error. + + +Changes with Unit 1.32.1 26 Mar 2024 + + *) Bugfix: NJS variables in templates may have incorrect values due to + improper caching. + + *) Bugfix: Wasm application process hangs after receiving restart signal + from the control. + + +Changes with Unit 1.32.0 27 Feb 2024 + + *) Feature: WebAssembly Components using WASI interfaces defined in + wasi:http/proxy@0.2.0. + + *) Feature: conditional access logging. + + *) Feature: njs variables access. + + *) Feature: $request_id variable contains a string that is formed using + random data and can be used as a unique request identifier. + + *) Feature: options to set control socket permissions. + + *) Feature: Ruby arrays in response headers, improving compatibility + with Rack v3.0. + + *) Feature: Python bytearray response bodies for ASGI applications. + + *) Bugfix: router could crash while sending large files. Thanks to + rustedsword. + + *) Bugfix: serving static files from a network filesystem could lead to + error. + + *) Bugfix: "uidmap" and "gidmap" isolation options validation. + + *) Bugfix: abstract UNIX socket name could be corrupted during + configuration validation. Thanks to Alejandro Colomar. + + *) Bugfix: HTTP header field value encoding could be misinterpreted in + Python module. + + *) Bugfix: Node.js http.createServer() accepts and ignores the "options" + argument, improving compatibility with strapi applications, among + others. + + *) Bugfix: ServerRequest.flushHeaders() implemented in Node.js module to + make it compatible with Next.js. + + *) Bugfix: ServerRequest.httpVersion variable format in Node.js module. + + *) Bugfix: Node.js module handles standard library imports prefixed with + "node:", making it possible to run newer Nuxt applications, among + others. + + *) Bugfix: Node.js tarball location changed to avoid build/install + errors. + + *) Bugfix: Go module sets environment variables necessary for building + on macOS/arm64 systems. + + +Changes with Unit 1.31.1 19 Oct 2023 + + *) Feature: allow to set the HTTP response status in Wasm module. + + *) Feature: allow uploads larger than 4GiB in Wasm module. + + *) Bugfix: application process could crash while rewriting URLs with + query strings. + + *) Bugfix: requests larger than about 64MiB could cause error in Wasm + module. + + *) Bugfix: when using many headers in Java module some of them could be + corrupted due to memory realocation issue. + + *) Bugfix: ServerRequest.destroy() implemented in Node.js module to make + it compatible with some frameworks that might use it. + + *) Bugfix: chunk argument of ServerResponse.write() can now be a + Uint8Array to improve compatibility with Node.js 15.0.0 and above. + + *) Bugfix: Node.JS unit-http NPM module now has appropriate default + paths for macOS/arm64 systems. + + *) Bugfix: build on musl libc with clang. + + +Changes with Unit 1.31.0 31 Aug 2023 + + *) Change: if building with njs, version 0.8.0 or later is now required. + + *) Feature: technology preview of WebAssembly application module. + + *) Feature: "response_headers" option to manage headers in the action + and fallback. + + *) Feature: HTTP response header variables. + + *) Feature: ASGI lifespan state support. Thanks to synodriver. + + *) Bugfix: ensure that $uri variable is not cached. + + *) Bugfix: deprecated options were unavailable. + + *) Bugfix: ASGI applications inaccessible over IPv6. + + +Changes with Unit 1.30.0 10 May 2023 + + *) Change: remove Unix domain listen sockets upon reconfiguration. + + *) Feature: basic URI rewrite support. + + *) Feature: njs loadable modules support. + + *) Feature: per-application logging. + + *) Feature: conditional logging of route selection. + + *) Feature: support the keys API on the request objects in njs. + + *) Feature: default values for 'make install' pathnames such as prefix; + this allows to './configure && make && sudo make install'. + + *) Feature: "server_version" setting to omit the version token from + "Server" header field. + + *) Bugfix: request header field values could be corrupted in some cases; + the bug had appeared in 1.29.0. + + *) Bugfix: PHP error handling (added missing 403 and 404 errors). + + *) Bugfix: Perl applications crash on second responder call. + + +Changes with Unit 1.29.1 28 Feb 2023 + + *) Bugfix: stop creating world-writeable directories. + + *) Bugfix: memory leak related to njs. + + *) Bugfix: path parsing in PHP applications. + + *) Bugfix: enabled UTF-8 for Python config by default to avoid + applications failing in some cases. + + *) Bugfix: using asyncio.get_running_loop() instead of + asyncio.get_event_loop() when it's available to prevent errors in + some Python ASGI applications. + + *) Bugfix: applications that make use of various low level APIs such as + pthreads could fail to work correctly. + + *) Bugfix: websocket endianness detection for obscure operating systems. + + +Changes with Unit 1.29.0 15 Dec 2022 + + *) Change: removed $uri auto-append for "share" when loading + configuration. + + *) Change: prefer system crypto policy instead of hardcoding a default. + + *) Feature: njs support with the basic syntax of JS template literals. + + *) Feature: support per-application cgroups on Linux. + + *) Feature: the $request_time variable contains the request processing + time. + + *) Feature: "prefix" option in Python applications to set WSGI + "SCRIPT_NAME" and ASGI root-path variables. + + *) Feature: compatibility with Python 3.11. + + *) Feature: compatibility with OpenSSL 3. + + *) Feature: compatibility with PHP 8.2. + + *) Feature: compatibility with Node.js 19.0. + + *) Feature: Ruby Rack v3 support. + + *) Bugfix: fix error in connection statistics when using proxy. + + *) Bugfix: fix HTTP cookie parsing when the value contains an equals + sign. + + *) Bugfix: PHP directory URLs without a trailing '/' would give a 503 + error (fixed with a 301 re-direct). + + *) Bugfix: missing error checks in the C API. + + *) Bugfix: report the regex status in configure summary. + + +Changes with Unit 1.28.0 13 Sep 2022 + + *) Change: increased the applications' startup timeout. + + *) Change: disallowed abstract Unix domain socket syntax in non-Linux + systems. + + *) Feature: basic statistics API. + + *) Feature: customizable access log format. + + *) Feature: more HTTP variables support. + + *) Feature: forwarded header to replace client address and protocol. + + *) Feature: ability to get dynamic variables. + + *) Feature: support for abstract Unix sockets. + + *) Feature: support for Unix sockets in address matching. + + *) Feature: the $dollar variable translates to a literal "$" during + variable substitution. + + *) Bugfix: router process could crash if index file didn't contain an + extension. + + *) Bugfix: force SCRIPT_NAME in Ruby to always be an empty string. + + *) Bugfix: when isolated PID numbers reach the prototype process host + PID, the prototype crashed. + + *) Bugfix: the Ruby application process could crash on SIGTERM. + + *) Bugfix: the Ruby application process could crash on SIGINT. + + *) Bugfix: mutex leak in the C API. + + +Changes with Unit 1.27.0 02 Jun 2022 + + *) Feature: ability to specify a custom index file name when serving + static files. + + *) Feature: variables support in the "location" option of the "return" + action. + + *) Feature: support empty strings in the "location" option of the + "return" action. + + *) Feature: added a new variable, $request_uri, that includes both the + path and the query parts as per RFC 3986, sections 3-4. + + *) Feature: Ruby Rack environment parameter "SCRIPT_NAME" support. + + *) Feature: compatibility with GCC 12. + + *) Bugfix: Ruby Sinatra applications don't work without custom logging. + + *) Bugfix: the controller process could crash when a chain of more than + four certificates was uploaded. + + *) Bugfix: some Perl applications failed to process the request body, + notably with Plack. + + *) Bugfix: some Spring Boot applications failed to start, notably with + Grails. + + *) Bugfix: incorrect Python protocol auto detection (ASGI or WSGI) for + native callable object, notably with Falcon. + + *) Bugfix: ECMAScript modules did not work with the recent Node.js + versions. + + +Changes with Unit 1.26.1 02 Dec 2021 + + *) Bugfix: occasionally, the Unit daemon was unable to fully terminate; + the bug had appeared in 1.26.0. + + *) Bugfix: a prototype process could crash on an application process + exit; the bug had appeared in 1.26.0. + + *) Bugfix: the router process crashed on reconfiguration if "access_log" + was configured without listeners. + + *) Bugfix: a segmentation fault occurred in the PHP module if chdir() or + fastcgi_finish_request() was called in the OPcache preloading script. + + *) Bugfix: fatal errors on DragonFly BSD; the bug had appeared in + 1.26.0. + + +Changes with Unit 1.26.0 18 Nov 2021 + + *) Change: the "share" option now specifies the entire path to the files + it serves, rather than a document root directory to be prepended to + the request URI. + + *) Feature: automatic adjustment of existing configurations to the new + "share" behavior when updating from previous versions. + + *) Feature: variables support in the "share" option. + + *) Feature: multiple paths in the "share" option. + + *) Feature: variables support in the "chroot" option. + + *) Feature: PHP opcache is shared between application processes. + + *) Feature: request routing by the query string. + + *) Bugfix: the router and app processes could crash when the requests + limit was reached by asynchronous or multithreaded apps. + + *) Bugfix: established WebSocket connections could stop reading frames + from the client after the corresponding listener had been + reconfigured. + + *) Bugfix: fixed building with glibc 2.34, notably Fedora 35. + + +Changes with Unit 1.25.0 19 Aug 2021 + + *) Feature: client IP address replacement from a specified HTTP header + field. + + *) Feature: TLS sessions cache. + + *) Feature: TLS session tickets. + + *) Feature: application restart control. + + *) Feature: process and thread lifecycle hooks in Ruby. + + *) Bugfix: the router process could crash on TLS connection open when + multiple listeners with TLS certificates were configured; the bug had + appeared in 1.23.0. + + *) Bugfix: TLS connections were rejected for configurations with + multiple certificate bundles in a listener if the client did not use + SNI. + + *) Bugfix: the router process could crash with frequent multithreaded + application reconfiguration. + + *) Bugfix: compatibility issues with some Python ASGI apps, notably + based on the Starlette framework. + + *) Bugfix: a descriptor and memory leak occurred in the router process + when an app process stopped or crashed. + + *) Bugfix: the controller or router process could crash if the + configuration contained a full-form IPv6 in a listener address. + + *) Bugfix: the router process crashed when a request was passed to an + empty "routes" or "upstreams" using a variable "pass" option. + + *) Bugfix: the router process crashed while matching a request to an + empty array of source or destination address patterns. + + +Changes with Unit 1.24.0 27 May 2021 + + *) Change: PHP added to the default MIME type list. + + *) Feature: arbitrary configuration of TLS connections via OpenSSL + commands. + + *) Feature: the ability to limit static file serving by MIME types. + + *) Feature: support for chrooting, rejecting symlinks, and rejecting + mount point traversal on a per-request basis when serving static + files. + + *) Feature: a loader for automatically overriding the "http" and + "websocket" modules in Node.js. + + *) Feature: multiple "targets" in Python applications. + + *) Feature: compatibility with Ruby 3.0. + + *) Bugfix: the router process could crash while closing a TLS + connection. + + *) Bugfix: a segmentation fault might have occurred in the PHP module if + fastcgi_finish_request() was used with the "auto_globals_jit" option + enabled. + + +Changes with Unit 1.23.0 25 Mar 2021 + + *) Feature: support for multiple certificate bundles on a listener via + the Server Name Indication (SNI) TLS extension. + + *) Feature: "--mandir" ./configure option to specify the directory for + man page installation. + + *) Bugfix: the router process could crash on premature TLS connection + close; the bug had appeared in 1.17.0. + + *) Bugfix: a connection leak occurred on premature TLS connection close; + the bug had appeared in 1.6. + + *) Bugfix: a descriptor and memory leak occurred in the router process + when processing small WebSocket frames from a client; the bug had + appeared in 1.19.0. + + *) Bugfix: a descriptor leak occurred in the router process when + removing or reconfiguring an application; the bug had appeared in + 1.19.0. + + *) Bugfix: persistent storage of certificates might've not worked with + some filesystems in Linux, and all uploaded certificate bundles were + forgotten after restart. + + *) Bugfix: the controller process could crash while requesting + information about a certificate with a non-DNS SAN entry. + + *) Bugfix: the controller process could crash on manipulations with a + certificate containing a SAN and no standard name attributes in + subject or issuer. + + *) Bugfix: the Ruby module didn't respect the user locale for defaults + in the Encoding class. + + *) Bugfix: the PHP 5 module failed to build with thread safety enabled; + the bug had appeared in 1.22.0. + + +Changes with Unit 1.22.0 04 Feb 2021 + + *) Feature: the ServerRequest and ServerResponse objects of Node.js + module are now compliant with Stream API. + + *) Feature: support for specifying multiple directories in the "path" + option of Python apps. + + *) Bugfix: a memory leak occurred in the router process when serving + files larger than 128K; the bug had appeared in 1.13.0. + + *) Bugfix: apps could stop processing new requests under high load; the + bug had appeared in 1.19.0. + + *) Bugfix: app processes could terminate unexpectedly under high load; + the bug had appeared in 1.19.0. + + *) Bugfix: invalid HTTP responses were generated for some unusual status + codes. + + *) Bugfix: the PHP_AUTH_USER, PHP_AUTH_PW, and PHP_AUTH_DIGEST server + variables were missing in the PHP module. + + *) Bugfix: the router process could crash with multithreaded apps under + high load. + + *) Bugfix: Ruby apps with multithreading configured could crash on start + under load. + + *) Bugfix: mount points weren't unmounted when the "mount" namespace + isolation was used; the bug had appeared in 1.21.0. + + *) Bugfix: the router process could crash while removing or + reconfiguring an app that used WebSocket. + + *) Bugfix: a memory leak occurring in the router process when removing + or reconfiguring an application; the bug had appeared in 1.19.0. + + +Changes with Unit 1.21.0 19 Nov 2020 + + *) Change: procfs is mounted by default for all languages when "rootfs" + isolation is used. + + *) Change: any characters valid according to RFC 7230 are now allowed in + HTTP header field names. + + *) Change: HTTP header fields with underscores ("_") are now discarded + from requests by default. + + *) Feature: optional multithreaded request processing for Java, Python, + Perl, and Ruby apps. + + *) Feature: regular expressions in route matching patterns. + + *) Feature: compatibility with Python 3.9. + + *) Feature: the Python module now supports ASGI 2.0 legacy applications. + + *) Feature: the "protocol" option in Python applications aids choice + between ASGI and WSGI. + + *) Feature: the fastcgi_finish_request() PHP function that finalizes + request processing and continues code execution without holding onto + the client connection. + + *) Feature: the "discard_unsafe_fields" HTTP option that enables + discarding request header fields with irregular (but still valid) + characters in the field name. + + *) Feature: the "procfs" and "tmpfs" automount isolation options to + disable automatic mounting of eponymous filesystems. + + *) Bugfix: the router process could crash when running Go applications + under high load; the bug had appeared in 1.19.0. + + *) Bugfix: some language dependencies could remain mounted after using + "rootfs" isolation. + + *) Bugfix: various compatibility issues in Java applications. + + *) Bugfix: the Java module built with the musl C library couldn't run + applications that use "rootfs" isolation. + + +Changes with Unit 1.20.0 08 Oct 2020 + + *) Change: the PHP module is now initialized before chrooting; this + enables loading all extensions from the host system. + + *) Change: AVIF and APNG image formats added to the default MIME type + list. + + *) Change: functional tests migrated to the pytest framework. + + *) Feature: the Python module now fully supports applications that use + the ASGI 3.0 server interface. + + *) Feature: the Python module now has a built-in WebSocket server + implementation for applications, compatible with the HTTP & WebSocket + ASGI Message Format 2.1 specification. + + *) Feature: automatic mounting of an isolated "/tmp" file system into + chrooted application environments. + + *) Feature: the $host variable contains a normalized "Host" request + value. + + *) Feature: the "callable" option sets Python application callable + names. + + *) Feature: compatibility with PHP 8 RC 1. Thanks to Remi Collet. + + *) Feature: the "automount" option in the "isolation" object allows to + turn off the automatic mounting of language module dependencies. + + *) Bugfix: "pass"-ing requests to upstreams from a route was broken; the + bug had appeared in 1.19.0. Thanks to 洪志道 (Hong Zhi Dao) for + discovering and fixing it. + + *) Bugfix: the router process could crash during reconfiguration. + + *) Bugfix: a memory leak occurring in the router process; the bug had + appeared in 1.18.0. + + *) Bugfix: the "!" (non-empty) pattern was matched incorrectly; the bug + had appeared in 1.19.0. + + *) Bugfix: fixed building on platforms without sendfile() support, + notably NetBSD; the bug had appeared in 1.16.0. + + +Changes with Unit 1.19.0 13 Aug 2020 + + *) Feature: reworked IPC between the router process and the applications + to lower latencies, increase performance, and improve scalability. + + *) Feature: support for an arbitrary number of wildcards in route + matching patterns. + + *) Feature: chunked transfer encoding in proxy responses. + + *) Feature: basic variables support in the "pass" option. + + *) Feature: compatibility with PHP 8 Beta 1. Thanks to Remi Collet. + + *) Bugfix: the router process could crash while passing requests to an + application under high load. + + *) Bugfix: a number of language modules failed to build on some systems; + the bug had appeared in 1.18.0. + + *) Bugfix: time in error log messages from PHP applications could lag. + + *) Bugfix: reconfiguration requests could hang if an application had + failed to start; the bug had appeared in 1.18.0. + + *) Bugfix: memory leak during reconfiguration. + + *) Bugfix: the daemon didn't start without language modules; the bug had + appeared in 1.18.0. + + *) Bugfix: the router process could crash at exit. + + *) Bugfix: Node.js applications could crash at exit. + + *) Bugfix: the Ruby module could be linked against a wrong library + version. + + +Changes with Unit 1.18.0 28 May 2020 + + *) Feature: the "rootfs" isolation option for changing root filesystem + for an application. + + *) Feature: multiple "targets" in PHP applications. + + *) Feature: support for percent-encoding in the "uri" and "arguments" + matching options and in the "pass" option. + + +Changes with Unit 1.17.0 16 Apr 2020 + + *) Feature: a "return" action with optional "location" for immediate + responses and external redirection. + + *) Feature: fractional weights support for upstream servers. + + *) Bugfix: accidental 502 "Bad Gateway" errors might have occurred in + applications under high load. + + *) Bugfix: memory leak in the router; the bug had appeared in 1.13.0. + + *) Bugfix: segmentation fault might have occurred in the router process + when reaching open connections limit. + + *) Bugfix: "close() failed (9: Bad file descriptor)" alerts might have + appeared in the log while processing large request bodies; the bug + had appeared in 1.16.0. + + *) Bugfix: existing application processes didn't reopen the log file. + + *) Bugfix: incompatibility with some Node.js applications. + + *) Bugfix: broken build on DragonFly BSD; the bug had appeared in + 1.16.0. + + +Changes with Unit 1.16.0 12 Mar 2020 + + *) Feature: basic load-balancing support with round-robin. + + *) Feature: a "fallback" option that performs an alternative action if a + request can't be served from the "share" directory. + + *) Feature: reduced memory consumption by dumping large request bodies + to disk. + + *) Feature: stripping UTF-8 BOM and JavaScript-style comments from + uploaded JSON. + + *) Bugfix: negative address matching in router might work improperly in + combination with non-negative patterns. + + *) Bugfix: Java Spring applications failed to run; the bug had appeared + in 1.10.0. + + *) Bugfix: PHP 7.4 was broken if it was built with thread safety + enabled. + + *) Bugfix: compatibility issues with some Python applications. + + +Changes with Unit 1.15.0 06 Feb 2020 + + *) Change: extensions of dynamically requested PHP scripts were + restricted to ".php". + + *) Feature: compatibility with Ruby 2.7. + + *) Bugfix: segmentation fault might have occurred in the router process + with multiple application processes under load; the bug had appeared + in 1.14.0. + + *) Bugfix: receiving request body over TLS connection might have + stalled. + + +Changes with Unit 1.14.0 26 Dec 2019 + + *) Change: the Go package import name changed to "unit.nginx.org/go". + + *) Change: Go package now links to libunit instead of including library + sources. + + *) Feature: ability to change user and group for isolated applications + when Unit daemon runs as an unprivileged user. + + *) Feature: request routing by source and destination addresses and + ports. + + *) Bugfix: memory bloat on large responses. + + +Changes with Unit 1.13.0 14 Nov 2019 + + *) Feature: basic support for HTTP reverse proxying. + + *) Feature: compatibility with Python 3.8. + + *) Bugfix: memory leak in Python application processes when the close + handler was used. + + *) Bugfix: threads in Python applications might not work correctly. + + *) Bugfix: Ruby on Rails applications might not work on Ruby 2.6. + + *) Bugfix: backtraces for uncaught exceptions in Python 3 might be + logged with significant delays. + + *) Bugfix: explicit setting a namespaces isolation option to false might + have enabled it. + + +Changes with Unit 1.12.0 03 Oct 2019 + + *) Feature: compatibility with PHP 7.4. + + *) Bugfix: descriptors leak on process creation; the bug had appeared in + 1.11.0. + + *) Bugfix: TLS connection might be closed prematurely while sending + response. + + *) Bugfix: segmentation fault might have occurred if an irregular file + was requested. + + +Changes with Unit 1.11.0 19 Sep 2019 + + *) Feature: basic support for serving static files. + + *) Feature: isolation of application processes with Linux namespaces. + + *) Feature: built-in WebSocket server implementation for Java Servlet + Containers. + + *) Feature: direct addressing of API configuration options containing + slashes "/" using URI encoding (%2F). + + *) Bugfix: segmentation fault might have occurred in Go applications + under high load. + + *) Bugfix: WebSocket support was broken if Unit was built with some + linkers other than GNU ld (e.g. gold or LLD). + + +Changes with Unit 1.10.0 22 Aug 2019 + + *) Change: matching of cookies in routes made case sensitive. + + *) Change: decreased log level of common errors when clients close + connections. + + *) Change: removed the Perl module's "--include=" ./configure option. + + *) Feature: built-in WebSocket server implementation for Node.js module. + + *) Feature: splitting PATH_INFO from request URI in PHP module. + + *) Feature: request routing by scheme (HTTP or HTTPS). + + *) Feature: support for multipart requests body in Java module. + + *) Feature: improved API compatibility with Node.js 11.10 or later. + + *) Bugfix: reconfiguration failed if "listeners" or "applications" + objects were missing. + + *) Bugfix: applying a large configuration might have failed. + + +Changes with Unit 1.9.0 30 May 2019 + + *) Feature: request routing by arguments, headers, and cookies. + + *) Feature: route matching patterns allow a wildcard in the middle. + + *) Feature: POST operation for appending elements to arrays in + configuration. + + *) Feature: support for changing credentials using CAP_SETUID and + CAP_SETGID capabilities on Linux without running main process as + privileged user. + + *) Bugfix: memory leak in the router process might have happened when a + client prematurely closed the connection. + + *) Bugfix: applying a large configuration might have failed. + + *) Bugfix: PUT and DELETE operations on array elements in configuration + did not work. + + *) Bugfix: request schema in applications did not reflect TLS + connections. + + *) Bugfix: restored compatibility with Node.js applications that use + ServerResponse._implicitHeader() function; the bug had appeared in + 1.7. + + *) Bugfix: various compatibility issues with Node.js applications. + + +Changes with Unit 1.8.0 01 Mar 2019 + + *) Change: now three numbers are always used for versioning: major, + minor, and patch versions. + + *) Change: now QUERY_STRING is always defined even if the request does + not include the query component. + + *) Feature: basic internal request routing by Host, URI, and method. + + *) Feature: experimental support for Java Servlet Containers. + + *) Bugfix: segmentation fault might have occurred in the router process. + + *) Bugfix: various potential memory leaks. + + *) Bugfix: TLS connections might have stalled. + + *) Bugfix: some Perl applications might have failed to send the response + body. + + *) Bugfix: some compilers with specific flags might have produced + non-functioning builds; the bug had appeared in 1.5. + + *) Bugfix: Node.js package had wrong version number when installed from + sources. + + +Changes with Unit 1.7.1 07 Feb 2019 + + *) Security: a heap memory buffer overflow might have been caused in the + router process by a specially crafted request, potentially resulting + in a segmentation fault or other unspecified behavior + (CVE-2019-7401). + + *) Bugfix: install of Go module failed without prior building of Unit + daemon; the bug had appeared in 1.7. + + +Changes with Unit 1.7 20 Dec 2018 + + *) Change: now rpath is set in Ruby module only if the library was not + found in default search paths; this allows to meet packaging + restrictions on some systems. + + *) Bugfix: "disable_functions" and "disable_classes" PHP options set via + Control API did not work. + + *) Bugfix: Promises on request data in Node.js were not triggered. + + *) Bugfix: various compatibility issues with Node.js applications. + + *) Bugfix: a segmentation fault occurred in Node.js module if + application tried to read request body after request.end() was + called. + + *) Bugfix: a segmentation fault occurred in Node.js module if + application attempted to send header twice. + + *) Bugfix: names of response header fields in Node.js module were + erroneously treated as case-sensitive. + + *) Bugfix: uncatched exceptions in Node.js were not logged. + + *) Bugfix: global install of Node.js module from sources was broken on + some systems; the bug had appeared in 1.6. + + *) Bugfix: traceback for exceptions during initialization of Python + applications might not be logged. + + *) Bugfix: PHP module build failed if PHP interpreter was built with + thread safety enabled. + + +Changes with Unit 1.6 15 Nov 2018 + + *) Change: "make install" now installs Node.js module as well if it was + configured. + + *) Feature: "--local" ./configure option to install Node.js module + locally. + + *) Bugfix: Node.js module might have crashed due to broken reference + counting. + + *) Bugfix: asynchronous operations in Node.js might not have worked. + + *) Bugfix: various compatibility issues with Node.js applications. + + *) Bugfix: "freed pointer is out of pool" alerts might have appeared in + log. + + *) Bugfix: module discovery did not work on 64-bit big-endian systems + like IBM/S390x. + + +Changes with Unit 1.5 25 Oct 2018 + + *) Change: the "type" of application object for Go was changed to + "external". + + *) Feature: initial version of Node.js package with basic HTTP + request-response support. + + *) Feature: compatibility with LibreSSL. + + *) Feature: --libdir and --incdir ./configure options to install libunit + headers and static library. + + *) Bugfix: connection might be closed prematurely while sending + response; the bug had appeared in 1.3. + + *) Bugfix: application processes might have stopped handling requests, + producing "last message send failed: Resource temporarily + unavailable" alerts in log; the bug had appeared in 1.4. + + *) Bugfix: Go applications did not work when Unit was built with musl C + library. + + +Changes with Unit 1.4 20 Sep 2018 + + *) Change: the control API maps the configuration object only at + "/config/". + + *) Feature: TLS support for client connections. + + *) Feature: TLS certificates storage control API. + + *) Feature: Unit library (libunit) to streamline language module + integration. + + *) Feature: "408 Request Timeout" responses while closing HTTP + keep-alive connections. + + *) Feature: improvements in OpenBSD support. Thanks to David Carlier. + + *) Bugfix: a segmentation fault might have occurred after + reconfiguration. + + *) Bugfix: building on systems with non-default locale might be broken. + + *) Bugfix: "header_read_timeout" might not work properly. + + *) Bugfix: header fields values with non-ASCII bytes might be handled + incorrectly in Python 3 module. + + +Changes with Unit 1.3 13 Jul 2018 + + *) Change: UTF-8 characters are now allowed in request header field + values. + + *) Feature: configuration of the request body size limit. + + *) Feature: configuration of various HTTP connection timeouts. + + *) Feature: Ruby module now automatically uses Bundler where possible. + + *) Feature: http.Flusher interface in Go module. + + *) Bugfix: various issues in HTTP connection errors handling. + + *) Bugfix: requests with body data might be handled incorrectly in PHP + module. + + *) Bugfix: individual PHP configuration options specified via control + API were reset to previous values after the first request in + application process. + + +Changes with Unit 1.2 07 Jun 2018 + + *) Feature: configuration of environment variables for application + processes. + + *) Feature: customization of php.ini path. + + *) Feature: setting of individual PHP configuration options. + + *) Feature: configuration of execution arguments for Go applications. + + *) Bugfix: keep-alive connections might hang after reconfiguration. + + +Changes with Unit 1.1 26 Apr 2018 + + *) Bugfix: Python applications that use the write() callable did not + work. + + *) Bugfix: virtual environments created with Python 3.3 or above might + not have worked. + + *) Bugfix: the request.Read() function in Go applications did not + produce EOF when the whole body was read. + + *) Bugfix: a segmentation fault might have occurred while access log + reopening. + + *) Bugfix: in parsing of IPv6 control socket addresses. + + *) Bugfix: loading of application modules was broken on OpenBSD. + + *) Bugfix: a segmentation fault might have occurred when there were two + modules with the same type and version; the bug had appeared in 1.0. + + *) Bugfix: alerts "freed pointer points to non-freeble page" might have + appeared in log on 32-bit platforms. + + +Changes with Unit 1.0 12 Apr 2018 + + *) Change: configuration object moved into "/config/" path. + + *) Feature: basic access logging. + + *) Bugfix: 503 error occurred if Go application did not write response + header or body. + + *) Bugfix: Ruby applications that use encoding conversions might not + have worked. + + *) Bugfix: various stability issues. + + +Changes with Unit 0.7 22 Mar 2018 + + *) Feature: Ruby application module. + + *) Bugfix: in discovering modules. + + *) Bugfix: various race conditions on reconfiguration and during + shutting down. + + *) Bugfix: tabs and trailing spaces were not allowed in header fields + values. + + *) Bugfix: a segmentation fault occurred in Python module if + start_response() was called outside of WSGI callable. + + *) Bugfix: a segmentation fault might have occurred in PHP module if + there was an error while initialization. + + +Changes with Unit 0.6 09 Feb 2018 + + *) Bugfix: the main process died when the "type" application option + contained version; the bug had appeared in 0.5. + + +Changes with Unit 0.5 08 Feb 2018 + + *) Change: the "workers" application option was removed, the "processes" + application option should be used instead. + + *) Feature: the "processes" application option with prefork and dynamic + process management support. + + *) Feature: Perl application module. + + *) Bugfix: in reading client request body; the bug had appeared in 0.3. + + *) Bugfix: some Python applications might not have worked due to missing + "wsgi.errors" environ variable. + + *) Bugfix: HTTP chunked responses might be encoded incorrectly on 32-bit + platforms. + + *) Bugfix: infinite looping in HTTP parser. + + *) Bugfix: segmentation fault in router. + + +Changes with Unit 0.4 15 Jan 2018 + + *) Feature: compatibility with DragonFly BSD. + + *) Feature: "configure php --lib-static" option. + + *) Bugfix: HTTP request body was not passed to application; the bug had + appeared in 0.3. + + *) Bugfix: HTTP large header buffers allocation and deallocation fixed; + the bug had appeared in 0.3. + + *) Bugfix: some PHP applications might not have worked with relative + "root" path. + + +Changes with Unit 0.3 28 Dec 2017 + + *) Change: the Go package name changed to "nginx/unit". + + *) Change: in the "limits.timeout" application option: application start + time and time in queue now are not accounted. + + *) Feature: the "limits.requests" application option. + + *) Feature: application request processing latency optimization. + + *) Feature: HTTP keep-alive connections support. + + *) Feature: the "home" Python virtual environment configuration option. + + *) Feature: Python atexit hook support. + + *) Feature: various Go package improvements. + + *) Bugfix: various crashes fixed. + + +Changes with Unit 0.2 19 Oct 2017 + + *) Feature: configuration persistence. + + *) Feature: improved handling of configuration errors. + + *) Feature: application "timeout" property. + + *) Bugfix: POST request for PHP were handled incorrectly. + + *) Bugfix: the router exited abnormally if all listeners had been + deleted. + + *) Bugfix: the router crashed under load. + + *) Bugfix: memory leak in the router. + + +Changes with Unit 0.1 06 Sep 2017 + + *) First public release. +``` diff --git a/content/unit/community.md b/content/unit/community.md new file mode 100644 index 000000000..a61629678 --- /dev/null +++ b/content/unit/community.md @@ -0,0 +1,64 @@ +--- +title: Community +weight: 1200 +toc: true +--- + + + + +The [Discussions section](https://github.com/nginx/unit/discussions) of the +Unit repo is the main place for discussing any topics related to the NGINX Unit project. + +Here you can share your ideas, ask questions, and discuss any issues both about +using Unit and contributing to Unit. + +## Reporting bugs and issues + +- You can report issues and bugs on the + [Issues](https://github.com/nginx/unit/issues) section of the Unit repo. +- If you have found a fix or want to suggest a change to the code, + you can open a [pull request](https://github.com/nginx/unit/pulls) + on the Unit repo. + +## Reporting security concerns + +Please report any security-related issues concerning Unit to +[security-alert@nginx.org](mailto:security-alert@nginx.org). +For our mutual convenience, specifically mention **NGINX Unit** in the subject and, if possible, provide a [CVSS score](https://www.first.org/cvss/). + +## Contributing + +We welcome contributions to the NGINX Unit project. + +If you have a feature request or want to suggest an improvement, please submit a pull request on the [unit repo](https://github.com/nginx/unit/pulls). + +Please review the [Contributing Guidelines](https://github.com/nginx/unit/blob/master/CONTRIBUTING.md) before submitting your pull request. + +You can also ask questions and discuss your suggestions for Unit by: + +- Visiting the [Discussions section](https://github.com/nginx/unit/discussions) + of the Unit repo. +- Subscribing to the [unit@nginx.org mailing list](https://mailman.nginx.org/mailman3/lists/unit.nginx.org/) + and posting your ideas there. + +## Documentation + +If you are interested in contributing to the **Unit documentation**, +or find a typo or an error in the docs, you can submit a pull request on the +[unit-docs repo](https://github.com/nginx/documentation/pulls). + +## Source Code + +The source code for the NGINX Unit project is available on GitHub. + +{{}} + +| Repository | Description | +|------------|------------| +| [github.com/nginx/unit](https://github.com/nginx/unit) | The repository for the NGINX Unit project source code. | +| [github.com/nginx/documentation`](https://github.com/nginx/documentation) | The source code for the NGINX Unit documentation you are currently reading. | +| [`github.com/nginx/unit/tree/master/tools`](https://github.com/nginx/unit/tree/master/tools) | Source code for the NGINX Unit command-line tools. | + +{{}} + diff --git a/content/unit/community.md:Zone.Identifier b/content/unit/community.md:Zone.Identifier new file mode 100644 index 000000000..e69de29bb diff --git a/content/unit/configuration.md b/content/unit/configuration.md new file mode 100644 index 000000000..cf7487c2b --- /dev/null +++ b/content/unit/configuration.md @@ -0,0 +1,4155 @@ +--- +title: Configuration +weight: 600 +toc: true +--- + +{{}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{}} + +The **/config** section of the +[control API]({{< relref "/unit/controlapi.md#configuration-api" >}}) +handles Unit's general configuration with entities such as +[listeners]({{< relref "/unit/configuration.md#configuration-listeners" >}}), +[routes]({{< relref "/unit/configuration.md#configuration-routes" >}}), +[applications]({{< relref "/unit/configuration.md#configuration-applications" >}}), +or +[upstreams]({{< relref "/unit/configuration.md#configuration-upstreams" >}}). + +## Listeners {#configuration-listeners} + +To accept requests, +add a listener object in the **config/listeners** API section; +the object's name can be: + +- A unique IP socket: + **127.0.0.1:80**, **\[::1\]:8080** +- A wildcard that matches any host IPs on the port: + **\*:80** +- On Linux-based systems, [abstract UNIX sockets](https://man7.org/linux/man-pages/man7/unix.7.html) +can be used as well: **unix:@abstract_socket**. + +{{< note >}} +Also on Linux-based systems, wildcard listeners can't overlap with other listeners +on the same port due to rules imposed by the kernel. +For example, **\*:8080** conflicts with **127.0.0.1:8080**; +in particular, this means **\*:8080** can't be *immediately* replaced +by **127.0.0.1:8080** (or vice versa) without deleting it first. +{{< /note >}} + +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** (required) | Destination to which the listener passes incoming requests. Possible alternatives:

- [Application](< relref "/unit/configuration.md#configuration-applications">): **applications/qwk2mart**

- [PHP target]({{< relref "/unit/configuration.md#configuration-php-targets" >}}) or [Python target]({{< relref "/unit/configuration.md#configuration-python-targets" >}}): **applications/myapp/section**

- [Route]({{< relref "/unit/configuration.md#configuration-routes" >}}): **routes/route66**, **routes**

- [Upstream]({{< relref "/unit/configuration.md#configuration-upstreams" >}}): **upstreams/rr-lb**

The value is [variable]({{< relref "/unit/configuration.md#configuration-variables-native" >}})-interpolated; if it matches no configuration entities after interpolation, a 404 "Not Found" response is returned. | +| **forwarded** | Object; configures client IP address and protocol [replacement]({{< relref "/unit/configuration.md#configuration-listeners-forwarded" >}}). | +| **tls** | Object; defines SSL/TLS [settings]({{< relref "/unit/configuration.md#configuration-listeners-ssl" >}}). | +| **backlog** | Integer; controls the 'backlog' parameter to the *listen(2)* system-call. This essentially limits the number of pending connections waiting to be accepted. The default varies by system.

On Linux, FreeBSD, OpenBSD, and macOS, the default is **-1** which means use the OS default. For example, on Linux since 5.4, this is **4096** (previously **128**) and on FreeBSD it's **128**. On other systems, the default is **511**.

NOTE: Whatever limit you set here will be limited by the OS system-wide sysctl. For example, on Linux that is `net.core.somaxconn` and on BSD it's `kern.ipc.somaxconn`. *(since 1.33.0)* | +{{
}} + +Here, a local listener accepts requests at port 8300 and passes them to the **blogs** app +[target]({{< relref "/unit/configuration.md#configuration-php-targets" >}}) +identified by the **uri** [variable]({{< relref "/unit/configuration.md#configuration-variables-native" >}}). +The wildcard listener on port 8400 relays requests at any host IPs to the **main** +[route]({{< relref "/unit/configuration.md#configuration-routes" >}}): + +```json +{ + "127.0.0.1:8300": { + "pass": "applications/blogs$uri" + }, + + "*:8400": { + "pass": "routes/main" + } +} +``` + +Also, **pass** values can be +[percent encoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1). +For example, you can escape slashes in entity names: + +```json +{ + "listeners": { + "*:80": { + "pass": "routes/slashes%2Fin%2Froute%2Fname" + } + }, + + "routes": { + "slashes/in/route/name": [] + } +} +``` + +### SSL/TLS configuration {#configuration-listeners-ssl} + +The **tls** object provides the following options: + +{{}} +| Option | Description | +|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **certificate** (required) | String or an array of strings; refers to one or more [certificate bundles]({{< relref "/unit/certificates.md">}}) uploaded earlier, enabling secure communication via the listener. | +| **conf_commands** | Object; defines the OpenSSL [configuration commands](https://www.openssl.org/docs/manmaster/man3/SSL_CONF_cmd.html) to be set for the listener.

To have this option, Unit must be built and run with OpenSSL 1.0.2 or later.

Also, make sure your OpenSSL version supports the commands set by this option. | +| **session** | Object; configures the TLS session cache and tickets for the listener. | +{{
}} + +To use a certificate bundle you +[uploaded]({{< relref "/unit/certificates.md#configuration-ssl" >}}) +earlier, +name it in the **certificate** option of the **tls** object: + +```json +{ + "listeners": { + "127.0.0.1:443": { + "pass": "applications/wsgi-app", + "tls": { + "certificate": "bundle", + "comment_certificate": "Certificate bundle name" + } + } + } +} +``` + +
+Configuring multiple bundles + +Since version 1.23.0, Unit supports configuring +[Server Name Indication (SNI)](https://datatracker.ietf.org/doc/html/rfc6066#section-3) +on a listener by supplying an array of certificate bundle names for the +**certificate** option value: + + ```json + { + "*: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 have priority over wildcards; + if this doesn't help, 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](https://www.openssl.org/docs/manmaster/man3/SSL_CONF_cmd.html) +for a listener, +use the **conf_commands** object in **tls**: + +```json +{ + "tls": { + "certificate": "bundle", + "comment_certificate": "Certificate bundle name", + "conf_commands": { + "ciphersuites": "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256", + "comment_ciphersuites": "Mandatory cipher suites as per RFC8446, section 9.1", + "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 **0** (caching is disabled). | +| **tickets** | Boolean, string, or an array of strings; configures TLS session tickets.

The default is **false** (tickets are disabled). | +| **timeout** | Integer; sets the session timeout for the TLS session cache.

When a new session is created, its lifetime derives from current time and **timeout**. If a cached session is requested past its lifetime, it is not reused.

The default is **300** (5 minutes). | +{{
}} + +#### Example: + +```json +{ + "tls": { + "certificate": "bundle", + "comment_certificate": "Certificate bundle name", + "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; + with **true**, a random session ticket key is used: + + ```json + { + "session": { + "tickets": true, + "comment_tickets": "Enables session tickets" + } + } + ``` + +- A string enables tickets and explicitly sets the session ticket key: + + ```json + { + "session": { + "tickets": "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP", + "comment_tickets": "Enables session tickets, sets a single session ticket key" + } + } + ``` + + This enables ticket reuse in scenarios + where the key is shared between individual servers. + +
+ Shared key rotation + If multiple Unit instances need to recognize tickets + issued by each other + (for example, when running behind a load balancer), + they should share session ticket keys. + + For example, + consider three SSH-enabled servers named `unit*.example.com`, + with Unit installed and identical `:443` listeners configured. + To configure a single set of three initial keys on each server: + + ```console + SERVERS="unit1.example.com + unit2.example.com + unit3.example.com" + + KEY1=$(openssl rand -base64 48) + KEY2=$(openssl rand -base64 48) + KEY3=$(openssl rand -base64 48) + + for SRV in $SERVERS; do + ssh root@$SRV \ # Assuming Unit runs as root on each server + curl -X PUT -d '["$KEY1", "$KEY2", "$KEY3"]' --unix-socket /path/to/control.unit.sock \ # Path to the remote control socket + 'http://localhost/config/listeners/*:443/tls/session/tickets/' + done + ``` + + To add a new key on each server: + + ```shell + NEWKEY=$(openssl rand -base64 48) + + for SRV in $SERVERS; do + ssh root@$SRV \ # Assuming Unit runs as root on each server + curl -X POST -d "\"$NEWKEY\"" --unix-socket /path/to/control.unit.sock \ # Path to the remote control socket + 'http://localhost/config/listeners/*:443/tls/session/tickets/' + done + ``` + + To delete the oldest key after adding the new one: + + ```shell + for SRV in $SERVERS; do + ssh root@$SRV \ # Assuming Unit runs as root on each server + curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to the remote control socket + 'http://localhost/config/listeners/*:443/tls/session/tickets/0' + done + ``` + + This scheme enables safely sharing session ticket keys + between individual Unit instances. + --- + +
+ + Unit supports AES256 (80-byte keys) or AES128 (48-byte keys); + the bytes should be encoded in Base64: + + ```console + $ openssl rand -base64 48 + + LoYjFVxpUFFOj4TzGkr5MsSIRMjhuh8RCsVvtIJiQ12FGhn0nhvvQsEND1+OugQ7 + ``` + + ```console + $ openssl rand -base64 80 + + GQczhdXawyhTrWrtOXI7l3YYUY98PrFYzjGhBbiQsAWgaxm+mbkm4MmZZpDw0tkK + YTqYWxofDtDC4VBznbBwTJTCgYkJXknJc4Gk2zqD1YA= + ``` + +- An array of strings just like the one above: + + ```json + { + "session": { + "tickets": [ + "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP", + "Ax4bv/JvMWoQG+BfH0feeM9Qb32wSaVVKOj1+1hmyU8ORMPHnf3Tio8gLkqm2ifC" + ], + "comment_tickets": "Enables session tickets, sets two session ticket keys" + } + } + ``` + + 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** to **false**. + {{< /note >}} + +### IP, protocol forwarding {#configuration-listeners-forwarded} + +Unit enables the **X-Forwarded-\*** header fields +with the **forwarded** object and its options: + +{{}} +| Option | Description | +|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **source** (required) | String or an array of strings; defines [address-based patterns]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}) for trusted addresses. Replacement occurs only if the source IP of the request is a [match](configuration-routes-matching-resolution).

A special case here is the **"unix"** string; it matches *any* UNIX domain sockets. | +| **client_ip** | String; names the HTTP header fields to expect in the request. They should use the [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For>) format where the value is a comma- or space-separated list of IPv4s or IPv6s. | +| **protocol** | String; defines the relevant HTTP header field to look for in the request. Unit expects it to follow the [X-Forwarded-Proto](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto>) notation, with the field value itself being **http**, **https**, or **on**. | +| **recursive** | Boolean; controls how the **client_ip** fields are traversed.

The default is **false** (no recursion). | +{{
}} + +{{< note >}} +Besides **source**, the **forwarded** object must specify +**client_ip**, **protocol**, or both. +{{< /note >}} + +{{< warning >}} +Before version 1.28.0, Unit provided the **client_ip** object +that evolved into **forwarded**: + +{{}} +| **client_ip** (pre-1.28.0) | **forwarded** (post-1.28.0) | +|----------------------------|-----------------------------| +| **header** | **client_ip** | +| **recursive** | **recursive** | +| **source** | **source** | +| N/A | **protocol** | +{{}} + +This old syntax still works but will be eventually deprecated, +though not earlier than version 1.30.0. +{{< /warning >}} + +When **forwarded** is set, Unit respects the appropriate header fields +only if the immediate source IP of the request +[matches]({{< relref "/unit/configuration.md#configuration-routes-matching-resolution" >}}) +the **source** option. Mind that it can use not only subnets but any +[address-based patterns]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}): + +```json +{ + "forwarded": { + "client_ip": "X-Forwarded-For", + "source": [ + "198.51.100.1-198.51.100.254", + "comment_source_1": "Ranges can be specified explicitly", + "!198.51.100.128/26", + "comment_source_2": "Negation rejects any addresses originating here", + "203.0.113.195", + "comment_source_3": "Individual addresses are supported as well" + ] + } +} +``` + +#### Overwriting protocol scheme {#configuration-listeners-xfp} + +The **protocol** option enables overwriting the incoming request's protocol scheme +based on the header field it specifies. Consider the following **forwarded** configuration: + +```json +{ + "forwarded": { + "protocol": "X-Forwarded-Proto", + "source": [ + "192.0.2.0/24", + "198.51.100.0/24" + ] + } +} +``` + +Suppose a request arrives with the following header field: + +```none +X-Forwarded-Proto: https +``` + +If the source IP of the request matches **source**, +Unit handles this request as an **https** one. + +#### Originating IP identification {#configuration-listeners-xff} + +Unit also supports identifying the clients' originating IPs +with the **client_ip** option: + +```json +{ + "forwarded": { + "client_ip": "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: + +```none +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* field named in **client_ip** as the originating IP of the +request. +In the example, it's 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 **client_ip** 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 previous example 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. + +## Routes {#configuration-routes} + +The **config/routes** configuration entity defines internal request routing. +It receives requests +from [listeners]({{< relref "/unit/configuration.md#configuration-listeners" >}}) +and filters them through +[sets of conditions]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}) +to be processed by +[apps]({{< relref "/unit/configuration.md#configuration-applications" >}}), +[proxied]({{< relref "/unit/configuration.md#configuration-proxy" >}}) +to external servers or +[load-balanced]({{< relref "/unit/configuration.md#configuration-upstreams" >}}) +between them, +served with +[static content]({{< relref "/unit/configuration.md#configuration-static" >}}), +[answered]({{< relref "/unit/configuration.md#configuration-return" >}}) +with arbitrary status codes, or +[redirected]({{< relref "/unit/configuration.md#configuration-return" >}}). + +In its simplest form, **routes** is an array that defines a single route: + +{ + "listeners": { + "*:8300": { + "pass": "routes" + } + }, + "routes": [], + "comment_routes": { + "hint": "Array-mode routes, simply referred to as 'routes'", + "placeholder": "Any acceptable route array may go here; see the 'Route Steps' section for details" + } +} +``` + +Another form is an object with one or more named route arrays as members: + +```json +{ + "listeners": { + "*:8300": { + "pass": "routes/main" + } + }, + "routes": { + "main": { + "value": [ + "..." + ], + "comment": "Any acceptable route array may go here; see the 'Route Steps' section for details" + }, + "comment_main": "Named route, referred to as 'routes/main'", + "route66": { + "value": [ + "..." + ], + "comment": "Any acceptable route array may go here; see the 'Route Steps' section for details" + }, + "comment_route66": "Named route, referred to as 'routes/route66'" + } +} +``` + +### Route steps {#configuration-routes-step} + +A +[route]({{< relref "/unit/configuration.md#configuration-routes" >}}) +array contains step objects as elements; they accept the following options: + +{{}} + +| Option | Description | +|---------------|--------------------------------------------------------------------------------------------------------------------| +| **action** (required) | Object; defines how matching requests are [handled]({{< relref "/unit/configuration.md#configuration-routes-action" >}}). | +| **match** | Object; defines the step's [conditions]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}). | +{{}} + +A request passed to a route traverses its steps sequentially: + +- If all **match** conditions in a step are met, the traversal ends + and the step's **action** 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** occurs automatically. +Thus, use no more than one such step per route, always placing it last to +avoid potential routing issues. +{{< /warning >}} + +
+Ad-Hoc examples +A basic one: + +```json +{ + "routes": [ + { + "match": { + "host": "example.com", + "scheme": "https", + "uri": "/php/*" + }, + + "action": { + "pass": "applications/php_version" + } + }, + { + "action": { + "share": "/www/static_version$uri" + } + } + ] +} +``` + +This route passes all HTTPS requests to the **/php/** subsection of the +**example.com** website 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: + +```json +{ + "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$uri" + } + } + ], + + "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 HTTP requests +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 {#configuration-routes-matching} + +Conditions in a +[route step]({{< relref "/unit/configuration.md#configuration-routes-step" >}})'s +**match** object +define patterns to be compared to the request's properties: + +{{}} +| Property | Patterns Are Matched Against | Case | +|------------|------------------------------|------| +| **arguments** | Arguments supplied with the request's [query string](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4); these names and value pairs are [percent decoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1), with plus signs (**+**) replaced by spaces. | Yes | +| **cookies** | Cookies supplied with the request. | Yes | +| **destination** | Target IP address and optional port of the request. | No | +| **headers** | [Header fields](https://datatracker.ietf.org/doc/html/rfc9110#section-6.3) supplied with the request. | No | +| **host** | **Host** [header field](https://datatracker.ietf.org/doc/html/rfc9110#section-7.2), converted to lower case and normalized by removing the port number and the trailing period (if any). | No | +| **method** | [Method](https://datatracker.ietf.org/doc/html/rfc7231#section-4) from the request line, uppercased. | No | +| **query** | [Query string](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4), [percent decoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1), with plus signs (**+**) replaced by spaces. | Yes | +| **scheme** | URI [scheme](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). Accepts only two patterns, either **http** or **https**. | No | +| **source** | Source IP address and optional port of the request. | No | +| **uri** | [Request target](https://datatracker.ietf.org/doc/html/rfc9110#target.resource), [percent decoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) and normalized by removing the [query string](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4) and resolving [relative references](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2) ("." and "..", "//"). | Yes | +{{}} + +
+Arguments vs. query + +Both **arguments** and **query** operate on the query string, +but **query** is matched against the entire string whereas **arguments** considers +only the key-value pairs such as **key1=4861&key2=a4f3**. + +Use **arguments** to define conditions based on key-value pairs in the query string: + +```json +"arguments": { + "key1": "4861", + "key2": "a4f3" +} +``` + +Argument order is irrelevant: **key1=4861&key2=a4f3** and **key2=a4f3&key1=4861** +are considered the same. Also, multiple occurrences of an argument must all match, +so **key=4861&key=a4f3** matches this: + +```json +"arguments":{ + "key": "*" +} +``` + +But not this: + +```json +"arguments":{ + "key": "a*" +} +``` + +To the contrary, use **query** if your conditions concern query strings +but don't rely on key-value pairs: + +```json +"query": [ + "utf8", + "utf16" +] +``` + +This only matches query strings of the form +**https://example.com?utf8** or **https://example.com?utf16**. + +--- +
+ +#### Match resolution {#configuration-routes-matching-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. + +
+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 is: + +*U* ∩ *P* \\ *N* if *P* ≠ ∅ + +*U* \\ *N* if *P* = ∅ + +--- +
+ +Here, the URI of the request must fit **pattern3**, +but must not match **pattern1** or **pattern2**: + +```json +{ + "match": { + "uri": [ + "!pattern1", + "!pattern2", + "pattern3" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Additionally, special matching logic applies to **arguments**, **cookies**, +and **headers**. Each of these can be either 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 matches only if the request arguments include **arg1** and **arg2**, +and both match their patterns: + +```json +{ + "match": { + "arguments": { + "arg1": "pattern", + "arg2": "pattern" + } + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +With an object array, the condition matches if the request's arguments include +**arg1** or **arg2** (or both) that matches the respective pattern: + +```json +{ + "match": { + "arguments": [ + { + "arg1": "pattern" + }, + { + "arg2": "pattern" + } + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +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))**): + +```json +{ + "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": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +
+Object pattern examples + +This requires **mode=strict** and any **access** argument other than **access=full** +in the URI query: + +```json +{ + "match": { + "arguments": { + "mode": "strict", + "access": "!full" + } + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +This matches requests that either use **gzip** and identify as **Mozilla/5.0** +or list **curl** as the user agent: + +```json +{ + "match": { + "headers": [ + { + "Accept-Encoding": "*gzip*", + "User-Agent": "Mozilla/5.0*" + }, + { + "User-Agent": "curl*" + } + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +--- +
+ +#### Pattern syntax {#configuration-routes-matching-patterns} + +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 (Available only if Unit was built with PCRE support enabled, +which is the default for the official packages) 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](https://www.json.org/json-en.html)). + The regexes are + [PCRE](https://www.pcre.org/current/doc/html/pcre2syntax.html)-flavored. + +
+Percent encoding in arguments, query, and URI patterns + + +Argument names, non-regex string patterns in **arguments**, +**query**, and **uri** can be `percent encoded +(https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) +to mask special characters (**!** is **%21**, **~** is **%7E**, +***** is **%2A**, **%** is **%25**) or even target single bytes. +For example, you can select diacritics such as Ö or Å by their starting byte +**0xC3** in UTF-8: + +```json +{ + "match": { + "arguments": { + "word": "*%C3*" + } + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Unit decodes such strings and matches them against respective request entities, +decoding these as well: + +```json +{ + "routes": [ + { + "match": { + "query": "`%7Efuzzy word search" + }, + + "action": { + "return": 200 + } + } + ] +} +``` + +This condition matches the following percent-encoded request: + +```console +$ curl http://127.0.0.1/?~fuzzy%20word%20search -v + + > GET /?~fuzzy%20word%20search HTTP/1.1 + ... + < HTTP/1.1 200 OK + ... +``` + +Note that the encoded spaces (**%20**) in the request match their unencoded +counterparts in the pattern; vice versa, the encoded tilde (**%7E**) +in the condition matches **~** in the request. + +--- +
+ +
+String pattern examples + + + +A regular expression that matches any **.php** files in the **/data/www/** +directory and its subdirectories. Note the backslashes; escaping is a +JSON-specific requirement: + +```json +{ + "match": { + "uri": "~^/data/www/.*\\.php(/.*)?$" + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Only subdomains of **example.com** match: + +```json +{ + "match": { + "host": "*.example.com" + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Only requests for **.php** files +located in **/admin/**'s subdirectories +match: + +```json +{ + "match": { + "uri": "/admin/*/*.php" + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Here, any **eu-** subdomains of **example.com** match except **eu-5.example.com**: + +```json +{ + "match": { + "host": [ + "eu-*.example.com", + "!eu-5.example.com" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Any methods match except **HEAD** and **GET**: + +```json +{ + "match": { + "method": [ + "!HEAD", + "!GET" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +You can also combine certain special characters in a pattern. Here, any URIs match +except the ones containing **/api/**: + +```json +{ + "match": { + "uri": "!*/api/*" + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Here, URIs of any articles that don't look like **YYYY-MM-DD** dates match. +Again, note the backslashes; they are a JSON requirement: + +```json +{ + "match": { + "uri": [ + "/articles/*", + "!~/articles/\\d{4}-\\d{2}-\\d{2}" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` +--- + +
+ +Address-based patterns define individual IPv4 +(dot-decimal or +[CIDR](https://datatracker.ietf.org/doc/html/rfc4632)), +IPv6 (hexadecimal or +[CIDR](https://datatracker.ietf.org/doc/html/rfc4291#section-2.3)), +or any +[UNIX domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) +addresses +that must exactly match the property; +wildcards and ranges modify this behavior: + +- Wildcards + (**\***) + can only match arbitrary IPs + (**\*:\**). +- Ranges + (**-**) + work with both IPs + (in respective notation) + and ports + (**\-\**). + +
+Address-based allow-deny lists + + + +Addresses come in handy when implementing an allow-deny mechanism +with routes, for instance: + +```json +"routes": [ + { + "match": { + "source": [ + "!192.168.1.1", + "!10.1.1.0/16", + "192.168.1.0/24", + "2001:0db8::/32" + ] + }, + + "action": { + "share": "/www/data$uri" + } + } +] +``` + +See +[here]({{< relref "/unit/configuration.md#configuration-routes-matching-resolution" >}}) +for details of pattern resolution order; this corresponds to the following +`nginx` directive: + +```nginx +location / { + deny 10.1.1.0/16; + deny 192.168.1.1; + allow 192.168.1.0/24; + allow 2001:0db8::/32; + deny all; + + root /www/data; +} +``` +--- +
+ +
+ Address pattern examples + + +This uses IPv4-based matching with wildcards and ranges: + +```json +{ + "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": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +This uses IPv6-based matching with wildcards and ranges: + +```json +{ + "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": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +This matches any of the listed IPv4 or IPv6 addresses: + +```json +{ + "match": { + "destination": [ + "127.0.0.1", + "192.168.0.1", + "::1", + "2001:0db8:1::c0a8:1" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +Here, any IPs from the range match except **192.0.2.9**: + +```json +{ + "match": { + "source": [ + "192.0.2.1-192.0.2.10", + "!192.0.2.9" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +This matches any IPs but limits the acceptable ports: + +```json +{ + "match": { + "source": [ + "*:80", + "*:443", + "*:8000-8080" + ] + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` + +This matches any UNIX domain sockets: + +```json +{ + "match": { + "source": "unix" + }, + + "action": { + "pass": "...", + "_comment_pass": "Any acceptable 'pass' value may go here; see the 'Listeners' section for details" + } +} +``` +--- +
+ +### Handling actions {#configuration-routes-action} + +If a request matches all +[conditions]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}) +of a route step +or the step itself omits the **match** object, +Unit handles the request with 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]({{< relref "/unit/configuration.md#configuration-listeners" >}}) | +| **proxy** | Socket address of an HTTP server to where the request is proxied. | [Proxying]({{< relref "/unit/configuration.md#configuration-proxy" >}}) | +| **return** | HTTP status code with a context-dependent redirect location. | [Instant responses, redirects]({{< relref "/unit/configuration.md#configuration-return" >}}) | +| **share** | File paths that serve the request with static content. | [Static files]({{< relref "/unit/configuration.md#configuration-static" >}}) | + +{{}} + + +An additional option is applicable to any of these actions: +{{}} +| Option | Description | Details | +|-------------------|---------------------------------------------------|--------------------------------------------| +| **response_headers** | Updates the header fields of the upcoming response. | [Response headers]({{< relref "/unit/configuration.md#configuration-response-headers" >}}) | +| **rewrite** | Updated the request URI, preserving the query string. | [URI rewrite]({{< relref "/unit/configuration.md#configuration-rewrite" >}}) | +{{}} + +An example: + +```json +{ + "routes": [ + { + "match": { + "uri": [ + "/v1/*", + "/v2/*" + ] + }, + + "action": { + "rewrite": "/app/$uri", + "pass": "applications/app" + } + }, + { + "match": { + "uri": "~\\.jpe?g$" + }, + + "action": { + "share": [ + "/var/www/static$uri", + "/var/www/static/assets$uri" + ], + + "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 {#configuration-variables-native} + +Some options in Unit configuration allow the use ofvariables whose values are +calculated at runtime. There's a number of built-in variables available: + +{{}} +| Variable | Description | +|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **arg_***, **cookie_***, **header_*** | Variables that store [request arguments, cookies, and header fields]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}), such as **arg_queryTimeout**, **cookie_sessionId**, or **header_Accept_Encoding**. The names of the **header_*** variables are case insensitive. | +| **body_bytes_sent** | Number of bytes sent in the response body. | +| **dollar** | Literal dollar sign (**$**), used for escaping. | +| **header_referer** | Contents of the **Referer** request [header field](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer). | +| **header_user_agent** | Contents of the **User-Agent** request [header field](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent). | +| **host** | **Host** [header field](https://datatracker.ietf.org/doc/html/rfc9110#section-7.2), converted to lower case and normalized by removing the port number and the trailing period (if any). | +| **method** | [Method](https://datatracker.ietf.org/doc/html/rfc7231#section-4) from the request line. | +| **remote_addr** | Remote IP address of the request. | +| **request_id** | Contains a string generated with random data. Can be used as a unique request identifier. | +| **request_line** | Entire [request line](https://datatracker.ietf.org/doc/html/rfc9112#section-3). | +| **request_time** | Request processing time in milliseconds, formatted as follows: **1.234**. | +| **request_uri** | Request target [path](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) *including* the [query](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4), normalized by resolving relative path references ("." and "..") and collapsing adjacent slashes. | +| **response_header_*** | Variables that store [response header fields]({{< relref "/unit/configuration.md#configuration-response-headers" >}}), such as **response_header_content_type**. The names of these variables are case insensitive. | +| **status** | HTTP [status code](https://datatracker.ietf.org/doc/html/rfc7231#section-6) of the response. | +| **time_local** | Local time, formatted as follows: **31/Dec/1986:19:40:00 +0300**. | +| **uri** | Request target [path](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) *without* the [query](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4) part, normalized by resolving relative path references ("." and "..") and collapsing adjacent slashes. The value is [percent decoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1): Unit interpolates all percent-encoded entities in the request target [path](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3). | +{{}} + +These variables can be used with: + +- **pass** in + [listeners]({{< relref "/unit/configuration.md#configuration-listeners" >}}) + and + [actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) + to choose between routes, applications, app targets, or upstreams. +- **rewrite** in + [actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) + to enable [URI rewriting]({{< relref "/unit/configuration.md#configuration-rewrite" >}}). +- **share** and **chroot** in + [actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) + to control + [static content serving]({{< relref "/unit/configuration.md#configuration-static" >}}). +- **location** in **return** + [actions]({{< relref "/unit/configuration.md#configuration-return" >}}) + to enable HTTP redirects. +- **format** in the + [access log]({{< relref "/unit/configuration.md#configuration-access-log" >}}) + to customize Unit's log output. + +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 such characters: + +```json +{ + "listeners": { + "*:80": { + "pass": "routes/${method}_route", + "comment_pass": "The method variable is thus separated from the '_route' postfix" + } + }, + + "routes": { + "GET_route": [ + { + "action": { + "return": 201 + } + } + ], + + "PUT_route": [ + { + "action": { + "return": 202 + } + } + ], + + "POST_route": [ + { + "action": { + "return": 203 + } + } + ] + } +} +``` + +To reference an **arg\_\***, **cookie\_\***, or **header\_\*** variable, add +the name you need to the prefix. A query string of **Type=car&Color=red** +yields two variables: **\$arg_Type** and **\$arg_Color**. Unit additionally +normalizes capitalization and hyphenation in header field names, so the +**Accept-Encoding** header field can also be referred to as **\$header_Accept_Encoding**, +**\$header_accept-encoding**, or **\$header_accept_encoding**. + +{{< note >}} +With multiple argument instances (think **Color=Red&Color=Blue**),the rightmost +one is used (**Blue**). +{{< /note >}} + +At runtime, variables expand into dynamically computed values (at your risk!). +The previous example targets an entire set of routes, picking individual +ones by HTTP verbs from the incoming requests: + +```console +curl -i -X GET http://localhost + + HTTP/1.1 201 Created +``` + +```console +curl -i -X PUT http://localhost + + HTTP/1.1 202 Accepted +``` + +```console +curl -i -X POST http://localhost + + HTTP/1.1 203 Non-Authoritative Information +``` + +```console +curl -i --head http://localhost # Bumpy ride ahead, no route defined + + HTTP/1.1 404 Not Found +``` + +If you reference a non-existing variable, it is considered empty. + +
+Examples + + +This configuration selects the static file location based on the requested +hostname; if nothing's found, it attempts to retrieve the requested file +from a common storage: + +```json +{ + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "action": { + "share": [ + "/www/$host$uri", + "comment_share_1": "Note that the $uri variable value always includes a starting slash", + "/www/storage$uri" + ] + } + } + ] +} +``` + +Another use case is employing the URI to choose between applications: + +```json +{ + "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, requests are routed between applications by their target URIs: + +```console +curl http://localhost/blog # Targets the 'blog' app +``` + +```console +curl http://localhost/sandbox # Targets the 'sandbox' app +``` + +A different approach puts the **Host** header field received from the client +to the same use: + +```json +{ + "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 use multiple variables in a string, repeating and placing them +arbitrarily. This configuration picks an app target (supported for +[PHP]({{< relref "/unit/configuration.md#configuration-php-targets" >}}) +and [Python]({{< relref "/unit/configuration.md#configuration-python-targets" >}})) +based on the requested hostname and URI: + + +```json +{ + "listeners": { + "*:80": { + "pass": "applications/app_$host$uri" + } + } +} +``` + +Note that the $uri value doesn't include the request's query part. + +At runtime,a request for **example.com/myapp** is passed to +**applications/app_example.com/myapp**. + +To select a share directory based on an **app_session** cookie: + +```json +{ + "action": { + "share": "/data/www/$cookie_app_session" + } +} +``` + +Here, if **$uri** in **share** resolves to a directory, the choice of an index +file to be served is dictated by **index**: + +```json +{ + "action": { + "share": "/www/data$uri", + "index": "index.htm" + } +} +``` + +Note that the $uri variable value always includes a starting slash. + +Here, a redirect uses the **$request_uri** variable value to relay the +request, *including* the query part, to the same website over HTTPS: + +```json +{ + "action": { + "return": 301, + "location": "https://$host$request_uri" + } +} +``` +--- +
+ + +## URI rewrite {#configuration-rewrite} + +All route step [actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) +support the **rewrite** option that updates the URI of the incoming request +before the action is applied. It does not affect the [query](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4) +but changes the **uri** and **\$request_uri** [variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}). + +This **match**-less action prefixes the request URI with **/v1** and returns +it to routing: + +```json +{ + "action": { + "rewrite": "/v1$uri", + "pass": "routes" + } +} +``` + +{{< warning >}} +Avoid infinite loops when you **pass** requests back to **routes**. +{{< /warning >}} + +This action normalizes the request URI and passes it to an application: + +```json +{ + "match": { + "uri": [ + "/fancyAppA", + "/fancyAppB" + ] + }, + + "action": { + "rewrite": "/commonBackend", + "pass": "applications/backend" + } +} +``` + +## Response headers {#configuration-response-headers} + +All route step +[actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) +support the **response_headers** option that updates the header fields of Unit's response +before the action is taken: + +```json +{ + "action": { + "share": "/www/static/$uri", + "response_headers": { + "Cache-Control": "max-age=60, s-maxage=120", + "CDN-Cache-Control": "max-age=600" + } + } +} +``` + +This works only for the **2XX** and **3XX** responses; +also, **Date**, **Server**, and **Content-Length** can't be set. + +The option sets given string values for the header fields of the response +that Unit will send for the specific request: + +- If there's no header field associated with the name (regardless of the case), + the value is set. +- If a header field with this name is already set, its value is updated. +- If **null** is supplied for the value, the header field is *deleted*. + +If the action is taken and Unit issues a response, it sends the header +fields *this specific* action specifies. Only the last action along the +entire routing path of a request affects the resulting response headers. + +The values support [variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}) +and [template literals]({{< relref "/unit/scripting.md" >}}), which enables +arbitrary runtime logic: + +```json +"response_headers": { + "Content-Language": "`${ uri.startsWith('/uk') ? 'en-GB' : 'en-US' }`" +} +``` + +Finally, there are the **response_header\_\*** variables that evaluate to the +header field values set with the response (by the app, upstream, or Unit +itself; the latter is the case with **\$response_header_connection**, +**\$response_header_content_length**, and **\$response_header_transfer_encoding**). + +One use is to update the headers in the final response; this extends the +**Content-Type** issued by the app: + + +```json +"action": { + "pass": "applications/converter", + "response_headers": { + "Content-Type": "${response_header_content_type};charset=iso-8859-1" + } + } +} +``` + +Alternatively, they will come in handy with +[custom log formatting]({{< relref "/unit/configuration.md#configuration-access-log" >}}). + + +## Instant responses, redirects {#configuration-return} + +You can use route step +[actions]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) +to instantly handle certain conditions with arbitrary +[HTTP status codes](https://datatracker.ietf.org/doc/html/rfc7231#section-6): + +```json +{ + "match": { + "uri": "/admin_console/*" + }, + + "action": { + "return": 403 + } +} +``` + +The **return** action provides the following options: + +{{}} + +| Option | Description | +|-------------|----------------------------------------------------------------------------------------------------------------------------| +| **return** | (required)
Integer (000–999); defines the HTTP response status code to be returned. | +| **location**| String URI; used if the **return** value implies redirection. | + +{{
}} + + +Use the codes according to their intended [semantics](https://datatracker.ietf.org/doc/html/rfc7231#section-6); +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**: + +```json +{ + "action": { + "return": 301, + "location": "https://www.example.com" + } +} +``` + +Besides enriching the response semantics, **return** simplifies +[allow-deny lists]({{< relref "/unit/configuration.md#allow-deny" >}}): +instead of guarding each action with a filter, add [conditions]({{< relref +"/unit/configuration.md#configuration-routes-matching" >}}) to deny unwanted +requests as early as possible, for example: + + +```json +"routes": [ + { + "match": { + "scheme": "http" + }, + + "action": { + "return": 403 + } + }, + { + "match": { + "source": [ + "!192.168.1.1", + "!10.1.1.0/16", + "192.168.1.0/24", + "2001:0db8::/32" + ] + }, + + "action": { + "return": 403 + } + } +] +``` + +## Static files {#configuration-static} + +Unit is capable of acting as a standalone web server, efficiently serving +static files from the local file system; to use the feature, list the file +paths in the **share** option of a route step [action]({{< relref +"/unit/configuration.md#configuration-routes-action" >}}). + + +A **share**-based action provides the following options: + +{{}} + +| Option | Description | +|---------------------|------------------------| +| **share** (required) | String or an array of strings; lists file paths that are tried until a file is found. When no file is found, **fallback** is used if set.

The value is [variable]({{< relref "/unit/configuration.md#configuration-variables" >}})-interpolated. | +| **index** | Filename; tried if **share** is a directory. When no file is found, **fallback** is used if set.

The default is **index.html**. | +| **fallback** | Action-like [object]({{< relref "/unit/configuration.md#configuration-fallback" >}}); used if the request can't be served by **share** or **index**. | +| **types** | [Array]({{< relref "/unit/configuration.md#configuration-share-mime" >}}) of [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml) patterns; used to filter the shared files. | +| **chroot** | Directory pathname that [restricts]({{< relref "/unit/configuration.md#configuration-share-path" >}}) the shareable paths.

The value is [variable]({{< relref "/unit/configuration.md#configuration-variables" >}})-interpolated. | +| **follow_symlinks**, **traverse_mounts** | Booleans; turn on and off symbolic link and mount point [resolution]({{< relref "/unit/configuration.md#configuration-share-resolution">}}) respectively; if **chroot** is set, they only [affect]({{< relref "/unit/configuration.md#configuration-share-path" >}}) the insides of **chroot**.

The default for both options is **true** (resolve links and mounts). | + +{{
}} + + +{{< note >}} +To serve the files, Unit's router process must be able to access them; +thus, the account this process runs as must have proper permissions +[assigned]({{< relref "/unit/configuration.md#security-apps" >}}). When +Unit is installed from the [official packages]({{< relref +"/unit/installation.md#installation-precomp-pkgs" >}}), the process runs +as **unit:unit**; for details of other installation methods, see +[installation]({{< relref "/unit/installation/">}}). +{{< /note >}} + +Consider the following configuration: + +```json +{ + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "action": { + "share": "/www/static/$uri" + } + } + ] +} +``` + +It uses +[variable interpolation]({{< relref "/unit/configuration.md#configuration-variables-native" >}}): +Unit replaces the **\$uri** reference with its current value and tries the +resulting path. If this doesn't yield a servable file, a 404 "Not Found" +response is returned. + + +{{< warning >}} +Before version 1.26.0, Unit used **share** as the document root. This was +changed for flexibility, so now **share** must resolve to specific files. +A common solution is to append **\$uri** to your document root. + +Pre-1.26, the snippet above would've looked like this: + +```json +"action": { + "share": "/www/static/" +} +``` + +Mind that URI paths always start with a slash, so there's no need to separate +the directory from **\$uri**; even if you do, Unit compacts adjacent slashes +during path resolution, so there won't be an issue. +{{< /warning >}} + +If **share** is an array, its items are searched in order of appearance +until a servable file is found: + +```json +"share": [ + "/www/$host$uri", + "/www/error_pages/not_found.html" +] +``` + +This snippet tries a **\$host**-based directory first; if a suitable file +isn't found there, the **not_found.html** file is tried. If neither is +accessible, a 404 "Not Found" response is returned. + +Finally, if a file path points to a directory, Unit attempts to serve an +**index**-indicated file from it. Suppose we have the following directory +structure and share configuration: + + +```none +/www/static/ +├── ... +└──default.html +``` + +```json +"action": { + "share": "/www/static$uri", + "index": "default.html" +} +``` + +The following request returns **default.html** even though the file isn't named +explicitly: + +```console +curl http://localhost/ -v + + ... + < HTTP/1.1 200 OK + < Last-Modified: Fri, 20 Sep 2021 04:14:43 GMT + < ETag: "5d66459d-d" + < Content-Type: text/html + < Server: Unit/{{< param "unitversion" >}} + ... +``` + +{{< 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. +{{< /note >}} + +### MIME filtering {#configuration-share-mime} + +To filter the files a **share** serves by their [MIME types](https://www.iana.org/assignments/media-types/media-types.xhtml), +define a **types** array of string patterns. They work like [route patterns]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}) +but are compared to the MIME type of each file; the request is served only if +it's a [match]({{< relref "/unit/configuration.md#configuration-routes-matching-resolution" >}}): + + +```json +{ + "share": "/www/data/static$uri", + "types": [ + "!text/javascript", + "!text/css", + "text/*", + "~video/3gpp2?" + ] +} +``` + +This sample configuration blocks JS and CSS files with +[negation]({{< relref "/unit/configuration.md#configuration-routes-matching-resolution" >}}) +but allows all other text-based MIME types with a +[wildcard pattern]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}). +Additionally, the **.3gpp** and **.3gpp2** file types are allowed by a +[regex pattern]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}). + +If no MIME types match the request, a 403 "Forbidden" response is returned. You +can pair that behavior with a +[fallback]({{< relref "/unit/configuration.md#configuration-fallback" >}}) option that will be called +when a 40x response would be returned. + +```json +{ + "share": "/www/data/static$uri", + "types": ["image/*", "font/*", "text/*"], + "response_headers": { + "Cache-Control": "max-age=1209600" + }, + "fallback": { + "share": "/www/data/static$uri", + } +} +``` + +Here, all requests to images, fonts, and any text-based files will have a +cache control header added to the response. Any other requests will still +serve the files, but this time without the header. This is useful for +serving common web page resources that do not change; web browsers and +proxies are informed that this content should be cached. + +If the MIME type of a requested file isn't recognized, it's considered +empty (**""**). Thus, the **"!"** pattern ("deny empty strings") can be +used to restrict all file types [unknown]({{< relref "/unit/configuration.md#configuration-mime" >}}) +to Unit: + + +```json +{ + "share": "/www/data/known-types-only$uri", + "types": [ + "!" + ] +} +``` + +If a share path specifies only the directory name, Unit *doesn't* apply MIME filtering. + +### Path restrictions {#configuration-share-path} + +{{< note >}} +To have these options, Unit must be built and run on a system with Linux kernel +version 5.6+. +{{< /note >}} + +The **chroot** option confines the path resolution within a share to a +certain directory. First, it affects symbolic links: any attempts to go up +the directory tree with relative symlinks like **../../var/log** stop at +the **chroot** directory, and absolute symlinks are treated as relative +to this directory to avoid breaking out: + + +```json +{ + "action": { + "share": "/www/data$uri", + "chroot": "/www/data/", + "comment_chroot": "Now, any paths accessible via the share are confined to this directory" + } +} +``` + +Here, a request for **/log** initially resolves to **/www/data/log**; however, +if that's an absolute symlink to **/var/log/app.log**, the resulting path +is **/www/data/var/log/app.log**. + +Another effect is that any requests for paths that resolve outside the +**chroot** directory are forbidden: + + +```json +{ + "action": { + "share": "/www$uri", + "chroot": "/www/data/" + } +} +``` + +Now, any paths accessible via the share are confined to the `/www/data/` directory + +Here, a request for **/index.xml** elicits a 403 "Forbidden" response +because it resolves to **/www/index.xml**, which is outside **chroot**. + + + +The **follow_symlinks** and **traverse_mounts** options disable resolution +of symlinks and traversal of mount points when set to **false** (both default +to **true**): + + +```json +{ + "action": { + "share": "/www/$host/static$uri", + "follow_symlinks": false, + "comment_follow_symlinks": "Disables symlink traversal", + "traverse_mounts": false, + "comment_traverse_mounts": "Disables mount point traversal" + } +} +``` + +Here, any symlink or mount point in the entire **share** path results in a +403 "Forbidden" response. + +With **chroot** set, **follow_symlinks** and **traverse_mounts** only affect +portions of the path *after* **chroot**: + + +```json +{ + "action": { + "share": "/www/$host/static$uri", + "chroot": "/www/$host/", + "follow_symlinks": false, + "traverse_mounts": false + } +} +``` + +Here, **www/** and interpolated **\$host** can be symlinks or mount points, +but any symlinks and mount points beyond them, including the **static/** +portion, won't be resolved. + +
+Details + + + +Suppose you want to serve files from a share that itself includes a symlink +(let's assume **$host** always resolves to **localhost** and make it a symlink +in our example) but disable any symlinks inside the share. + +Initial configuration: + +```json +{ + "action": { + "share": "/www/$host/static$uri", + "chroot": "/www/$host/", + "comment_chroot": "Now, any paths accessible via the share are confined to this directory" + } +} +``` + +Create a symlink to **/www/localhost/static/index.html**: + +```console +mkdir -p /www/localhost/static/ && cd /www/localhost/static/ +``` + +```console +cat > index.html << EOF + > index.html + > EOF +``` + +```console +ln -s index.html /www/localhost/static/symlink +``` + +If symlink resolution is enabled (with or without **chroot**), a request that +targets the symlink works: + +```console +curl http://localhost/index.html + + index.html +``` + +```console +curl http://localhost/symlink + + index.html +``` + +Now set **follow_symlinks** to **false**: + +```json +{ +"action": { + "share": "/www/$host/static$uri", + "chroot": "/www/$host/", + "comment_chroot": "Now, any paths accessible via the share are confined to this directory", + "follow_symlinks": false +} +} +``` + +The symlink request is forbidden, which is presumably the desired effect: + +```console +curl http://localhost/index.html + + index.html +``` + +```console +curl http://localhost/symlink + + Error 403

Error 403. +``` + +Finally, what difference does **chroot** make? To see, remove it: + +```json +{ + "action": { + "share": "/www/$host/static$uri", + "follow_symlinks": false + } +} +``` + +Now, **"follow_symlinks": false** affects the entire share, and **localhost** +is a symlink, so it's forbidden: + +```console +curl http://localhost/index.html + + Error 403

Error 403. +``` + +--- +

+ +### Fallback action {#configuration-fallback} + +Finally, within an **action**, you can supply a **fallback** option beside +a **share**. It specifies the [action]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) +to be taken if the requested file can't be served from the **share** path: + + +```json +{ + "share": "/www/data/static$uri", + "fallback": { + "pass": "applications/php" + } +} +``` + +Serving a file can be impossible for different reasons, such as: + +- The request's HTTP method isn't **GET** or **HEAD**. +- The file's MIME type doesn't match the **types** + [array]({{< relref "/unit/configuration.md#configuration-share-mime" >}}). +- The file isn't found at the **share** path. +- The router process has + [insufficient permissions]({{< relref "/unit/configuration.md#security-apps" >}}) + to access the file or an underlying directory. + +In the previous example, 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**: + + +```json +{ + "share": "/www/data/static$uri", + "fallback": { + "share": "/www/cache$uri", + "chroot": "/www/", + "fallback": { + "proxy": "http://127.0.0.1:9000" + } + } +} +``` + +The first **share** tries to serve the request from **/www/data/static/**; +on failure, the second **share** tries the **/www/cache/** path with +**chroot** enabled. If both attempts fail, the request is proxied elsewhere. + +
+Examples + + + +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 **.php** files to an application and uses a +catch-all static **share** with a **fallback**: + +```json +{ + "routes": [ + { + "match": { + "uri": "*.php" + }, + + "action": { + "pass": "applications/php-app" + } + }, + { + "action": { + "share": "/www/php-app/assets/files$uri", + "fallback": { + "proxy": "http://127.0.0.1:9000" + } + } + } + + ], + + "applications": { + "php-app": { + "type": "php", + "root": "/www/php-app/scripts/" + } + } +} +``` + +You can reverse this scheme for apps that avoid filenames in dynamic URIs, +listing all types of static content to be served from a **share** in a +**match** condition and adding an unconditional application path: + +```json +{ + "routes": [ + { + "match": { + "uri": [ + "*.css", + "*.ico", + "*.jpg", + "*.js", + "*.png", + "*.xml" + ] + }, + + "action": { + "share": "/www/php-app/assets/files$uri", + "fallback": { + "proxy": "http://127.0.0.1:9000" + } + } + }, + { + "action": { + "pass": "applications/php-app" + } + } + + ], + + "applications": { + "php-app": { + "type": "php", + "root": "/www/php-app/scripts/" + } + } +} +``` + +If image files should be served locally and other proxied, use the **types** +array in the first route step: + + +```json +{ + "match": { + "uri": [ + "*.css", + "*.ico", + "*.jpg", + "*.js", + "*.png", + "*.xml" + ] + }, + + "action": { + "share": "/www/php-app/assets/files$uri", + "types": [ + "image/*" + ], + + "fallback": { + "proxy": "http://127.0.0.1:9000" + } + } +} +``` + +Another way to combine **share**, **types**, and **fallback** +is exemplified by the following compact pattern: + +```json +{ + "share": "/www/php-app/assets/files$uri", + "types": [ + "!application/x-httpd-php" + ], + + "fallback": { + "pass": "applications/php-app" + } +} +``` + +It forwards explicit requests for PHP files to the app while serving all +other types of files from the share; note that a **match** object isn't +needed here to achieve this effect. + +--- +
+ + +## Proxying {#configuration-proxy} + +Unit's routes support HTTP proxying to socket addresses using the **proxy** +option of a route step [action]({{< relref "/unit/configuration.md#configuration-routes-action" >}}): + +```json +{ + "routes": [ + { + "match": { + "uri": "/ipv4/*" + }, + "action": { + "proxy": "http://127.0.0.1:8080", + "comment_proxy": "Note that the http:// scheme is required" + } + }, + { + "match": { + "uri": "/ipv6/*" + }, + "action": { + "proxy": "http://[::1]:8080", + "comment_proxy": "Note that the http:// scheme is required" + } + }, + { + "match": { + "uri": "/unix/*" + }, + "action": { + "proxy": "http://unix:/path/to/unix.sock", + "comment_proxy": "Note that the http:// scheme is required, followed by the unix: prefix" + } + } + ] +} +``` + +As the example suggests, you can use UNIX, IPv4, and IPv6 socket addresses +for proxy destinations. + +{{< note >}} +The HTTPS scheme is not supported yet. +{{< /note >}} + +### Load balancing {#configuration-upstreams} + +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 entityand may be used as a **pass** destination for incoming requests in a +[listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}) +or a [route]({{< relref "/unit/configuration.md#configuration-routes" >}}). + +Upstreams are defined in the eponymous **/config/upstreams** section of the API: + +```json +{ + "listeners": { + "*:80": { + "pass": "upstreams/rr-lb" + } + }, + "upstreams": { + "rr-lb": { + "comment_rr-lb": "Upstream object", + "servers": { + "comment_servers": "Lists individual servers as object-valued options", + "192.168.0.100:8080": { + "comment_192.168.0.100:8080": "Empty object needed due to JSON requirements" + }, + "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: + + +```json +{ + "servers": { + "192.168.0.100:8080": { + "weight": 1e1, + "comment_weight": "All three values are equal" + }, + "192.168.0.101:8080": { + "weight": 10.0, + "comment_weight": "All three values are equal" + }, + "192.168.0.102:8080": { + "weight": 10, + "comment_weight": "All three values are equal" + } + } +} +``` + +The maximum weight is **1000000**, the minimum is **0** (such servers receive +no requests); the default is **1**. + + +## Applications {#configuration-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-specific packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +include end-to-end examples of application configuration, available for your +reference at **/usr/share/doc/\/examples/** after package installation. +{{< /note >}} + +Here, Unit runs 20 processes of a PHP app called **blogs**, stored in the +**/www/blogs/scripts/** directory: + +```json +{ + "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: **external** (Go and Node.js), **java**, **perl**, **php**, **python**, **ruby**, or **wasm** (WebAssembly).

Except for **external** and **wasm**, you can detail the runtime version: **"type": "python 3"**, **"type": "python 3.4"**, or even **"type": "python 3.4.9rc1"**.

Unit searches its modules and uses the latest matching one, reporting an error if none match.

For example, if you have only one PHP module, 7.1.9, it matches **"php"**, **"php 7"**, **"php 7.1"**, and **"php 7.1.9"**.

If you have modules for versions 7.0.2 and 7.0.23, set **"type": "php 7.0.2"** to specify the former; otherwise, PHP 7.0.23 will be used. | +| **environment** | String-valued object; environment variables to be passed to the app.

Unit passes the environment variables to the app without modification, even if no environment configuration object is specified.

Any data specified in the **environment** object gets merged to the existing environment variables.

If an environment variable already exists in the system and gets declared in this object, the object's value takes precedence and gets passed to the application. | +| **group** | String; group name that runs the [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}).

The default is the **user**'s primary group. | +| **isolation** | Object; manages the isolation of an application process.

For details, see [here]({{< relref "/unit/configuration.md#configuration-proc-mgmt-isolation" >}}). | +| **limits** | Object; accepts two integer options, **timeout** and **requests**.

Their values govern the life cycle of an application process.

For details, see [here]({{< relref "/unit/configuration.md#configuration-proc-mgmt-lmts" >}}). | +| **processes** | Integer or object; integer sets a static number of app processes, and object options **max**, **spare**, and **idle_timeout** enable dynamic management.

For details, see [here]({{< relref "/unit/configuration.md#configuration-proc-mgmt-prcs" >}}).

The default is 1. | +| **stderr**, **stdout** | Strings; filenames where Unit redirects the application's output.

The default when running *with* **--no-daemon** is to send *stdout* to the *console* and *stderr* to Unit's *log*.

The default when running *without* **--no-daemon** is to send *stdout* to */dev/null* and *stderr* to Unit's *log*.

These options have *no* effect when running with **--no-daemon**. | +| **user** | String; username that runs the [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}).

The default is the username configured at [build time]({{< relref "/unit/howto/source.md#source-config-src" >}}) or at [startup]({{< relref "/unit/howto/source.md#source-startup" >}}). | +| **working_directory** | String; the app's working directory.

The default is the working directory of Unit's [main process]({{< relref "/unit/howto/security.md#sec-processes" >}}). | + +{{
}} + +Also, you need to set **type**-specific options +to run the app. This +[Python app]({{< relref "/unit/configuration.md#configuration-python" >}}) +sets **path** and **module**: + +```json +{ + "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 {#configuration-proc-mgmt} + +Unit has three per-app options that control how the app's processes behave: +**isolation**, **limits**, and **processes**. Also, you can **GET** the +**/control/applications/** section of the API to restart an app: + + +```console +# curl -X GET --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/control/applications/app_name/restart # Your application's name as defined in the /config/applications/ section +``` + +Unit handles the rollover gracefully, allowing the old processes to deal +with existing requests and starting a new set of processes (as defined by +the **processes** [option]({{< relref "/unit/configuration.md#configuration-proc-mgmt-prcs" >}})) +to accept new requests. + + + +#### Process isolation {#configuration-proc-mgmt-isolation} + +You can use +[namespace](https://man7.org/linux/man-pages/man7/namespaces.7.html) +and +[file system](https://man7.org/linux/man-pages/man2/chroot.2.html) +isolation for your apps if Unit's underlying OS supports them: + +```console +ls /proc/self/ns/ + + cgroup mnt \ # The mount namespace + net \ # The network namespace + pid ... \ + user \ # The credential namespace + uts \ # The uname namespace +``` + +The **isolation** application option has the following members: + +{{}} +| Option | Description | +|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **automount** | Object; controls mount behavior if **rootfs** is enabled. By default, Unit automatically mounts the [language runtime dependencies]({{< relref "/unit/configuration.md#conf-rootfs" >}}), a [procfs](https://man7.org/linux/man-pages/man5/procfs.5.html) at **/proc/**, and a [tmpfs](https://man7.org/linux/man-pages/man5/tmpfs.5.html) at **/tmp/**, but you can [disable]({{< relref "/unit/configuration.md#disable-automounts" >}}) any of these default mounts. | +| **cgroup** | Object; defines the app's [cgroup]({{< relref "/unit/configuration.md#conf-app-cgroup" >}}).

**Options:**

   - **path** (required): String; configures absolute or relative path of the app in the [cgroups v2 hierarchy](https://man7.org/linux/man-pages/man7/cgroups.7.html#CGROUPS_VERSION_2). The limits trickle down the hierarchy, so child cgroups can't exceed parental thresholds. | +| **gidmap** | Same as **uidmap**, but configures group IDs instead of user IDs. | +| **namespaces** | Object; configures [namespace](https://man7.org/linux/man-pages/man7/namespaces.7.html) isolation scheme for the application.

**Options (system-dependent; check your OS manual for guidance):**

   - **cgroup**: Creates a new cgroup namespace for the app.

   - **credential**: Creates a new user namespace for the app.

   - **mount**: Creates a new mount namespace for the app.

   - **network**: Creates a new network namespace for the app.

   - **pid**: Creates a new PID namespace for the app.

   - **uname**: Creates a new UTS namespace for the app. | +| | | | All options listed above are Boolean; to isolate the app, set the corresponding namespace option to **true**; to disable isolation, set the option to **false** (default). | +| **rootfs** | String; pathname of the directory to be used as the new [file system root]({{< relref "/unit/configuration.md#conf-rootfs">}}) for the app. | +| **uidmap** | Array of user ID [mapping objects]({{< relref "/unit/configuration.md#conf-uidgid-mapping" >}}); each array item must define the following:

- **container**: Integer; starts the user ID mapping range in the app's namespace.

- **host**: Integer; starts the user ID mapping range in the OS namespace.

- **size**: Integer; size of the ID range in both namespaces. | +{{
}} + +To disable automounts: + + + +```json +{ + "isolation": { + "automount": { + "procfs": false, + "tmpfs": false + } + } +} +``` + + + + +A sample **isolation** object that enables all namespaces and sets mappings for +user and group IDs: + +```json +{ + "namespaces": { + "cgroup": true, + "credential": true, + "mount": true, + "network": true, + "pid": true, + "uname": true + }, + + "cgroup": { + "path": "/unit/appcgroup" + }, + + "uidmap": [ + { + "host": 1000, + "container": 0, + "size": 1000 + } + ], + + "gidmap": [ + { + "host": 1000, + "container": 0, + "size": 1000 + } + ] +} +``` + +##### Using control groups {#conf-app-cgroup} + +A control group (cgroup) commands the use of computational resources by a +group of processes in a unified hierarchy. Cgroups are defined by their +*paths* in the cgroups file system. + +The **cgroup** object defines the cgroup for a Unit app; its **path** option +can set an absolute (starting with **/**) or a relative value. If the path +doesn't exist in the cgroups file system, Unit creates it. + +Relative paths are implicitly placed inside the cgroup of Unit's +[main process]({{< relref "/unit/howto/security.md#sec-processes" >}}); +this setting effectively puts the app to the **/\
/production/app** +cgroup: + +```json +{ + "isolation": { + "cgroup": { + "path": "production/app" + } + } +} +``` + +An absolute pathname places the application under a separate cgroup subtree; +this configuration puts the app under **/staging/app**: + +```json +{ + "isolation": { + "cgroup": { + "path": "/staging/app" + } + } +} +``` + +A basic use case would be to set a memory limit on a cgroup. +First, find the cgroup mount point: + +```console +mount -l | grep cgroup + + cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot) +``` + +Next, check the available controllers and set the **memory.high** limit: + +```console +cat /sys/fs/cgroup/staging/app/cgroup.controllers # cgroup's path set in Unit configuration + + cpuset cpu io memory pids +``` + +As root, run the following command: + +```console +echo 1G > /sys/fs/cgroup/staging/app/memory.high # cgroup's path set in Unit configuration +``` + +For more details and possible options,refer to the +[admin guide](https://docs.kernel.org/admin-guide/cgroup-v2.html). + +{{< note >}} +To avoid confusion, mind that the **namespaces/cgroups** option +controls the application's cgroup *namespace*; instead, the **cgroup/path** option +specifies the cgroup where Unit puts the application. +{{< /note >}} + +##### Changing root directory {#conf-rootfs} + +The **rootfs** option confines the app to the directory you provide, making +it the new [file system root](https://man7.org/linux/man-pages/man2/chroot.2.html). +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: + + +```json +{ + "type": "python 2.7", + "path": "/", + "comment_path": "Without rootfs, this would be /var/app/sandbox/", + "home": "/venv/", + "comment_home": "Without rootfs, this would be /var/app/sandbox/venv/", + "module": "wsgi", + "isolation": { + "rootfs": "/var/app/sandbox/" + } +} +``` + +{{< warning >}} +When using **rootfs** with **credential** set to **true**: + +```json +"isolation": { + "rootfs": "/var/app/sandbox/", + "namespaces": { + "credential": true + } +} +``` + +Ensure that the user the app *runs as* can access the **rootfs** directory. +{{< /warning >}} + +Unit mounts language-specific files and directories to the new root +so the app stays operational: + +{{}} +| Language | Language-Specific Mounts | +|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Java** | - JVM's **libc.so** directory

- Java module's [home]({{< relref "/unit/howto/source.md#modules-java" >}}) directory | +| **Python** | Python's **sys.path** [directories](https://docs.python.org/3/library/sys.html#sys.path) | +| **Ruby** | - Ruby's header, interpreter, and library [directories](https://idiosyncratic-ruby.com/42-ruby-config.html): **rubyarchhdrdir**, **rubyhdrdir**, **rubylibdir**, **rubylibprefix**, **sitedir**, and **topdir**

- Ruby's gem installation directory (**gem env gemdir**)

- Ruby's entire gem path list (**gem env gempath**) | +{{
}} + + +
+Using "uidmap", "gidmap" + + +The **uidmap** and **gidmap** options are available only if the underlying OS +supports [user namespaces](https://man7.org/linux/man-pages/man7/user_namespaces.7.html) + +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: + +```json +{ + "user": "some_user", + "isolation": { + "namespaces": { + "credential": true + } + } +} +``` + +Is equivalent to the following (assuming **some_user**'s EUID and EGID are +both equal to 1000): + +```json +{ + "user": "some_user", + "isolation": { + "namespaces": { + "credential": true + }, + + "uidmap": [ + { + "host": "1000", + "container": "1000", + "size": 1 + } + ], + + "gidmap": [ + { + "host": "1000", + "container": "1000", + "size": 1 + } + ] + } +} +``` +--- +
+ +#### Request limits {#configuration-proc-mgmt-lmts} + +The **limits** object controls request handling by the app process and has two +integer options: + +{{}} +| Option | Description | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **requests** | Integer; maximum number of requests an app process can serve. When the limit is reached, the process restarts; this mitigates possible memory leaks or other cumulative issues. | +| **timeout** | Integer; request timeout in seconds. If an app process exceeds it while handling a request, Unit cancels the request and returns a 503 "Service Unavailable" response to the client.

Unit doesn't detect freezes, so the hanging process stays on the app's process pool. | +{{
}} + + +#### Example: + +```json +{ + "type": "python", + "working_directory": "/www/python-apps", + "module": "blog.wsgi", + "limits": { + "timeout": 10, + "requests": 1000 + } +} +``` + +#### Application processes {#configuration-proc-mgmt-prcs} + +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 | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **idle_timeout** | Number of seconds Unit waits for before terminating an idle process that exceeds **spare**. | +| **max** | Maximum number of application processes that Unit maintains (busy and idle).

The default is 1. | +| **spare** | Minimum number of idle processes that Unit tries to maintain for an app. When the app is started, **spare** idles are launched; Unit passes new requests to existing idles, forking new idles to keep the **spare** level if **max** allows. When busy processes complete their work and turn idle again, Unit terminates extra idles after **idle_timeout**. | +{{
}} + +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: + + +```json +{ + "max": 10, + "spare": 5, + "idle_timeout": 20 +} +``` + +{{< note >}} +For details of manual application process restart, see the +[process management]({{< relref "/unit/configuration.md#configuration-proc-mgmt" >}}) +documentation. +{{< /note >}} + + + +### Go {#configuration-go} + +To run a Go app on Unit, modify its source to make it Unit-aware and rebuild +the app. + +
+Updating Go apps to run on Unit + + + +Unit uses [cgo](https://pkg.go.dev/cmd/cgo) to invoke C code from Go, +so check the following prerequisites: + +- The `CGO_ENABLED` variable is set to **1**: + + ```console + go env CGO_ENABLED + + 0 + ``` + + ```console + go env -w CGO_ENABLED=1 + ``` + +- If you installed Unit from the + [official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), + install the development package: + + - Debian, Ubuntu + + ```console + apt install unit-dev + ``` + + - Amazon, Fedora, RHEL + + ```console + yum install unit-devel + ``` + +- If you installed Unit from + [source]({{< relref "/unit/howto/source/" >}}), + install the include files and libraries: + + ```console + make libunit-install + ``` + +In the **import** section, list the **unit.nginx.org/go** package: + +```go +import ( + ... + "unit.nginx.org/go" + ... +) +``` + +Replace the **http.ListenAndServe** call with **unit.ListenAndServe**: + +```go +func main() { + ... + http.HandleFunc("/", handler) + ... + // http.ListenAndServe(":8080", nil) + unit.ListenAndServe(":8080", nil) + ... +} +``` + +If you haven't done so yet, initialize the Go module for your app: + +```console +go mod init example.com/app # Arbitrary module designation + + go: creating new go.mod: module example.com/app +``` + +Install the newly added dependency and build your application: + +```console +go get unit.nginx.org/go@{{< param "unitversion" >}} + + go: downloading unit.nginx.org + +go build -o app app.go # Executable name and Application source code +``` + +If you update Unit to a newer version, repeat the two commands above +to rebuild your app. + +The resulting executable works as follows: + +- When you run it standalone, the **unit.ListenAndServe** call + falls back to **http** functionality. + +- When Unit runs it, **unit.ListenAndServe** directly communicates + with Unit's router process, ignoring the address supplied as its first argument + and relying on the + [listener's settings]({{< relref "/unit/configuration.md#configuration-listeners" >}}) + instead. + +--- + +
+ +Next, configure the app on Unit; besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} +| Option | Description | +|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **executable** (required) | String; pathname of the application, absolute or relative to **working_directory**. | +| **arguments** | Array of strings; 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: + +```json +{ + "type": "external", + "working_directory": "/www/chat", + "executable": "bin/chat_app", + "user": "www-go", + "group": "www-go", + "arguments": [ + "--tmp-files", + "/tmp/go-cache" + ] +} +``` + +{{< note >}} +For Go-based examples, see our [grafana]({{< relref "/unit/howto/apps/grafana/">}}) +howto or a basic +[sample]({{< relref "/unit/howto/samples.md#sample-go" >}}). +{{< /note >}} + +### Java {#configuration-java} + +First, make sure to install Unit along with the +[Java language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} +| Option | Description | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **webapp** (required) | String; pathname of the application's **.war** file (packaged or unpackaged). | +| **classpath** | Array of strings; paths to your app's required libraries (may point to directories or individual **.jar** files). | +| **options** | Array of strings; defines JVM runtime options. Unit itself exposes the **-Dnginx.unit.context.path** option that defaults to **/**; use it to customize the [context path](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletContext.html#getContextPath--). | +| **thread_stack_size** | Integer; stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default is usually system dependent. | +| **threads** | Integer; number of worker threads per [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}). When started, each app process creates this number of threads to handle requests. The default is **1**. | +{{}} + + +#### Example: + +```json +{ + "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]({{< relref "/unit/howto/apps/jira/">}}), +[OpenGrok]({{< relref "/unit/howto/apps/opengrok/">}}), +and +[Springbook]({{< relref "/unit/howto/frameworks/springboot/">}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-java" >}}). +{{< /note >}} + +### Node.js {#configuration-nodejs} + +First, you need to have the `unit-http` module +[installed]({{< relref "/unit/installation.md#installation-nodejs-package" >}}). +If it's global, symlink it in your project directory: + +```console +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]({{< relref "/unit/installation.md#installation-nodejs-package" >}}). + +Next, to run your Node.js apps on Unit, you need to configure them. +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} + +| Option | Description | +|---------------|--------------------------------------------------------------------------------------------------------------------| +| **executable** (required) | String; pathname of the app, absolute or relative to **working_directory**.

Supply your **.js** pathname here and start the file itself with a proper shebang:

`#!/usr/bin/env node`

**Note:** Make sure to `chmod +x` the file you list here so Unit can start it. | +| **arguments** | Array of strings; command-line arguments to be passed to the app. The example below is equivalent to **/www/apps/node-app/app.js --tmp-files /tmp/node-cache**. | + +{{
}} + + +#### Example: + +```json +{ + "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: + +{{}} + +{{%tab name="14.16.x and later"%}} + +```json +{ + "type": "external", + "executable": "/usr/bin/env", + "comment_executable": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "app.js" + ], + "comment_arguments_last": "Application script name" +} +``` + +{{%/tab%}} + +{{%tab name="14.15.x and earlier"%}} + +```json +{ + "type": "external", + "executable": "/usr/bin/env", + "comment_executable": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--require", + "unit-http/loader", + "app.js" + ], + "comment_arguments_last": "Application script name" +} +``` + +{{%/tab%}} + +{{}} + +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: + +```javascript +var http = require('unit-http'); +``` + +To use the WebSocket protocol, your app only needs to replace the default **websocket**: + +```javascript +var webSocketServer = require('unit-http/websocket').server; +``` + +{{< note >}} +For Node.js-based examples, see our +[apollo]({{< relref "/unit/howto/apps/apollo/">}}), +[express]({{< relref "/unit/howto/frameworks/express/">}}), +[koa]({{< relref "/unit/howto/frameworks/koa/">}}), +and +[Docker]({{< relref "/unit/howto/docker" >}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-nodejs" >}}). +{{< /note >}} + +### Perl {#configuration-perl} + +First, make sure to install Unit along with the +[Perl language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} +| Option | Description | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **script** (required) | String; PSGI script path. | +| **thread_stack_size** | Integer; stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default is usually system dependent and can be set with `ulimit -s `. | +| **threads** | Integer; number of worker threads per [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}). When started, each app process creates this number of threads to handle requests. The default is **1**. | +{{}} + +#### Example: + +```json +{ + "type": "perl", + "script": "/www/bugtracker/app.psgi", + "working_directory": "/www/bugtracker", + "processes": 10, + "user": "www", + "group": "www" +} +``` + +{{< note >}} +For Perl-based examples of Perl, +see our +[Bugzilla]({{< relref "/unit/howto/apps/bugzilla/">}}) +and +[Catalyst]({{< relref "/unit/howto/frameworks/catalyst/">}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-perl" >}}). +{{< /note >}} + +### PHP {#configuration-php} + +First, make sure to install Unit along with the +[PHP language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), you have: + +{{}} + +| Option | Description | +|---------------|--------------------------------------------------------------------------------------------------------------------| +| **root** (required) | String; base directory of the app's file structure. All URI paths are relative to it. | +| **index** | String; filename added to URI paths that point to directories if no **script** is set.

The default is **index.php**. | +| **options** | Object; [defines]({{< relref "/unit/configuration.md#configuration-php-options" >}}) the **php.ini** location and options. | +| **script** | String; filename of a **root**-based PHP script that serves all requests to the app. | +| **targets** | Object; defines application sections with [custom]({{< relref "/unit/configuration.md#configuration-php-targets" >}}) **root**, **script**, and **index** values. | + +{{
}} + + +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 specify in this option. +- Otherwise, the requests are served according to their URI paths; + if they point to directories, **index** is used. + + + +You can customize **php.ini** via the **options** object: + +{{}} + +| Option | Description | +|-----------------------|------------------------------------------------------------------------------------------------------------------------| +| **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 can be [updated](https://www.php.net/manual/en/function.ini-set.php) at runtime.

- The objects override the settings from any ***.ini** files

- The **admin** object can only set what's [listed](https://www.php.net/manual/en/ini.list.php) as **PHP_INI_SYSTEM**; for other modes, set **user**

- Neither **admin** nor **user** can set directives listed as [php.ini only](https://www.php.net/manual/en/ini.list.php) except for **disable_classes** and **disable_functions** | +| **file** | String; pathname of the **php.ini** file with [PHP configuration directives](https://www.php.net/manual/en/ini.list.php). | + +{{
}} + + +To load multiple **.ini** files, use **environment** with `PHP_INI_SCAN_DIR` to +[scan a custom directory](https://www.php.net/manual/en/configuration.file.php): + +```json +{ + "applications": { + "hello-world": { + "type": "php", + "root": "/www/public/", + "script": "index.php", + "environment": { + "PHP_INI_SCAN_DIR": ":/tmp/php.inis/" + }, + "comment_PHP_INI_SCAN_DIR": "Path separator" + } + } +} +``` + +Mind that the colon that prefixes the value here is a path separator; +it causes PHP to scan the directory preconfigured with the +{option}`!--with-config-file-scan-dir` option, which is usually **/etc/php.d/**, +and then the directory you set here, which is **/tmp/php.inis/**. +To skip the preconfigured directory, drop the **:** prefix. + +{{< 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 aof **PHP_INI\_\*** modes, see the +[PHP docs](https://www.php.net/manual/en/configuration.changes.modes.php). +{{< /note >}} + +{{< note >}} +Unit implements the **fastcgi_finish_request()** [function](https://www.php.net/manual/en/function.fastcgi-finish-request.php) in a manner similar to PHP-FPM. +{{< /note >}} + +#### Example: + +```json +{ + "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" + }, + + "user": { + "display_errors": "0" + } + } +} +``` + +#### Targets {#configuration-php-targets} + +You can configure up to 254 individual entry points for a single PHP app: + +```json +{ + "applications": { + "php-app": { + "type": "php", + "targets": { + "front": { + "script": "front.php", + "root": "/www/apps/php-app/front/" + }, + + "back": { + "script": "back.php", + "root": "/www/apps/php-app/back/" + } + } + } + } +} +``` + +Each target is an object that specifies **root** and can define **index** or **script** +just like a regular app does. Targets can be used by the **pass** options +in listeners and routes to serve requests: + +```json +{ + "listeners": { + "127.0.0.1:8080": { + "pass": "applications/php-app/front" + }, + + "127.0.0.1:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/back" + }, + + "action": { + "pass": "applications/php-app/back" + } + } + ] +} +``` + +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 app level. +{{< /warning >}} + +{{< note >}} +For PHP-based examples, see our +[CakePHP]({{< relref "/unit/howto/frameworks/cakephp/">}}), +[CodeIgniter]({{< relref "/unit/howto/frameworks/codeigniter/">}}), +[DokuWiki]({{< relref "/unit/howto/apps/dokuwiki/">}}), +[Drupal]({{< relref "/unit/howto/apps/drupal/">}}), +[Laravel]({{< relref "/unit/howto/frameworks/laravel/">}}), +[Lumen]({{< relref "/unit/howto/frameworks/lumen/">}}), +[Matomo]({{< relref "/unit/howto/apps/matomo/">}}), +[MediaWiki]({{< relref "/unit/howto/apps/mediawiki/">}}), +[MODX]({{< relref "/unit/howto/apps/modx/">}}), +[Nextcloud]({{< relref "/unit/howto/apps/nextcloud/">}}), +[phpBB]({{< relref "/unit/howto/apps/phpbb/">}}), +[phpMyAdmin]({{< relref "/unit/howto/apps/phpmyadmin/">}}), +[Roundcube]({{< relref "/unit/howto/apps/roundcube/">}}), +[Symfony]({{< relref "/unit/howto/frameworks/symfony/">}}), +[WordPress]({{< relref "/unit/howto/apps/wordpress/">}}), +and +[Yii]({{< relref "/unit/howto/frameworks/yii/">}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-php" >}}). +{{< /note >}} + +### Python {#configuration-python} + +First, make sure to install Unit along with the +[Python language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} + +| Option | Description | +|-----------------------|------------------------------------------------------------------------------------------------------------------------| +| **module** (required) | String; app's module name. This module is [imported](https://docs.python.org/3/reference/import.html) by Unit the usual Python way. | +| **callable** | String; name of the **module**-based callable that Unit runs as the app. The default is **application**. | +| **factory** | Boolean: when enabled, Unit treats **callable** as a factory. The default is **false**.

**Note:** Unit does *not* support passing arguments to factories. *(since 1.33.0)* | +| **home** | String; path to the app's [virtual environment](https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments). Absolute or relative to **working_directory**.

**Note:** The Python version used to run the app is determined by **type**; for performance, Unit doesn't use the command-line interpreter from the virtual environment. | +| **path** | String or an array of strings; additional Python module lookup paths. These values are prepended to **sys.path**. | +| **prefix** | String; **SCRIPT_NAME** context value for WSGI or the **root_path** context value for ASGI. Should start with a slash (**/**). | +| **protocol** | String; hints Unit that the app uses a certain interface. Can be **asgi** or **wsgi**. | +| **targets** | Object; app sections with [custom]({{< relref "/unit/configuration.md#configuration-python-targets" >}}) **module** and **callable** values. | +| **thread_stack_size** | Integer; stack size of a worker thread (in bytes, multiple of memory page size; the minimum value is usually architecture specific). The default is usually system dependent and can be set with `ulimit -s `. | +| **threads** | Integer; number of worker threads per [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}). When started, each app process creates this number of threads to handle requests. The default is **1**. | + +{{
}} + + +#### Example: + +```json +{ + "type": "python", + "processes": 10, + "working_directory": "/www/store/cart/", + "path": "/www/store/", + "comment_path": "Added to sys.path for lookup; store the application module within this directory", + "home": ".virtualenv/", + "comment_home": "Path where the virtual environment is located; here, it's relative to the working directory", + "module": "cart.run", + "comment_module": "Looks for a 'run.py' module in /www/store/cart/", + "callable": "app", + "prefix": "/cart", + "comment_prefix": "Sets the SCRIPT_NAME or root_path context value", + "user": "www", + "group": "www" +} +``` + +This snippet runs the **app** callable from the **/www/store/cart/run.py** module +with **/www/store/cart/** as the working directory and **/www/store/.virtualenv/** +as the virtual environment; the **path** value accommodates for situations +when some modules of the app are imported from outside the **cart/** subdirectory. + + + +You can provide the callable in two forms. The first one uses WSGI +([PEP 333](https://peps.python.org/pep-0333/) +or [PEP 3333](https://peps.python.org/pep-3333/)): + +```python +def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + yield b'Hello, WSGI\n' +``` + +The second one, supported with Python 3.5+, uses +[ASGI](https://asgi.readthedocs.io/en/latest/): + +```python +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](https://asgi.readthedocs.io/en/latest/specs/main.html#legacy-applications) +ASGI 2.0 applications were not supported prior to Unit 1.21.0. +{{< /note >}} + +Choose either one according to your needs; Unit tries to infer your choice automatically. +If this inference fails, use the **protocol** option to set the interface explicitly. + +{{< note >}} +The **prefix** option controls the **SCRIPT_NAME** +([WSGI](https://wsgi.readthedocs.io/en/latest/definitions.html)) +or **root_path** +([ASGI](https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope)) +setting in Python's context, allowing to route requests regardless of the app's factual path. +{{< /note >}} + + +#### Targets {#configuration-python-targets} + +You can configure up to 254 individual entry points for a single Python app: + +```json +{ + "applications": { + "python-app": { + "type": "python", + "path": "/www/apps/python-app/", + "targets": { + "front": { + "module": "front.wsgi", + "callable": "app" + }, + + "back": { + "module": "back.wsgi", + "callable": "app" + } + } + } + } +} +``` + +Each target is an object that specifies **module** and can also define **callable** and **prefix** +just like a regular app does. Targets can be used by the **pass** options +in listeners and routes to serve requests: + +```json +{ + "listeners": { + "127.0.0.1:8080": { + "pass": "applications/python-app/front" + }, + + "127.0.0.1:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/back" + }, + + "action": { + "pass": "applications/python-app/back" + } + } + ] +} +``` + +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 app level. Moreover, you can't combine WSGI and ASGI targets +within a single app. +{{< /warning >}} + +{{< note >}} +For Python-based examples, see our +[Bottle]({{< relref "/unit/howto/frameworks/bottle/">}}), +[Datasette]({{< relref "/unit/howto/apps/datasette/">}}), +[Django]({{< relref "/unit/howto/frameworks/django/">}}), +[DjangoChannels]({{< relref "/unit/howto/frameworks/djangochannels/">}}), +[Falcon]({{< relref "/unit/howto/frameworks/falcon/">}}), +[FastAPI]({{< relref "/unit/howto/frameworks/fastapi/">}}), +[Flask]({{< relref "/unit/howto/frameworks/flask/">}}), +[Guillotina]({{< relref "/unit/howto/frameworks/guillotina/">}}), +[Mailman]({{< relref "/unit/howto/apps/mailman/">}}), +[Mercurial]({{< relref "/unit/howto/apps/mercurial/">}}), +[Moin]({{< relref "/unit/howto/apps/moin/">}}), +[Plone]({{< relref "/unit/howto/apps/plone/">}}), +[Pyramid]({{< relref "/unit/howto/frameworks/pyramid/">}}), +[Quart]({{< relref "/unit/howto/frameworks/quart/">}}), +[Responder]({{< relref "/unit/howto/frameworks/responder/">}}), +[ReviewBoard]({{< relref "/unit/howto/apps/reviewboard/">}}), +[Sanic]({{< relref "/unit/howto/frameworks/sanic/">}}), +[Starlette]({{< relref "/unit/howto/frameworks/starlette/">}}), +[Trac]({{< relref "/unit/howto/apps/trac/">}}), +and +[Zope]({{< relref "/unit/howto/frameworks/zope/">}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-python" >}}). +{{< /note >}} + + +### Ruby {#configuration-ruby} + +First, make sure to install Unit along with the +[Ruby language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +{{< note >}} +Unit uses the [Rack](https://rack.github.io) interface to run Ruby scripts; +you need to have it installed as well: + +```console +$ gem install rack +``` +{{< /note >}} + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} + +| Option | Description | +|----------------|----------------------------------------------------------------------------------------------------------------------| +| **script** (required) | String; rack script pathname, including the **.ru** extension, for instance: **/www/rubyapp/script.ru**. | +| **hooks** | String; pathname of the **.rb** file setting the event hooks invoked during the app's lifecycle. | +| **threads** | Integer; number of worker threads per [app process]({{< relref "/unit/howto/security.md#sec-processes" >}}). When started, each app process creates this number of threads to handle requests. The default is **1**. | + +{{}} + + +#### Example: + +```json +{ + "type": "ruby", + "processes": 5, + "user": "www", + "group": "www", + "script": "/www/cms/config.ru", + "hooks": "hooks.rb" +} +``` + +The **hooks** script is evaluated when the app 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 app's lifecycle, for example: + + +```ruby +@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 app. + +{{< note >}} +For Ruby-based examples, see our +[Rails]({{< relref "/unit/howto/frameworks/rails/">}}) +and +[Redmine]({{< relref "/unit/howto/apps/redmine/">}}) +howtos or a basic +[sample]({{< relref "/unit/configuration.md#sample-ruby" >}}). +{{< /note >}} + + +### WebAssembly {#configuration-wasm} + + + +{{}} +{{%tab name="wasm-wasi-component"%}} + +First, make sure to install Unit along with the +[WebAssembly language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} +| Option | Description | +|--------------|-----------------------------------------------------------------------------------------------------------------| +| **component** (required) | String; WebAssembly component pathname, including the **.wasm** extension, for instance: "/var/www/wasm/component.wasm" | +| **access** | Object; its only array member, **filesystem**, lists directories to which the application has access. | +{{}} + +The `access` object has the following structure: + +```json +"access": { + "filesystem": [ + "/tmp/", + "/var/tmp/" + ] +} +``` + +#### Example: + +```json + { + "listeners": { + "127.0.0.1:8080": { + "pass": "applications/wasm" + } + }, + "applications": { + "wasm": { + "type": "wasm-wasi-component", + "component": "/var/www/app/component.wasm", + "access": { + "filesystem": [ + "/tmp/", + "/var/tmp/" + ] + } + } + } + } +``` + +{{< note >}} +A good, first Rust-based project is available at +[sunfishcode/hello-wasi-http](https://github.com/sunfishcode/hello-wasi-http). +It also includes all the important steps to get started with WebAssembly, WASI, and Rust. +{{< /note >}} +{{%/tab%}} + +{{%tab name="unit-wasm"%}} +{{< warning >}} +The `unit-wasm` module is deprecated. We recommend using `wasm-wasi-component` +instead, which supports WebAssembly Components using standard WASI 0.2 interfaces. +The `wasm-wasi-component` module is available in Unit 1.32 and later. +{{< /warning >}} + +First, make sure to install Unit along with the +[WebAssembly language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Besides the +[common options]({{< relref "/unit/configuration.md#configuration-apps-common" >}}), +you have: + +{{}} +| Option | Description | +|----------------------------|-----------------------------------------------------------------------------------------------------------------| +| **module** (required) | String; WebAssembly module pathname, including the **.wasm** extension, for instance: **applications/wasmapp/module.wasm**. | +| **request_handler** (required) | String; name of the request handler function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. The runtime calls this handler, providing the address of the shared memory block used to pass data in and out the app. | +| **malloc_handler** (required) | String; name of the memory allocator function. See note above regarding language-specific handlers in the official `unit-wasm` package. The runtime calls this handler at language module startup to allocate the shared memory block used to pass data in and out the app. | +| **free_handler** (required) | String; name of the memory deallocator function. See note above regarding language-specific handlers in the official `unit-wasm` package. The runtime calls this handler at language module shutdown to free the shared memory block used to pass data in and out the app. | +| **access** | Object; its only array member, **filesystem**, lists directories the application can access: | +| | `"access": {`

    `"filesystem": [`

        `"/tmp/",`

        `"/var/tmp/"`

    `]`

  `}` | +| **module_init_handler** | String; name of the module initialization function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. It is invoked by the WebAssembly language module at language module startup, after the WebAssembly module was initialized. | +| **module_end_handler** | String; name of the module finalization function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. It is invoked by the WebAssembly language module at language module shutdown. | +| **request_init_handler** | String; name of the request initialization function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. It is invoked by the WebAssembly language module at the start of each request. | +| **request_end_handler** | String; name of the request finalization function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. It is invoked by the WebAssembly language module at the end of each request, when the headers and the request body were received. | +| **response_end_handler** | String; name of the response finalization function. If you use Unit with the official `unit-wasm` [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), the value is language specific; see the [SDK](https://github.com/nginx/unit-wasm/) documentation for details. Otherwise, use the name of your custom implementation. It is invoked by the WebAssembly language module at the end of each response, when the headers and the response body were sent. | +{{
}} + + +#### Example: + +```json + { + "type": "wasm", + "module": "/www/webassembly/unitapp.wasm", + "request_handler": "my_custom_request_handler", + "malloc_handler": "my_custom_malloc_handler", + "free_handler": "my_custom_free_handler", + "access": { + "filesystem": [ + "/tmp/", + "/var/tmp/" + ] + }, + "module_init_handler": "my_custom_module_init_handler", + "module_end_handler": "my_custom_module_end_handler", + "request_init_handler": "my_custom_request_init_handler", + "request_end_handler": "my_custom_request_end_handler", + "response_end_handler": "my_custom_response_end_handler" + } +``` + +Use these handlers to add custom runtime logic to your app; for a detailed +discussion of their usage and requirements, see the +[SDK](https://github.com/nginx/unit-wasm/) source code and documentation. + +{{< note >}} +For WASM-based examples, see our [Rust and C samples]({{< relref "/unit/configuration.md#sample-wasm" >}}). +{{< /note >}} +{{%/tab%}} +{{
}} + +## Settings {#configuration-stngs} + +Unit has a global **settings** configuration object that stores instance-wide preferences. + +{{}} +| Option | Description | +|---------------------|--------------------------------------------------------------------------------------------------------------------------| +| **listen_threads** | Integer; controls the number of router threads created to handle client connections. Each thread includes all the configured listeners. By default, we create as many threads as the number of CPUs that are available to run on. *(since 1.33.0)* | +| **http** | Object; fine-tunes handling of HTTP requests from the clients. | +| **js_module** | String or an array of strings; lists enabled `njs` [modules]({{< relref "/unit/scripting.md" >}}), uploaded via the [control API]({{< relref "/unit/controlapi.md" >}}). | +| **telemetry** | Object: OpenTelemetry configuration *(since 1.34.0)* | + +{{}} + + +In turn, the **http** option exposes the following settings: + +{{}} + +| Option | Description | +|------------------------|----------------------------------------------------------------------------------------------------------------------------| +| **body_read_timeout** | Maximum number of seconds to read data from the body of a client's request. This is 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 returns a 408 "Request Timeout" response. The default is 30. | +| **discard_unsafe_fields** | Boolean; controls header field name parsing. If it's set to **true**, Unit only processes header names made of alphanumeric characters and hyphens (see [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#section-16.3.1-6); otherwise, these characters are also permitted: **.!#$%&'*+^_`|~**. The default is **true**. | +| **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 returns a 408 "Request Timeout" response. The default is 30. | +| **idle_timeout** | Maximum number of seconds between requests in a keep-alive connection. If no new requests arrive within this interval, Unit returns a 408 "Request Timeout" response and closes the connection. The default is 180. | +| **log_route** | Boolean; enables or disables [router logging]({{< relref "/unit/troubleshooting.md#troubleshooting-router-log" >}}). The default is **false** (disabled). | +| **max_body_size** | Maximum number of bytes in the body of a client's request. If the body size exceeds this value, Unit returns a 413 "Payload Too Large" response and closes the connection. The default is 8388608 (8 MB). | +| **send_timeout** | Maximum number of seconds to transmit data as a response to the client. This is the interval between consecutive transmissions, not the time for the entire response. If no data is sent to the client within this interval, Unit closes the connection. The default is 30. | +| **server_version** | Boolean; if set to **false**, Unit omits version information in its **Server** response [header fields](https://datatracker.ietf.org/doc/html/rfc9110.html#section-10.2.4). The default is **true**. *(since 1.30.0)* | +| **static** | Object; configures static asset handling. Has a single object option named **mime_types** that defines specific [MIME types](https://www.iana.org/assignments/media-types/media-types.xhtml) as options. Their values can be strings or arrays of strings; each string must specify a filename extension or a specific filename that's included in the MIME type. You can override default MIME types or add new types. | + +{{}} + + +The **telemetry** option exposes the following settings: + +{{}} + +| Option | Description | +|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| **endpoint** | The endpoint for the OpenTelemetry (OTEL) Collector.

It takes a URL to either a gRPC or HTTP(S) endpoint. | +| **protocol** | Determines the protocol used to communicate with the endpoint.

Can be either *http(s)* or *grpc*. | +| **batch_size** | Number of spans to cache before triggering a transaction with the configured endpoint. This is optional.

This allows the user to cache up to N spans before the OpenTelemetry (OTEL) background thread sends spans over the network to the collector.

If specified, it must be a positive integer. | +| **sampling_ratio** | Percentage of requests to trace.

This allows the user to only trace anywhere from 0% to 100% of requests that hit Unit. In high throughput environments this percentage should be lower. This allows the user to save space in storing span data, and to collect request metrics like time to decode headers and whatnot without storing massive amounts of duplicate superfluous data.

If specified, it must be a positive floating point number. | + +{{
}} + + +#### Example: + +```json +"settings": { + "telemetry": { + "batch_size": 20, + "endpoint": "http://example.com/v1/traces", + "protocol": "http", + "sampling_ratio": 1.0 + } +}, +``` + + +## Access log {#configuration-access-log} + +To enable basic 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**: + +```console +curl -X PUT -d '"/var/log/access.log"' \ + --unix-socket /path/to/control.unit.sock \ + http://localhost/config/access_log + + { + "success": "Reconfiguration done." + } +``` + +By default, the log is written in the +[Combined Log Format](https://httpd.apache.org/docs/2.2/logs.html#combined). +Example of a CLF line: + +```none +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" +``` + +### Custom log formatting {#custom-log-format} + +The **access_log** option can be also set to an object to customize both the log path +and its format: + +{{}} + +| Option | Description | +|----------|-------------------------------------------------------------------------------------------------------------------------------| +| **format** | String; sets the log format. Besides arbitrary text, can contain any [variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}) Unit supports. | +| **path** | String; pathname of the access log file. | + +{{}} + + +#### Example: + +```json +{ + "access_log": { + "path": "/var/log/unit/access.log", + "format": "$remote_addr - - [$time_local] \"$request_line\" $status $body_bytes_sent \"$header_referer\" \"$header_user_agent\"" + } +} +``` + +By a neat coincidence, the above **format** is the default setting. +Also, mind that the log entry is formed *after* the request has been handled. + +Besides +[built-in variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}), +you can use `njs` [templates]({{< relref "/unit/scripting.md" >}}) +to define the log format: + +```json +{ + "access_log": { + "path": "/var/log/unit/basic_access.log", + "format": "`${host + ': ' + uri}`" + } +} +``` + +### JSON log format + +Starting with NGINX Unit 1.34.0, **format** can instead be an object +describing JSON field name/value pairs, for example, + +```json +{ + "access_log": { + "path": "/tmp/access.log", + "format": { + "remote_addr": "$remote_addr", + "time_local": "$time_local", + "request_line": "$request_line", + "status": "$status", + "body_bytes_sent": "$body_bytes_sent", + "header_referer": "$header_referer", + "header_user_agent": "$header_user_agent" + } + } +} +``` + +The JSON *values* can be strings, variables and JavaScript. + + +### Conditional access log {#conditional-access-log-1} + +The **access_log** can be dynamically turned on and off by using the **if** option: + +{{}} +| Option | Description | +|--------|-------------| +| **if** | if the value is empty, 0, false, null, or undefined, the logs will not be recorded. | +{{}} + + +This feature lets users set conditions to determine whether access logs are +recorded. The **if** option supports a string and JavaScript code. +If its value is empty, 0, false, null, or undefined, the logs will not be +recorded. And the '!' as a prefix inverses the condition. + +Example without njs: + +```json +{ + "access_log": { + "if": "$cookie_session", + "path": "..." + } +} +``` + +All requests using a session cookie named **session** will be logged. + +We can add `!` to inverse the condition. + +```json +{ + "access_log": { + "if": "!$cookie_session", + "path": "..." + } +} +``` + +Now, all requests without a session cookie will be logged. + +Example with njs and the use of a template literal: + +```json +{ + "access_log": { + "if": "`${uri == '/health' ? false : true}`", + "path": "..." + } +} +``` diff --git a/content/unit/controlapi.md b/content/unit/controlapi.md new file mode 100644 index 000000000..a8c57aab2 --- /dev/null +++ b/content/unit/controlapi.md @@ -0,0 +1,524 @@ +--- +title: Control API +weight: 500 +toc: true +--- + +Unit's configuration is JSON-based, accessible via a RESTful control API, +and entirely manageable over HTTP. The control API provides a root object +(**/**) that comprises four primary options: + + +{{}} +| Object | Description | +|-------------------|------------------------------------------------------------------------------------------------------------------| +| **/certificates**| Responsible for SSL/TLS [certificate management]({{}}). | +| **/config** | Used for general [configuration management]({{}}). | +| **/control** | Queried for [application restart]({{}}). | +| **/status** | Queried for [usage statistics]({{}}). | +{{}} + + + +The API is exposed through a socket whose type and address depend on the + [installation method]({{}}). +Its compile-time setting can be overridden at +[startup]({{}}). + + + +For consistency and +[security]({{}}), +our examples use Unix domain sockets unless stated otherwise. +Example queries use `curl`, and URIs are prefixed with **http://localhost** +as the utility expects (the hostname is irrelevant for Unit itself), +but you can use any HTTP tool you like. For instance, Visual Studio Code users +may benefit from this +[third-party extension](https://marketplace.visualstudio.com/items?itemName=Stanislav.vscode-nginx-unit). + +
+No configuration files used + +The control API is the single source of truth about Unit's configuration. +There are no configuration files that can or should be manipulated; +this is a deliberate design choice made to avoid issues such as: + +- Undetected invalid states: + Configuration files can be saved in an invalid state, + and the issue won't be seen until reload or startup. + The control API avoids this by validating configuration changes on the fly. + +- Too broad or too narrow configuration file permissions: + If a configuration file is inaccessible, it can't be loaded; + if it's public,sensitive data may leak. + The control API has a single manageable point of entry. + +- Unpredictable behavior: + In a configuration file hierarchy, it's easy to lose track and misconfigure something. + With the control API, the entire configuration is a single, organized, and navigatable entity. +
+ + +
+Replicating Unit states + +Although Unit is fully dynamic, sometimes you just want to copy an existing setup +without extra modification. Unit's +[state directory]({{< relref "/unit/howto/source.md#source-config-src-state" >}}) +are interchangeable as long as Unit version stays the same, +so you can use a shortcut to replicate a Unit instance. Also, this works with the +Docker [images]({{< relref "/unit/howto/docker.md" >}}). + +{{< warning >}} +Unit's state can change its structure between versions +and must not be edited by external means. +{{< /warning >}} + +On the machine where the *reference* Unit instance runs, +find out where the state is stored: + +```console +unitd -h + + --state DIRECTORY set state directory name + default: "/path/to/reference/unit/state" # The value we're looking for +``` + +Double-check that the state location isn't overridden at startup: + +```console +ps ax | grep unitd + ... + unit: main v1.34.1 [unitd --state /runtime/path/to/reference/unit/state ... ] # The runtime value overrides the default +``` + +Repeat these commands on the second machine to see where the target instance +stores its state. + +Stop both Unit instances, for example, running the following command as root: + +```console +systemctl stop unit +``` + +{{< note >}} +Stop and start commands may differ if Unit was installed from a +[non-official]({{}}) +repo or built from [source]({{}}). +{{< /note >}} + +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 running the following command as root: + +```console +systemctl restart unit +``` + +If you run your Unit instances manually, **--state** can be used to set the +state directory at [startup]({{}}). + +After restart, the target instance picks up the configuration you've copied to +the state directory. +
+ +## OpenAPI specification {#controlapi-openapi} + +For a more formal approach to Unit's control API, download the +[OpenAPI specification](https://raw.githubusercontent.com/nginx/unit/master/docs/unit-openapi.yaml) +or try the interactive Docker +[ version](/unit/downloads/unit-openapi.Dockerfile). +First, build the image and run a container: + +```console +docker build --tag=unit-openapi -f unit-openapi.Dockerfile . +``` + +```console +docker run -d -p 8765:8765 -p 8080:8080 unit-openapi +``` + +Next, open **http://localhost:8765** in a browser. + +To use this image against a pre-existing Unit instance, type in the address and +port of the instance's +[control socket]({{}}). +(only IP sockets are supported now) on the page: + +![Unit's OpenAPI Image Page - Customizing Control Socket Address](/unit/images/openapi.png) + + +## Quick start {#configuration-quickstart} + +{{< note >}} +Run the `curl` commands in this section as root. +{{< /note >}} + +For a brief intro, we configure Unit to serve a static file. +Suppose you saved this as **/www/data/index.html**: + +```html + + + + Welcome to NGINX Unit! + + + +

Welcome to NGINX Unit!

+

If you see this page, the NGINX Unit web server is successfully + installed and working. Further configuration is required. +

+

For online documentation and support, please refer to + unit.nginx.org.
+

+

Thank you for using NGINX Unit.

+ + +``` + +Now, Unit should +[listen]({{< relref "/unit/configuration.md#configuration-listeners" >}}) +on a port that +[routes]({{< relref "/unit/configuration.md#configuration-routes" >}}) +the incoming requests to a **share** action, +which serves the file: + +```json +{ + "listeners": { + "127.0.0.1:8080": { + "pass": "routes" + } + }, + + "routes": [ + { + "action": { + "share": "/www/data$uri" + } + } + ] +} +``` + +To configure Unit, **PUT** this snippet to the **/config** section via the +[control socket]({{}}). +Working with JSON in the command line +can be cumbersome; instead, save and upload it as **snippet.json**. + +```console +curl -X PUT --data-binary @snippet.json --unix-socket \ + /path/to/control.unit.sock http://localhost/config # Path to Unit's control socket in your installation + + { + "success": "Reconfiguration done." + } +``` + +To confirm this works, query the listener. Unit responds with the **index.html** +file from the **share** directory: + +```console +curl -i 127.0.0.1:8080 + + HTTP/1.1 200 OK + Content-Type: text/html + Server: Unit/1.28.0 + + + + + Welcome to NGINX Unit! + ... +``` + +## API manipulation {#configuration-mgmt} + +To address parts of the control API, query the +[control socket]({{}}) +over HTTP; URI path segments of your API requests must be the names of its +[JSON object](https://datatracker.ietf.org/doc/html/rfc8259#section-4) +members or indexes of its +[JSON array](https://datatracker.ietf.org/doc/html/rfc8259#section-5) +elements. + +{{< note >}} +If you often configure Unit manually, JSON command-line tools such as +[jq](https://stedolan.github.io/jq/) and [jo](https://jpmens.net/2016/03/05/a-shell-command-to-create-json-jo/) may +come in handy. +{{< /note >}} + +The API supports 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 a status message in the HTTP response body.| +| **DELETE**| Deletes the entity at the request URI

and returns a status message in the HTTP response body.| +{{
}} + + +Before a change, Unit checks the difference it makes in the entire configuration; +if there's none, nothing is done. Thus, you can't restart an app +by reuploading its unchanged configuration (but there's a +[way]({{< relref "/unit/configuration.md#configuration-proc-mgmt" >}}) +of restarting apps). + +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. Run the following commands as root: + +```console +# curl -X PUT -d '{ "pass": "applications/blogs" }' --unix-socket \ + /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/listeners/127.0.0.1:8300 +``` + +```console +# curl -X PUT -d '"applications/blogs"' --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/listeners/127.0.0.1:8300/pass +``` + +However, 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 {#conf-examples} + +To minimize typos and effort, +avoid embedding JSON payload in your commands; +instead, store your configuration snippets for review and reuse. +For instance, save your application object as **wiki.json**: + +```json +{ + "type": "python", + "module": "wsgi", + "user": "www-wiki", + "group": "www-wiki", + "path": "/www/wiki/" +} +``` + +Use it to set up an application called **wiki-prod**. + +```console +# curl -X PUT --data-binary @wiki.json \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/applications/wiki-prod +``` + +Use it again to set up a development version of the same app +called **wiki-dev**: + +```console +# curl -X PUT --data-binary @wiki.json \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/applications/wiki-dev +``` + +Toggle the **wiki-dev** app to another source code directory: + +```console +# curl -X PUT -d '"/www/wiki-dev/"' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/applications/wiki-dev/path +``` + +Next, boost the process count for the production app +to warm it up a bit: + +```console +# curl -X PUT -d '5' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/applications/wiki-prod/processes +``` + +Add a listener for the **wiki-prod** app +to accept requests at all host IPs: + +```console +# curl -X PUT -d '{ "pass": "applications/wiki-prod" }' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:8400' +``` + +Plug the **wiki-dev** app into the listener to test it: + +```console +# curl -X PUT -d '"applications/wiki-dev"' --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:8400/pass' +``` + +Then rewire the listener, +adding a URI-based route to the development version of the app: + +```console +$ cat << EOF > config.json + + [ + { + "match": { + "uri": "/dev/*" + }, + + "action": { + "pass": "applications/wiki-dev" + } + } + ] + EOF +``` + +```console +# curl -X PUT --data-binary @config.json --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/routes +``` + +```console +# curl -X PUT -d '"routes"' --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:8400/pass' +``` + +Next, change the **wiki-dev**'s URI prefix +in the **routes** array, +using its index (0): + +```console +# curl -X PUT -d '"/development/*"' --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/routes/0/match/uri +``` + +Append a route to the prod app: +**POST** always adds to the array end, +so there's no need for an index: + +```console +# curl -X POST -d '{"match": {"uri": "/production/*"}, \ + "action": {"pass": "applications/wiki-prod"}}' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 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: + +```console +# curl -X PUT -d '{"match": {"uri": "/production/*"}, \ + "action": {"pass": "applications/wiki-prod"}}' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/routes/1/ +``` + +To get the complete **/config** section: + +```console +# curl --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 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: + +```console +# curl --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 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: + +```console +# curl --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/ > config.json +``` + +To drop the listener on **\*:8400**: + +```console +# curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + '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: + +```console +# curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/routes + + { + "error": "Invalid configuration.", + "detail": "Request \"pass\" points to invalid location \"routes\"." + } +``` diff --git a/content/unit/howto/_index.md b/content/unit/howto/_index.md new file mode 100644 index 000000000..4d4e46b58 --- /dev/null +++ b/content/unit/howto/_index.md @@ -0,0 +1,5 @@ +--- +title: How-to guides +weight: 1100 +url: /unit/howto/ +--- diff --git a/content/unit/howto/ansible.md b/content/unit/howto/ansible.md new file mode 100644 index 000000000..b891e3235 --- /dev/null +++ b/content/unit/howto/ansible.md @@ -0,0 +1,116 @@ +--- +title: Unit in Ansible +weight: 400 +toc: true +--- + +The [Ansible collection](https://galaxy.ansible.com/steampunk/unit) by [XLAB +Steampunk](https://steampunk.si) provides a number of Unit-related tasks +that you can use with Ansible; some of them simplify installation and setup, +while others provide common configuration steps. + +{{< note >}} +Ansible 2.9+ required; the collection relies on official packages and +supports Debian only. + +A brief intro by the collection's authors can be found [here](https://docs.steampunk.si/unit/quickstart.html); a behind-the-scenes +blog post is [here](https://steampunk.si/blog/why-and-how-of-the-nginx-unit-ansible-collection/). +{{< /note >}} + +First, install the collection: + +```console +$ ansible-galaxy collection install steampunk.unit +``` + +After installation, you can use it in a playbook. Consider this +[WSGI app]({{< ref "/unit/configuration.md#python.md" >}}): + +```python +def application(environ, start_response): + start_response("200 OK", [("Content-Type", "text/plain")]) + return (b"Hello, Python on Unit!") +``` + +This [playbook](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html) +installs Unit with the Python language module, copies the app's file, and runs +the app: + +```yaml +--- +- name: Install and run NGINX Unit + hosts: unit_hosts + become: true + + tasks: + - name: Install Unit + include_role: + name: steampunk.unit.install + + - name: Create a directory for our application + file: + path: /var/www + state: directory + comment_path: "Directory where the app will be stored on the host" + + - name: Copy application + copy: + src: files/wsgi.py + dest: /var/www/wsgi.py + mode: "644" + comment_src: "Note that the application's code is copied from a subdirectory" + comment_dest: "Filename on the host" + + - name: Add application config to Unit + steampunk.unit.python_app: + name: sample + module: wsgi + path: /var/www + comment_name: "Becomes the application's name in the configuration" + comment_module: "Goes straight to 'module' in the application's configuration" + comment_path: "Again, goes straight to the application's configuration" + + - name: Expose application via port 3000 + steampunk.unit.listener: + pattern: "*:3000" + pass: applications/sample + comment_pattern: "The listener's name in the configuration" + comment_pass: "Goes straight to 'pass' in the listener's configuration" +``` + +The final preparation step is the +[host inventory](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html) +that lists your managed hosts' addresses: + +```yaml +all: + children: + unit_hosts: + hosts: + 203.0.113.1: +``` + +With everything in place, start the playbook: + +```console +$ ansible-playbook -i inventory.yaml playbook.yaml # Replace with your filenames + + PLAY [Install and run NGINX Unit] *** + + ... + + TASK [Expose application via port 3000] *** + ok: [203.0.113.1] + + PLAY RECAP ******************************** + 203.0.113.1 : ok=15 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +``` + +If it's OK, try the app at the host address from the inventory and the port +number set in the playbook: + +```console +$ curl 203.0.113.1:3000 + + Hello, Python on Unit! +``` diff --git a/content/unit/howto/apps/_index.md b/content/unit/howto/apps/_index.md new file mode 100644 index 000000000..bbeb1a472 --- /dev/null +++ b/content/unit/howto/apps/_index.md @@ -0,0 +1,4 @@ +--- +title: Applications +weight: 1100 +--- \ No newline at end of file diff --git a/content/unit/howto/apps/apollo.md b/content/unit/howto/apps/apollo.md new file mode 100644 index 000000000..5f0c7c992 --- /dev/null +++ b/content/unit/howto/apps/apollo.md @@ -0,0 +1,179 @@ +--- +title: Apollo +weight: 100 +toc: true +--- + +To run the [Apollo](https://www.apollographql.com) GraphQL server +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with the + **unit-dev/unit-devel** package. Next, [install]({{< relref "unit/installation.md#installation-nodejs-package" >}}) Unit's **unit-http** package. Run the following + command as root: + + ```console + # npm install -g --unsafe-perm unit-http + ``` + +2. Create your app directory, [install](https://expressjs.com/en/starter/installing.html) Apollo, and link + **unit-http**. Run the commands starting with a hash (#) as root: + + ```console + $ mkdir -p /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ npm install @apollo/server graphql + ``` + + ```console + # npm link unit-http + ``` + +3. Create the [middleware](https://www.apollographql.com/docs/apollo-server/api/express-middleware/) + module; let's store it as **/path/to/app/apollo.js**. + First, initialize the directory: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ npm init + ``` + + Next, add the following code: + + ```javascript + import { ApolloServer } from '@apollo/server'; + import { expressMiddleware } from '@apollo/server/express4'; + import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; + import express from 'express'; + import http from 'http'; + import cors from 'cors'; + import bodyParser from 'body-parser'; + //import { typeDefs, resolvers } from './schema'; + + const typeDefs = `#graphql + type Query { + hello: String + } + `; + + // A map of functions which return data for the schema. + const resolvers = { + Query: { + hello: () => 'world', + }, + }; + + + // Required logic for integrating with Express + const app = express(); + // Our httpServer handles incoming requests to our Express app. + // Below, we tell Apollo Server to "drain" this httpServer, + // enabling our servers to shut down gracefully. + const httpServer = http.createServer(app); + + // Same ApolloServer initialization as before, plus the drain plugin + // for our httpServer. + const server = new ApolloServer({ + typeDefs, + resolvers, + plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], + }); + // Ensure we wait for our server to start + await server.start(); + + // Set up our Express middleware to handle CORS, body parsing, + // and our expressMiddleware function. + app.use( + '/', + cors(), + bodyParser.json(), + // expressMiddleware accepts the same arguments: + // an Apollo Server instance and optional configuration options + expressMiddleware(server, { + context: async ({ req }) => ({ token: req.headers.token }), + }), + ); + + // Modified server startup; port number is overridden by Unit config + await new Promise((resolve) => httpServer.listen({ port: 80 }, resolve)); + ``` + + Make sure your **package.json** resembles this + (mind **"type": "module"**): + + ```json + { + "name": "unit-apollo", + "version": "1.0.0", + "description": "Running Apollo over Express on Unit", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Unit Team", + "license": "ISC", + "dependencies": { + "@apollo/server": "^4.7.5", + "apollo-server": "^3.12.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "graphql": "^16.7.1", + "unit-http": "^1.30.0" + } + } + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) the Apollo configuration for + Unit: + ```json + { + "listeners": { + "*:80": { + "pass": "applications/apollo" + } + }, + + "applications": { + "apollo": { + "type": "external", + "working_directory": "/path/to/app/", + "_comment_working_directory": "Needed to use the installed NPM modules; use a real path in your configuration", + "executable": "/usr/bin/env", + "_comment_executable": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "apollo.js" + ], + "_comment_arguments": "The env executable runs Node.js, supplying Unit's loader module and your app code as arguments" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener's IP + address and port: + + ![Apollo on Unit](/unit/images/apollo.png) diff --git a/content/unit/howto/apps/bugzilla.md b/content/unit/howto/apps/bugzilla.md new file mode 100644 index 000000000..4947be823 --- /dev/null +++ b/content/unit/howto/apps/bugzilla.md @@ -0,0 +1,93 @@ +--- +title: Bugzilla +weight: 200 +toc: true +--- + +To run the [Bugzilla](https://www.bugzilla.org) bug tracking system using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Perl language module. + + +2. Install and configure Bugzilla's [prerequisites](https://bugzilla.readthedocs.io/en/latest/installing/linux.html#install-packages). + +3. Install Bugzilla`s [core files](https://bugzilla.readthedocs.io/en/latest/installing/linux.html#bugzilla). + Here we install them at **/path/to/app**; use a real path in your configuration. + + {{< note >}} + Unit uses [PSGI](https://metacpan.org/pod/PSGI) to run Perl + applications; Bugzilla natively supports PSGI since version 5.1. + {{< /note >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, + [prepare]({{{< relref "/unit/configuration.md#configuration-perl" >}}}) + the Bugzilla configuration for Unit. The default **.htaccess** scheme roughly + translates into the following (use real values for **share**, **script**, + and **working_directory**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "source": "192.20.225.0/24", + "_comment_source": "Well-known IP range", + "uri": "/data/webdot/*.dot" + }, + "_comment_match": "Restricts access to .dot files to the public webdot server at research.att.com", + + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves static files that match the conditions above" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Unconditionally serves remaining requests that target static files", + "types": [ + "text/css", + "image/*", + "application/javascript" + ], + "_comment_types": "Enables sharing only for certain file types", + "fallback": { + "pass": "applications/bugzilla" + }, + "_comment_fallback": "Serves any requests not served with the 'share' immediately above" + } + } + ], + + "applications": { + "bugzilla": { + "type": "perl", + "working_directory": "/path/to/app/", + "_comment_working_directory": "Path to the application directory; use a real path in your configuration", + "script": "/path/to/app/app.psgi", + "_comment_script": "Path to the application directory; use a real path in your configuration" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://bugzilla.readthedocs.io/en/latest/installing/essential-post-install-config.html) + your Bugzilla installation: + + ![Bugzilla on Unit - Setup Screen](/unit/images/bugzilla.png) diff --git a/content/unit/howto/apps/datasette.md b/content/unit/howto/apps/datasette.md new file mode 100644 index 000000000..55782d2b2 --- /dev/null +++ b/content/unit/howto/apps/datasette.md @@ -0,0 +1,74 @@ +--- +title: Datasette +weight: 300 +toc: true +--- + +To run the [Datasette](https://docs.datasette.io/en/stable/) data exploration tool using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Create a virtual environment to install Datasette's + [PIP package](https://docs.datasette.io/en/stable/installation.html#using-pip), for instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install datasette + $ deactivate + ``` + +3. Running Datasette on Unit requires a wrapper to expose the [application object](https://github.com/simonw/datasette/blob/4f7c0ebd85ccd8c1853d7aa0147628f7c1b749cc/datasette/app.py#L169) + as the ASGI callable. Let's use the following basic version, saving it as + **/path/to/app/asgi.py**: + + ```python + import glob + from datasette.app import Datasette + + application = Datasette(glob.glob('*.db')).app() + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Datasette configuration for + Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/datasette" + } + }, + + "applications": { + "datasette": { + "type": "python 3.Y", + "_comment_type": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "_comment_path": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "_comment_home": "Path to the virtual environment, if any", + "module": "asgi", + "_comment_module": "ASGI module filename with extension omitted", + "callable": "app", + "_comment_callable": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Datasette should be available on the listener’s IP + address and port: + + ![Datasette on Unit - Query Screen](/unit/images/datasette.png) diff --git a/content/unit/howto/apps/dokuwiki.md b/content/unit/howto/apps/dokuwiki.md new file mode 100644 index 000000000..56ee87b6f --- /dev/null +++ b/content/unit/howto/apps/dokuwiki.md @@ -0,0 +1,105 @@ +--- +title: DokuWiki +toc: true +weight: 400 +--- + +To run the [DokuWiki](https://www.dokuwiki.org) content management system +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PGP language module. + +2. Install and configure DokuWiki's [prerequisites](https://www.dokuwiki.org/requirements) + +3. Install DokuWiki's [core files](https://www.dokuwiki.org/install). Here we install them at **/path/to/app**; + use a real path in your configuration. + + ```console + $ mkdir -p /path/to/app/ && cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ wget https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz + ``` + + ```console + $ tar xvzf dokuwiki-stable.tgz --strip-components=1 # Avoids creating a redundant subdirectory + ``` + + ```console + $ rm dokuwiki-stable.tgz + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, prepare the app + [configuration]({{< relref "/unit/configuration.md#configuration-php" >}}) + for Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/data/*", + "/conf/*", + "/bin/*", + "/inc/*", + "/vendor/*" + ] + }, + "_comment_match": "Denies access to files and directories best kept private", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/", + "*.php" + ] + }, + "action": { + "pass": "applications/dokuwiki" + } + }, + { + "action": { + "share": "/path/to/app$uri" + }, + "_comment_action": "Serves static files" + } + ], + + "applications": { + "dokuwiki": { + "type": "php", + "root": "/path/to/app/", + "_comment_root": "Path to the application directory; use a real path in your configuration", + "index": "doku.php", + "_comment_index": "The app's main script" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port. + +7. Browse to **/install.php** to complete your [installation](https://www.dokuwiki.org/installer): + + ![DokuWiki on Unit - Installation Screen](/unit/images/dokuwiki.png) diff --git a/content/unit/howto/apps/drupal.md b/content/unit/howto/apps/drupal.md new file mode 100644 index 000000000..ce917466f --- /dev/null +++ b/content/unit/howto/apps/drupal.md @@ -0,0 +1,157 @@ +--- +title: Drupal +weight: 500 +toc: true +--- + +To run the [Drupal](https://www.drupal.org) content management system using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure Drupal's [prerequisites](https://www.drupal.org/docs/system-requirements) + +3. Install Drupal's [core files](https://www.drupal.org/docs/develop/using-composer/manage-dependencies#download-core). +Here we install them at **/path/to/app**; use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Drupal configuration for Unit. + The default **.htaccess** [scheme](https://github.com/drupal/drupal) + in a Drupal installation roughly translates into the following (use real + values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "!*/.well-known/*", + "/vendor/*", + "/core/profiles/demo_umami/modules/demo_umami_content/default_content/*", + "*.engine", + "*.inc", + "*.install", + "*.make", + "*.module", + "*.po", + "*.profile", + "*.sh", + "*.theme", + "*.tpl", + "*.twig", + "*.xtmpl", + "*.yml", + "*/.*", + "*/Entries*", + "*/Repository", + "*/Root", + "*/Tag", + "*/Template", + "*/composer.json", + "*/composer.lock", + "*/web.config", + "*sql", + "*.bak", + "*.orig", + "*.save", + "*.swo", + "*.swp", + "*~" + ] + }, + "_comment_match": "Denies access to certain types of files and directories best kept hidden, allows access to well-known locations according to RFC 5785", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/core/authorize.php", + "/core/core.api.php", + "/core/globals.api.php", + "/core/install.php", + "/core/modules/statistics/statistics.php", + "~^/core/modules/system/tests/https?\\.php", + "/core/rebuild.php", + "/update.php", + "/update.php/*" + ] + }, + "_comment_match": "Allows direct access to core PHP scripts", + "action": { + "pass": "applications/drupal/direct" + } + }, + { + "match": { + "uri": [ + "!/index.php*", + "*.php" + ] + }, + "_comment_match": "Explicitly denies access to any PHP scripts other than index.php", + "action": { + "return": 404 + } + }, + { + "action": { + "share": "/path/to/app/web$uri", + "_comment_share": "Serves static files", + "fallback": { + "pass": "applications/drupal/index" + }, + "_comment_fallback": "Funnels all requests to index.php" + } + } + ], + + "applications": { + "drupal": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/web/", + "_comment_root": "Path to the web/ directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/web/", + "_comment_root": "Path to the web/ directory; use a real path in your configuration", + "script": "index.php", + "_comment_script": "All requests are handled by a single script" + } + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of + the **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the + URI or **index.php** if the URI omits it. + - The **index** target specifies the **script** that Unit + runs for *any* URIs the target receives. + {{< /note >}} + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://www.drupal.org/docs/develop/using-composer/manage-dependencies#s-install-drupal-using-the-standard-web-interface) + your Drupal installation: + + ![Drupal on Unit - Setup Screen](/unit/images/drupal.png) diff --git a/content/unit/howto/apps/grafana.md b/content/unit/howto/apps/grafana.md new file mode 100644 index 000000000..3a4b47952 --- /dev/null +++ b/content/unit/howto/apps/grafana.md @@ -0,0 +1,192 @@ +--- +title: Grafana +toc: true +weight: 600 +--- + +Here, we install Grafana from [sources](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md) +so we can +[configure it]({{< relref "/unit/configuration.md#configuration-go" >}}) +to run on Unit. + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Go language module. + + Also, make sure Unit's Go module is available at **\$GOPATH**. + +2. Download Grafana's source files: + + ```console + $ go get github.com/grafana/grafana + ``` + +3. Update the code, adding Unit to Grafana's protocol list. You can either + apply a patch ([ grafana.patch](/unit/downloads/grafana.patch)): + + ```console + $ cd $GOPATH/src/github.com/grafana/grafana # The path where the previous step saves the application's files + ``` + + ```console + $ curl -O https://unit.nginx.org/_downloads/grafana.patch + ``` + + ```console + $ patch -p1 < grafana.patch + ``` + + Or update the sources manually. In **conf/defaults.ini**: + + ```ini + #################################### Server ############################## + [server] + # Protocol (http, https, socket, unit) + protocol = unit + ``` + + In **pkg/api/http_server.go**: + + ```go + import ( + // ... + "net/http" + "unit.nginx.org/go" + "os" + // ... + ) + + // ... + + switch setting.Protocol { + + // ... + + case setting.HTTP, setting.HTTPS, setting.HTTP2: + var err error + listener, err = net.Listen("tcp", hs.httpSrv.Addr) + if err != nil { + return errutil.Wrapf(err, "failed to open listener on address %s", hs.httpSrv.Addr) + } + case setting.SOCKET: + var err error + listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"}) + if err != nil { + return errutil.Wrapf(err, "failed to open listener for socket %s", setting.SocketPath) + } + case setting.UNIT: + var err error + err = unit.ListenAndServe(hs.httpSrv.Addr, hs.macaron) + if err == http.ErrServerClosed { + hs.log.Debug("server was shutdown gracefully") + return nil + } + ``` + + In **pkg/setting/setting.go**: + + ```go + const ( + HTTP Scheme = "http" + HTTPS Scheme = "https" + SOCKET Scheme = "socket" + UNIT Scheme = "unit" + DEFAULT_HTTP_ADDR string = "0.0.0.0" + ) + + // ... + + Protocol = HTTP + protocolStr, err := valueAsString(server, "protocol", "http") + // ... + if protocolStr == "https" { + Protocol = HTTPS + CertFile = server.Key("cert_file").String() + KeyFile = server.Key("cert_key").String() + } + if protocolStr == "h2" { + Protocol = HTTP2 + CertFile = server.Key("cert_file").String() + KeyFile = server.Key("cert_key").String() + } + if protocolStr == "socket" { + Protocol = SOCKET + SocketPath = server.Key("socket").String() + } + if protocolStr == "unit" { + Protocol = UNIT + } + ``` + +4. Build Grafana: + + ```console + $ cd $GOPATH/src/github.com/grafana/grafana # The path where the previous step saves the application's files + $ go get ./... # Installs dependencies + $ go run build.go setup + $ go run build.go build + $ yarn install --pure-lockfile + $ yarn start + ``` + + Note the directory where the newly built **grafana-server** is placed, + usually **\$GOPATH/bin/**; it's used for the **executable** option in + the Unit configuration. + +5. Run the following commands (as root) so Unit can access Grafana's files: + + ```console + # chown -R unit:unit $GOPATH/src/github.com/grafana/grafana # User and group that Unit's router runs as by default | Path to the application's files + ``` + + ```console + # chown unit:unit $GOPATH/bin/grafana-server # User and group that Unit's router runs as by default | Path to the application's executable + ``` + + {{< note >}} + The **unit:unit** user-group pair is available only with + [official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), Docker + [images]({{< relref "/unit/installation.md#installation-docker" >}}) + , and some + [third-party repos]({{< relref "/unit/installation.md#installation-community-repos" >}}). + Otherwise, account names may differ; run the `ps aux | grep unitd` command to be sure. + {{< /note >}} + + For further details, including permissions, see the + [security checklist]({{< relref "/unit/howto/security.md#security-apps" >}}). + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the configuration (replace + **\$GOPATH** with its value in **executable** and + **working_directory**): + + ```json + { + "listeners": { + "*:3000": { + "pass": "applications/grafana" + } + }, + + "applications": { + "grafana": { + "executable": "$GOPATH/bin/grafana-server", + "_comment_executable": "Replace with the environment variable's value | Path to the application's executable", + "type": "external", + "working_directory": "$GOPATH/src/github.com/grafana/grafana/", + "_comment_working_directory": "Replace with the environment variable's value | Path to the application's files" + } + } + } + ``` + + See + [Go application options]({{< relref "/unit/configuration.md#configuration-go" >}}) + and the Grafana [docs](https://grafana.com/docs/grafana/latest/administration/configuration/#static_root_path) + for details. + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Grafana should be available on the listener's IP + and port: + + ![Grafana on Unit - Setup Screen](/unit/images/grafana.png) diff --git a/content/unit/howto/apps/jira.md b/content/unit/howto/apps/jira.md new file mode 100644 index 000000000..ad99fcd81 --- /dev/null +++ b/content/unit/howto/apps/jira.md @@ -0,0 +1,168 @@ +--- +title: Jira +toc: true +weight: 700 +--- + +{{< note >}} +This howto uses the 8.19.1 version; other versions may have different +dependencies and options. +{{< /note >}} + +To run [Atlassian Jira](https://www.atlassian.com/software/jira) using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Java language module. + +2. Install Jira's [core files](https://www.atlassian.com/software/jira/update). Here we install them at **/path/to/app**; + use a real path in your configuration. + + For example: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ curl https://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-software-8.19.1.tar.gz -O -C - + ``` + + ```console + $ tar xzf atlassian-jira-core-8.19.1.tar.gz --strip-components 1 + ``` + +3. Download Jira's third-party dependencies to the **lib** subdirectory: + + ```console + $ cd lib/ + ``` + + ```console + $ curl https://github.com/mar0x/unit-transaction-init/releases/download/2.0/transaction-init-2.0.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/com/atomikos/atomikos-util/5.0.8/atomikos-util-5.0.8.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/com/atomikos/transactions-api/5.0.8/transactions-api-5.0.8.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/com/atomikos/transactions-jdbc/5.0.8/transactions-jdbc-5.0.8.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/com/atomikos/transactions-jta/5.0.8/transactions-jta-5.0.8.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/com/atomikos/transactions/5.0.8/transactions-5.0.8.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-jndi/11.0.6/jetty-jndi-10.0.6.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-plus/11.0.6/jetty-plus-10.0.6.jar -O -C - + ``` + + ```console + $ curl https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-util/11.0.6/jetty-util-10.0.6.jar -O -C - + ``` + + Later, these **.jar** files will be listed in the **classpath** + option of the Unit configuration. + +4. Patch your Jira configuration, dropping **env** from the + **comp/env/UserTransaction** object path. This ensures the + **UserTransaction** object will be found by your installation: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ sed -i 's#comp/env/UserTransaction#comp/UserTransaction#g' \ + atlassian-jira/WEB-INF/classes/entityengine.xml + ``` + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +6. Next, + [put together]({{< relref "/unit/configuration.md#configuration-java" >}}) + the Jira configuration (use real values for **working_directory** and **jira.home**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/jira" + } + }, + + "applications": { + "jira": { + "type": "java", + "working_directory": "/path/to/app/", + "_comment_working_directory": "Path to the application directory; use a real path in your configuration", + "webapp": "atlassian-jira", + "options": [ + "-Djava.awt.headless=true", + "-Djavax.accessibility.assistive_technologies= ", + "-Djira.home=/path/to/jira/home/", + "_comment_options": "Path to your Jira home directory; use a real path in your configuration", + "-Dnginx.unit.context.listener=nginx.unit.TransactionInit", + "-Xms1024m", + "-Xmx1024m" + ], + "_comment_options": "Jira-specific startup options", + "classpath": [ + "lib/atomikos-util-5.0.8.jar", + "lib/hsqldb-1.8.0.10.jar", + "lib/jcl-over-slf4j-1.7.30.jar", + "lib/jetty-jndi-10.0.6.jar", + "lib/jetty-plus-10.0.6.jar", + "lib/jetty-util-10.0.6.jar", + "lib/jta-1.1.jar", + "lib/log4j-1.2.17-atlassian-3.jar", + "lib/slf4j-api-1.7.30.jar", + "lib/slf4j-log4j12-1.7.30.jar", + "lib/transaction-init-2.0.jar", + "lib/transactions-5.0.8.jar", + "lib/transactions-api-5.0.8.jar", + "lib/transactions-jdbc-5.0.8.jar", + "lib/transactions-jta-5.0.8.jar" + ], + "_comment_classpath": "Required third-party dependencies from Step 3" + } + } + } + ``` + + See + [Java application options]({{< relref "/unit/configuration.md#configuration-java" >}}) + for details. + + {{< note >}} + You can't update the configuration in Unit after startup due to Jira's + own restrictions. + {{< /note >}} + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Jira should be available on the listener's IP + address and port. Browse to to continue the setup in + your browser: + + ![Jira on Unit - Setup Screen](/unit/images/jira.png) diff --git a/content/unit/howto/apps/joomla.md b/content/unit/howto/apps/joomla.md new file mode 100644 index 000000000..b451bef40 --- /dev/null +++ b/content/unit/howto/apps/joomla.md @@ -0,0 +1,96 @@ +--- +title: Joomla +toc: true +weight: 800 +--- + +To run the [Joomla](https://www.joomla.org) content management system using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + + +2. Install and configure Joomla's [prerequisites](https://downloads.joomla.org/technical-requirements) + +3. Install Joomla's [core files](https://docs.joomla.org/Special:MyLanguage/J3.x:Installing_Joomla). +Here we install them at **/path/to/app**; use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Joomla configuration for + Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*", + "/administrator/" + ] + }, + "_comment_match": "Matches direct URLs and the administrative section of the site", + "action": { + "pass": "applications/joomla/direct" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves matching static files", + "fallback": { + "pass": "applications/joomla/index" + }, + "_comment_fallback": "Unconditionally matches all remaining URLs, including rewritten ones" + } + } + ], + + "applications": { + "joomla": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/", + "_comment_root": "Path to the application directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/", + "_comment_root": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "_comment_script": "All requests are handled by a single script" + } + } + } + } + } + ``` + + The first route step handles the admin section and all URLs that specify a + PHP script; the **direct** target doesn't set the **script** option + to be used by default, so Unit looks for the respective **.php** file. + + The next step serves static files via a **share**. Its **fallback** + enables rewrite mechanics for [search-friendly URLs](https://docs.joomla.org/Enabling_Search_Engine_Friendly_(SEF)_URLs). All + requests go to the **index** target that runs the **index.php** + script at Joomla's directory root. + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Joomla should be available on the listener’s IP + and port to finish the [setup](https://docs.joomla.org/J3.x:Installing_Joomla#Main_Configuration): + + ![Joomla on Unit - Setup Screen](/unit/images/joomla.png) + diff --git a/content/unit/howto/apps/mailman.md b/content/unit/howto/apps/mailman.md new file mode 100644 index 000000000..4893d59dc --- /dev/null +++ b/content/unit/howto/apps/mailman.md @@ -0,0 +1,105 @@ +--- +title: Mailman Web +toc: true +weight: 800 +--- + +To install and run the web UI for the [Mailman 3](https://docs.list.org/en/latest/index.html) suite using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.7+ language module. + +2. Follow Mailman's [guide](https://docs.list.org/en/latest/install/virtualenv.html#virtualenv-install) + to install its prerequisites and core files, but stop at [setting up a WSGI + server](https://docs.list.org/en/latest/install/virtualenv.html#setting-up-a-wsgi-server); + we'll use Unit instead. Also, note the following settings (values from the + guide are given after the colon): + + - Virtual environment path: **/opt/mailman/venv/** + - Installation path: **/etc/mailman3/** + - Static file path: **/opt/mailman/web/static/** + - User and group: **mailman:mailman** + + These are needed to configure Unit. + +3. Run the following command (as root) so Unit can access Mailman's static files: + + ```console + # chown -R unit:unit /opt/mailman/web/static/ # User and group that Unit's router runs as by default | Mailman's static file path + ``` + + {{< note >}} + The **unit:unit** user-group pair is available only with + [official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}), Docker + [images]({{< relref "/unit/installation.md#installation-docker" >}}) + , and some + [third-party repos]({{< relref "/unit/installation.md#installation-community-repos" >}}). + Otherwise, account names may differ; run the `ps aux | grep unitd` command to be sure. + {{< /note >}} + + Alternatively, add Unit's unprivileged user account to Mailman's group so Unit + can access the static files. Run the following command as root: + + ```console + # usermod -a -G mailman unit # Mailman's user group noted in Step 2 | User that Unit's router runs as by default + ``` + +4. Next, prepare the Mailman [configuration]({{< relref "/unit/configuration.md#configuration-python" >}}) for Unit + (use values from Step 2 for **share**, **path**, and **home**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/static/*" + }, + "_comment_match": "Matches requests for web UI's static content", + "action": { + "share": "/opt/mailman/web/$uri" + }, + "_comment_action": "Mailman's static file path without the 'static/' part; URIs starting with /static/ are thus served from /opt/mailman/web/static/" + }, + { + "action": { + "pass": "applications/mailman_web" + } + } + ], + + "applications": { + "mailman_web": { + "type": "python 3.X", + "_comment_type": "Must match language module version and virtual environment version", + "path": "/etc/mailman3/", + "_comment_path": "Mailman's installation path you noted in Step 2", + "home": "/opt/mailman/venv/", + "_comment_home": "Mailman's virtual environment path you noted in Step 2", + "module": "mailman_web.wsgi", + "_comment_module": "Qualified name of the WSGI module, relative to installation path", + "user": "mailman", + "_comment_user": "Mailman's user group noted in Step 2", + "environment": { + "DJANGO_SETTINGS_MODULE": "settings" + }, + "_comment_environment": "App-specific environment variables", + "_comment_DJANGO_SETTINGS_MODULE": "Web configuration module name, relative to installation path" + } + } + } + ``` + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Mailman's web UI should be available on the + listener’s IP address and port: + + ![Mailman on Unit - Lists Screen](/unit/images/mailman.png) + diff --git a/content/unit/howto/apps/matomo.md b/content/unit/howto/apps/matomo.md new file mode 100644 index 000000000..f518866f9 --- /dev/null +++ b/content/unit/howto/apps/matomo.md @@ -0,0 +1,125 @@ +--- +title: Matomo +toc: true +weight: 900 +--- + +To run the [Matomo](https://matomo.org) web analytics platform using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure Matomo's [prerequisites](https://matomo.org/faq/on-premise/matomo-requirements/) + +3. Install Matomo's [core files](https://matomo.org/faq/on-premise/installing-matomo/). + Here we install them at **/path/to/app**; use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Matomo configuration for Unit + (use real values for **share** and **root**). The default + **.htaccess** scheme in a Matomo installation roughly translates into the + following: + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/index.php", + "/js/index.php", + "/matomo.php", + "/misc/cron/archive.php", + "/piwik.php", + "/plugins/HeatmapSessionRecording/configs.php" + ] + }, + "_comment_match": "Handles all PHP scripts that should be public", + "action": { + "pass": "applications/matomo/direct" + } + }, + { + "match": { + "uri": [ + "*.php", + "*/.htaccess", + "/config/*", + "/core/*", + "/lang/*", + "/tmp/*" + ] + }, + "_comment_match": "Denies access to files and directories best kept private, including internal PHP scripts", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": "~\\.(css|gif|html?|ico|jpg|js(on)?|png|svg|ttf|woff2?)$" + }, + "_comment_match": "Enables access to static content only", + "action": { + "share": "/path/to/app$uri" + }, + "_comment_action": "Serves matching static files" + }, + { + "match": { + "uri": [ + "!/libs/*", + "!/node_modules/*", + "!/plugins/*", + "!/vendor/*", + "!/misc/cron/*", + "!/misc/user/*" + ] + }, + "_comment_match": "Disables access to certain directories that may nonetheless contain public-facing static content served by the previous rule; forwards all unhandled requests to index.php in the root directory", + "action": { + "share": "/path/to/app$uri", + "_comment_action": "Serves remaining static files", + "fallback": { + "pass": "applications/matomo/index" + }, + "_comment_fallback": "A catch-all destination for the remaining requests" + } + } + ], + + "applications": { + "matomo": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/" + }, + "_comment_direct": "Path to the application directory; use a real path in your configuration", + "index": { + "root": "/path/to/app/", + "script": "index.php" + }, + "_comment_index": "All requests are handled by a single script" + } + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Matomo should be available on the listener’s IP + address and port: + + ![Matomo on Unit](/unit/images/matomo.png) diff --git a/content/unit/howto/apps/mediawiki.md b/content/unit/howto/apps/mediawiki.md new file mode 100644 index 000000000..c8a8261da --- /dev/null +++ b/content/unit/howto/apps/mediawiki.md @@ -0,0 +1,168 @@ +--- +title: MediaWiki +weight: 1000 +toc: true +--- + +To run the [MediaWiki](https://www.mediawiki.org) collaboration and +documentation platform using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PGP language module. + +2. Install MediaWiki's [core files](https://www.mediawiki.org/wiki/Download). + Here we install them at **/path/to/app**; use a real path in your configuration. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the MediaWiki configuration for Unit + (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "!/tests/qunit/*", + "/cache/*", + "/includes/*", + "/languages/*", + "/maintenance/*", + "/tests/*", + "/vendor/*" + ] + }, + "_comment_match": "Controls access to directories best kept private", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/api.php*", + "/img_auth.php*", + "/index.php*", + "/load.php*", + "/mw-config/*.php", + "/opensearch_desc.php*", + "/profileinfo.php*", + "/rest.php*", + "/tests/qunit/*.php", + "/thumb.php*", + "/thumb_handler.php*" + ] + }, + "_comment_match": "Enables access to application entry points", + "action": { + "pass": "applications/mw/direct" + } + }, + { + "match": { + "uri": [ + "!*.php", + "!*.json", + "!*.htaccess", + "/extensions/*", + "/images/*", + "/resources/assets/*", + "/resources/lib/*", + "/resources/src/*", + "/skins/*" + ] + }, + "_comment_match": "Enables static access to specific content locations", + "_comment_negations": "The negations deny access to the file types listed here", + "action": { + "share": "/path/to/app$uri" + }, + "_comment_action": "Serves matching static files" + }, + { + "action": { + "pass": "applications/mw/index" + } + } + ], + + "applications": { + "mw": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/" + }, + "_comment_direct": "Path to the application directory; use a real path in your configuration", + "index": { + "root": "/path/to/app/", + "script": "index.php" + }, + "_comment_index": "All requests are handled by a single script" + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the w omits it. + - The **index** target specifies the **script** that Unit runs + for *any* URIs the target receives. + {{< /note >}} + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + +6. Browse to and set MediaWiki up using + the settings noted earlier: + + ![MediaWiki on Unit](/unit/images/mw_install.png) + + Download the newly generated **LocalSettings.php** file and place it + [appropriately](https://www.mediawiki.org/wiki/Manual:Config_script): + + ```console + $ chmod 600 LocalSettings.php + ``` + + Run the following commands (as root) to set the correct ownership: + + ```console + # chown unit:unit LocalSettings.php # Values from Step 3 + ``` + + ```console + # mv LocalSettings.php /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + +7. After installation, add a match condition to the first step to disable + access to the **mw-config/** directory. Run the following command (as root): + + ```console + # curl -X POST -d '"/mw-config/*"' \ + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/config/routes/mediawiki/0/match/uri/ # Path to the route's first step condition and the 'uri' value in it + + { + "success": "Reconfiguration done." + }\ + ``` + + After a successful update, MediaWiki should be available on the listener’s IP + address and port: + + ![MediaWiki on Unit](/unit/images/mw_ready.png) diff --git a/content/unit/howto/apps/mercurial.md b/content/unit/howto/apps/mercurial.md new file mode 100644 index 000000000..3dc69c8e4 --- /dev/null +++ b/content/unit/howto/apps/mercurial.md @@ -0,0 +1,95 @@ +--- +title: Mercurial +weight: 1100 +toc: true +--- + +To install and run the [Mercurial](https://www.mercurial-scm.org) source +control system using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python language module. + +2. Install Mercurial's [core files](https://www.mercurial-scm.org/wiki/UnixInstall). + Here we install them at **/path/to/app**; use a real path in your configuration. + +3. Optionally, configure a [repository](https://www.mercurial-scm.org/wiki/TutorialInit) or choose an existing + one, noting its directory path. + +4. Unit [uses WSGI]({{< relref "/unit/configuration.md#configuration-python" >}}) to run Python apps, so it + requires a [wrapper](https://www.mercurial-scm.org/repo/hg/file/default/contrib/hgweb.wsgi) + script to publish a Mercurial repo. Here, it's **/path/to/app/hgweb.py** + (note the extension); the **application** callable is the entry + point: + + ```python + from mercurial.hgweb import hgweb + + # path to a repo or a hgweb config file to serve in UTF-8 (see 'hg help hgweb') + application = hgweb("/path/to/app/repo/or/config/file".encode("utf-8")) # Replace with a real path in your configuration + ``` + + This is a very basic script; to elaborate on it, see the + Mercurial repo publishing [guide](https://www.mercurial-scm.org/wiki/PublishingRepositories#hgweb). + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Next, prepare the Mercurial [configuration]({{< relref "/unit/configuration.md#configuration-python" >}}) for Unit (use a real value for **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/hg" + } + }, + + "applications": { + "hg": { + "type": "python", + "path": "/path/to/app/", + "_comment_path": "Path to the WSGI file referenced by the module option; use a real path in your configuration", + "module": "hgweb", + "_comment_module": "WSGI module basename with extension omitted" + } + } + } + ``` + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, you can proceed to work with your Mercurial + repository as usual: + + ```console + hg config --edit + ``` + + ```console + hg clone http://localhost/ project/ + ``` + + ```console + cd project/ + ``` + + ```console + touch hg_rocks.txt + ``` + + ```console + hg add + ``` + + ```console + hg commit -m 'Official: Mercurial on Unit rocks!' + ``` + + ```console + hg push + ``` + + ![Mercurial on Unit - Changeset Screen](/unit/images/hg.png) diff --git a/content/unit/howto/apps/modx.md b/content/unit/howto/apps/modx.md new file mode 100644 index 000000000..cf2b9635b --- /dev/null +++ b/content/unit/howto/apps/modx.md @@ -0,0 +1,85 @@ +--- +title: MODX +weight: 1200 +toc: true +--- + +To run the [MODX](https://modx.com) content application platform using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure MODX's [prerequisites]() + +3. Install MODX's [core files](https://modx.com/download). Here we install them at **/path/to/app**; + use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the MODX configuration for Unit + (use real values for **share** and **root**). The default + **.htaccess** scheme in a MODX installation roughly translates into the + following: + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "!/.well-known/", + "/core/*", + "*/.*" + ] + }, + "_comment_match": "Denies access to directories best kept private", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": "*.php" + }, + "_comment_match": "Serves direct requests for PHP scripts", + "action": { + "pass": "applications/modx" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves static files", + "fallback": { + "pass": "applications/modx" + }, + "_comment_fallback": "A catch-all destination for the remaining requests" + } + } + ], + + "applications": { + "modx": { + "type": "php", + "root": "/path/to/app/", + "_comment_root": "Path to the application directory; use a real path in your configuration" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, MODX should be available on the listener’s IP + address and port: + + ![MODX on Unit - Manager Screen](/unit/images/modx.png) diff --git a/content/unit/howto/apps/moin.md b/content/unit/howto/apps/moin.md new file mode 100644 index 000000000..f062b6edc --- /dev/null +++ b/content/unit/howto/apps/moin.md @@ -0,0 +1,159 @@ +--- +title: MoinMoin +toc: true +weight: 1300 +--- + +{{< warning >}} +So far, Unit doesn't support handling the **REMOTE_USER** headers directly, so +authentication should be implemented via other means. For a +full list of available authenticators, see [here](https://moinmo.in/HelpOnAuthentication). +{{< /warning >}} + +To run the [MoinMoin](https://moinmo.in/MoinMoinWiki) wiki engine using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 2 language module. + + {{< note >}} + As of now, MoinMoin [doesn't fully support](https://moinmo.in/Python3) + Python 3. Mind that Python 2 is officially deprecated. + {{< /note >}} + +2. Install and configure MoinMoin's [prerequisites](https://moinmo.in/MoinMoinDependencies) + +3. Install MoinMoin's [core files](https://moinmo.in/MoinMoinDownload). Here we install them at **/path/to/app**; + use a real path in your configuration. + + For example: + + ```console + tar xzf moin-X.Y.Z.tar.gz --strip-components 1 -C /path/to/app/ # MoinMoin version | Path to the application directory; use a real path in your configuration + ``` + +4. Configure your wiki instances: + + {{}} + {{%tab name="Single Wiki"%}} + See the 'Single Wiki' section [here](https://master.moinmo.in/InstallDocs/ServerInstall) for an explanation of these commands: + + ```console + cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + mkdir single/ + ``` + + ```console + cp wiki/config/wikiconfig.py single/ # Wiki instance configuration + ``` + + ```console + cp -r wiki/data/ single/data/ + ``` + + ```console + cp -r wiki/underlay/ single/underlay/ + ``` + + ```console + cp wiki/server/moin.wsgi single/moin.py # WSGI module to run, extension should be changed for proper discovery + ``` + + Next, [edit](https://moinmo.in/HelpOnConfiguration#Configuring_a_single_wiki) + the wiki instance configuration in **wikiconfig.py** as appropriate. + {{%/tab%}} + + {{%tab name="Multiple Wikis"%}} + + + See the 'Multiple Wikis' section [here](https://master.moinmo.in/InstallDocs/ServerInstall) for an explanation of these commands: + + ```console + cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + mkdir multi/ multi/wiki1/ multi/wiki2/ + ``` + + ```console + cp wiki/config/wikifarm/* multi/ + ``` + + ```console + cp wiki/config/wikiconfig.py multi/wiki1.py # Wiki instance configuration + + ``` + + ```console + cp wiki/config/wikiconfig.py multi/wiki2.py # Wiki instance configuration + + ``` + + ```console + cp -r wiki/data/ multi/wiki1/data/ + ``` + + ```console + cp -r wiki/data/ multi/wiki2/data/ + ``` + + ```console + cp -r wiki/underlay/ multi/wiki1/underlay/ + ``` + + ```console + cp -r wiki/underlay/ multi/wiki2/underlay/ + ``` + + ```console + cp wiki/server/moin.wsgi multi/moin.py # WSGI module to run, extension should be changed for proper discovery + ``` + + Next, [edit](https://moinmo.in/HelpOnConfiguration#Configuration_of_multiple_wikis) + the farm configuration in **farmconfig.py** and the wiki instance + configurations, shown here as **wiki1.py** and **wiki2.py**, as appropriate. + + {{%/tab%}} + {{}} + + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the MoinMoin configuration for + Unit (use real values for **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/moin" + } + }, + + "applications": { + "moin": { + "type": "python 2", + "path": [ + "/path/to/app/wsgi/module/", + "/path/to/app/" + ], + "_comment_path": "Path where the WSGI module was stored at Step 4 | Path where the MoinMoin directory was extracted at Step 3", + "module": "moin", + "_comment_module": "WSGI file basename" + } + } + } + ``` + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, MoinMoin should be available on the listener’s IP + address and port: + + ![Moin on Unit - Welcome Screen](/unit/images/moin.png) diff --git a/content/unit/howto/apps/nextcloud.md b/content/unit/howto/apps/nextcloud.md new file mode 100644 index 000000000..a1877a943 --- /dev/null +++ b/content/unit/howto/apps/nextcloud.md @@ -0,0 +1,192 @@ +--- +title: NextCloud +weight: 1400 +toc: true +--- + +To run the [NextCloud](https://nextcloud.com) share and collaboration +platform using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure NextCloud's [prerequisites](https://docs.nextcloud.com/server/latest/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation) + +3. Install NextCloud's [core files](https://docs.nextcloud.com/server/latest/admin_manual/installation/command_line_installation.html). Here we install them at **/path/to/app**; + use a real path in your configuration. + + {{< note >}} + Verify the resulting settings in **/path/to/app/config/config.php**; + in particular, check the [trusted domains](https://docs.nextcloud.com/server/latest/admin_manual/installation/installation_wizard.html#trusted-domains-label) + to ensure the installation is accessible within your network: + + ```php + 'trusted_domains' => + array ( + 0 => 'localhost', + 1 => '*.example.com', + ), + ``` + {{< /note >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, + [put together]({{< relref "/unit/configuration.md#configuration-php" >}}) + the NextCloud configuration for Unit (use real values for **share** and + **root**). The following is based on NextCloud's own + [guide](https://docs.nextcloud.com/server/latest/admin_manual/installation/nginx.html): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/build/*", + "/tests/*", + "/config/*", + "/lib/*", + "/3rdparty/*", + "/templates/*", + "/data/*", + "/.*", + "/autotest*", + "/occ*", + "/issue*", + "/indie*", + "/db_*", + "/console*" + ] + }, + "_comment_match": "Denies access to files and directories best kept private", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/core/ajax/update.php*", + "/cron.php*", + "/index.php*", + "/ocm-provider*.php*", + "/ocs-provider*.php*", + "/ocs/v1.php*", + "/ocs/v2.php*", + "/public.php*", + "/remote.php*", + "/status.php*", + "/updater*.php*" + ] + }, + "_comment_match": "Serves direct URIs with dedicated scripts", + "action": { + "pass": "applications/nextcloud/direct" + } + }, + { + "match": { + "uri": "/ocm-provider*" + }, + "action": { + "pass": "applications/nextcloud/ocm" + } + }, + { + "match": { + "uri": "/ocs-provider*" + }, + "action": { + "pass": "applications/nextcloud/ocs" + } + }, + { + "match": { + "uri": "/updater*" + }, + "action": { + "pass": "applications/nextcloud/updater" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves matching static files", + "fallback": { + "pass": "applications/nextcloud/index" + } + } + } + ], + + "applications": { + "nextcloud": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/" + }, + "_comment_direct": "Path to the application directory; use a real path in your configuration", + "index": { + "root": "/path/to/app/", + "script": "index.php" + }, + "_comment_index": "All requests are handled by a single script", + "ocm": { + "root": "/path/to/app/ocm-provider/", + "script": "index.php" + }, + "_comment_ocm": "All requests are handled by a single script", + "ocs": { + "root": "/path/to/app/ocs-provider/", + "script": "index.php" + }, + "_comment_ocs": "All requests are handled by a single script", + "updater": { + "root": "/path/to/app/nextcloud/updater/", + "script": "index.php" + }, + "_comment_updater": "All requests are handled by a single script" + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the URI omits it. + - Other targets specify the **script** that Unit runs for *any* URIs + the target receives. + {{< /note >}} + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + 7. Adjust Unit's **max_body_size** [option]({{< relref "/unit/configuration.md#configuration-stngs" >}}).to + avoid potential issues with large file uploads, for example, runnig the + following command as root: + + ```console + # curl -X PUT -d '{"http":{"max_body_size": 2147483648}}' --unix-socket \ + /path/to/control.unit.sock # Path to Unit's control socket in your installation + http://localhost/config/settings # Path to the 'config/settings' section in Unit's control API + ``` + + After a successful update, browse to and [set up](https://docs.nextcloud.com/server/latest/admin_manual/installation/installation_wizard.html) + your NextCloud installation: + + ![NextCloud on Unit - Home Screen](/unit/images/nextcloud.png) + diff --git a/content/unit/howto/apps/opengrok.md b/content/unit/howto/apps/opengrok.md new file mode 100644 index 000000000..08e2c716f --- /dev/null +++ b/content/unit/howto/apps/opengrok.md @@ -0,0 +1,67 @@ +--- +title: OpenGrok +toc: true +weight: 1500 +--- + +To run the [OpenGrok](https://github.com/oracle/opengrok) code search engine using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Java 11+ language module. + +2. Follow the official OpenGrok [installation guide](https://github.com/oracle/opengrok/wiki/How-to-setup-OpenGrok). Here, + we'll place the files at **/path/to/app/**: + + ```console + mkdir -p /path/to/app/{src,data,dist,etc,log} # Path to the application directory; use a real path in your configuration + ``` + + ```console + tar -C /path/to/app/dist --strip-components=1 -xzf opengrok-X.Y.Z.tar.gz # Path to the application directory; use a real path in your configuration | Specific OpenGrok version + ``` + + Our servlet container is Unit so we can repackage the **source.war** + file to an arbitrary directory at [Step 2](https://github.com/oracle/opengrok/wiki/How-to-setup-OpenGrok#step2---deploy-the-web-application): + + ```console + opengrok-deploy -c /path/to/app/etc/configuration.xml \ + /path/to/app/dist/lib/source.war /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + The resulting pathname is **/path/to/app/source.war**. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, [prepare]({{< relref "/unit/configuration.md#configuration-java" >}}) + the OpenGrok configuration for Unit: + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/opengrok" + } + }, + + "applications": { + "opengrok": { + "type": "java", + "webapp": "/path/to/app/source.war", + "_comment_webapp": "Path to the application directory; use a real path in your configuration | Repackaged in Step 2", + "options": [ + "-Djava.awt.headless=true" + ] + } + } + } + ``` + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, OpenGrok should be available on the listener’s IP + address and port: + + ![OpenGrok on Unit - Search Screen](/unit/images/opengrok.png) diff --git a/content/unit/howto/apps/phpbb.md b/content/unit/howto/apps/phpbb.md new file mode 100644 index 000000000..52ac1dbc6 --- /dev/null +++ b/content/unit/howto/apps/phpbb.md @@ -0,0 +1,121 @@ +--- +title: phpBB +toc: true +weight: 1600 +--- + +To run the [phpBB](https://www.phpbb.com) bulletin board using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure phpBB's [prerequisites](https://www.phpbb.com/support/docs/en/3.3/ug/quickstart/requirements/) + +3. Install phpBB's [core files](https://www.phpbb.com/downloads/). Here we install them at **/path/to/app**; + use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, prepare the app + [configuration]({{< relref "/unit/configuration.md#configuration-php" >}}) + for Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/cache/*", + "/common.php*", + "/config.php*", + "/config/*", + "/db/migration/data/*", + "/files/*", + "/images/avatars/upload/*", + "/includes/*", + "/store/*" + ] + }, + "_comment_match": "Denies access to files and directories best kept private", + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/", + "*.php", + "*.php/*" + ] + }, + "action": { + "pass": "applications/phpbb/direct" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves static files", + "fallback": { + "pass": "applications/phpbb/index" + }, + "_comment_fallback": "Catch-all for requests not yet served by other rules" + } + } + ], + + "applications": { + "phpbb": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/" + }, + "_comment_direct": "Path to the application directory; use a real path in your configuration", + "index": { + "root": "/path/to/app/", + "script": "app.php" + }, + "_comment_index": "Path to the application directory; use a real path in your configuration" + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the URI omits it. + - The **index** target specifies the **script** that Unit runs + for *any* URIs the target receives. + {{< /note >}} + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ![phpBB on Unit](/unit/images/phpbb.png) + + +7. Browse to **/install/app.php** to complete your installation. Having + done that, delete the **install/** subdirectory to mitigate security + risks: + + ```console + rm -rf /path/to/app/install/ # Path to the application directory; use a real path in your configuration + ``` diff --git a/content/unit/howto/apps/phpmyadmin.md b/content/unit/howto/apps/phpmyadmin.md new file mode 100644 index 000000000..bf6aedffb --- /dev/null +++ b/content/unit/howto/apps/phpmyadmin.md @@ -0,0 +1,71 @@ +--- +title: phpMyAdmin +toc: true +weight: 1700 +--- + +To run the [phpMyAdmin](https://www.phpmyadmin.net) web tool using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure phpMyAdmin's [prerequisites](https://docs.phpmyadmin.net/en/latest/require.html) + +3. Install phpMyAdmin's [core files](https://docs.phpmyadmin.net/en/latest/setup.html#quick-install-1). Here we install them at **/path/to/app**; + use a real path in your configuration. + + {{< note >}} + Make sure to create the **config.inc.php** file [manually](https://docs.phpmyadmin.net/en/latest/setup.html#manually-creating-the-file) + or using the [setup script](https://docs.phpmyadmin.net/en/latest/setup.html#using-the-setup-script). + {{< /note >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the phpMyAdmin configuration for Unit + (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "~\\.(css|gif|html?|ico|jpg|js(on)?|png|svg|ttf|woff2?)$" + }, + "_comment_match": "Enables access to static content only", + "action": { + "share": "/path/to/app$uri" + }, + "_comment_action": "Serves matching static files" + }, + { + "action": { + "pass": "applications/phpmyadmin" + } + } + ], + + "applications": { + "phpmyadmin": { + "type": "php", + "root": "/path/to/app/" + }, + "_comment_root": "Path to the application directory; use a real path in your configuration" + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, phpMyAdmin should be available on the listener’s IP + address and port: + + ![phpMyAdmin on Unit](/unit/images/phpmyadmin.png) diff --git a/content/unit/howto/apps/plone.md b/content/unit/howto/apps/plone.md new file mode 100644 index 000000000..aa1db6db6 --- /dev/null +++ b/content/unit/howto/apps/plone.md @@ -0,0 +1,124 @@ +--- +title: Plone +toc: true +weight: 1800 +--- + +To run the [Plone](https://plone.org) content management system using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Install and configure Plone's [prerequisites](https://docs.plone.org/manage/installing/requirements.html) + +3. Install Plone's [core files](https://docs.plone.org/manage/installing/installation.html). + Here, we install them at **/path/to/app/**; use a real path in your configuration: + + ```console + mkdir /tmp/plone && cd /tmp/plone/ + ``` + + ```console + wget https://launchpad.net/plone/A.B/A.B.C/+download/Plone-A.B.C-UnifiedInstaller-1.0.tgz # Plone version + ``` + + ```console + tar xzvf Plone-A.B.C-UnifiedInstaller-1.0.tgz --strip-components=1 # Plone version | Avoids creating a redundant subdirectory + ``` + + ```console + ./install.sh --target=:/path/to/app/ \ # Path to the application directory; use a real path in your configuration + --with-python=/full/path/to/python \ # Full pathname of the Python executable used to create Plone's virtual environment + standalone + ``` + + {{< note >}} + Plone's [Zope](https://plone.org/what-is-plone/zope) instance and + virtual environment are created in the **zinstance/** subdirectory; + later, the resulting path is used to configure Unit, so take care to note + it in your setup. Also, make sure the Python version specified with + `--with-python` matches the module version from Step 1. + {{< /note >}} + +4. To run Plone on Unit, add a new configuration file named + **/path/to/app/zinstance/wsgi.cfg**: + + ```cfg + [buildout] + extends = + buildout.cfg + + parts += + wsgi.py # The basename is arbitrary; the extension is required to make the resulting Python module discoverable + + [wsgi.py] + recipe = plone.recipe.zope2instance + user = admin:admin # Instance credentials; omit this line to configure them interactively + eggs = + ${instance:eggs} + scripts = + initialization = + from Zope2.Startup.run import make_wsgi_app + wsgiapp = make_wsgi_app({}, '${buildout:parts-directory}/instance/etc/zope.conf') # Path to the Zope instance's configuration + def application(*args, **kwargs):return wsgiapp(*args, **kwargs) + ``` + + It creates a new Zope instance. The part's name must end with **.py** + for the resulting instance script to be recognized as a Python module; the + **initialization** [option](https://pypi.org/project/plone.recipe.zope2instance/#common-options) + defines a WSGI entry point using **zope.conf** from the **instance** + part in **buildout.cfg**. + + Rerun Buildout, feeding it the new configuration file: + + ```console + cd /path/to/app/ # Path to the application directory; use a real path in your configurationzinstance/ + ``` + + ```console + bin/buildout -c wsgi.cfg + + ... + Installing wsgi.py. + Generated script '/path/to/app/zinstance/bin/wsgi.py'. + ``` + + Thus created, the instance script can be used with Unit. + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Plone configuration for Unit + (use real values for **path** and **home**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/plone" + } + }, + + "applications": { + "plone": { + "type": "python 3.Y", + "_comment_type": "Python executable version used to install Plone", + "path": "/path/to/app/zinstance/", + "_comment_path": "Path to the application directory; use a real path in your configuration", + "home": "/path/to/app/zinstance/", + "_comment_home": "Path to the application directory; use a real path in your configuration", + "module": "bin.wsgi", + "_comment_module": "WSGI module's qualified name with extension omitted" + } + } + } + ``` + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your Plone instance should be available on the + listener’s IP address and port: + + ![Plone on Unit - Setup Screen](/unit/images/plone.png) diff --git a/content/unit/howto/apps/redmine.md b/content/unit/howto/apps/redmine.md new file mode 100644 index 000000000..f71945e8a --- /dev/null +++ b/content/unit/howto/apps/redmine.md @@ -0,0 +1,55 @@ +--- +title: Redmine +toc: true +weight: 1900 +--- + +To run the [Redmine](https://www.redmine.org) project management system using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Ruby language module. + +2. Install and configure Redmine's [prerequisites](https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Installation-procedure) + +3. Install Redmine's [core files](https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-1-Redmine-application). + Here we install them at **/path/to/app**; use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-ruby" >}}) + the Redmine configuration for Unit (use a real value for **working_directory**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/redmine" + } + }, + + "applications": { + "redmine": { + "type": "ruby", + "working_directory": "/path/to/app/", + "_comment_working_directory": "Path to the application directory; use a real path in your configuration", + "script": "config.ru", + "_comment_script": "Entry point script name, including the file name extension", + "environment": { + "RAILS_ENV": "production" + }, + "_comment_environment": "Environment name in the Redmine configuration file" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Redmine should be available on the listener's IP + and port: + + ![Redmine on Unit - Sample Screen](/unit/images/redmine.png) diff --git a/content/unit/howto/apps/reviewboard.md b/content/unit/howto/apps/reviewboard.md new file mode 100644 index 000000000..559d7f016 --- /dev/null +++ b/content/unit/howto/apps/reviewboard.md @@ -0,0 +1,109 @@ +--- +title: Review Board +toc: true +weight: 2000 +--- + +To run the [Review Board](https://www.reviewboard.org) code review tool using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 2.7 language module. + +2. Install and configure Review Board's [prerequisites](https://www.reviewboard.org/docs/manual/dev/admin/installation/linux/#before-you-begin) + + {{< note >}} + We'll use Unit as the web server, so you can skip the corresponding step. + {{< /note >}} + +3. Install the [core files](https://www.reviewboard.org/docs/manual/dev/admin/installation/linux/#installing-review-board) + and create a [site](https://www.reviewboard.org/docs/manual/dev/admin/installation/creating-sites/). + Here, it's **/path/to/app/**; use a real path in your configuration: + + ```console + rb-site install /path/to/app/ # Path to the application directory; use a real path in your configuration + + * Welcome to the Review Board site installation wizard + + This will prepare a Review Board site installation in: + + /path/to/app + + We need to know a few things before we can prepare your site for + installation. This will only take a few minutes. + ... + ``` + +4. Add the **.py** extension to the WSGI module's name to make it + discoverable by Unit, for example: + + ```console + mv /path/to/app/htdocs/reviewboard.wsgi \ + /path/to/app/htdocs/wsgi.py # Path to the application directory; use a real path in your configuration + ``` + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + Also, make sure the following directories are [writable](https://www.reviewboard.org/docs/manual/dev/admin/installation/creating-sites/#changing-permissions): + + ```console + chmod u+w /path/to/app/htdocs/media/uploaded/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + chmod u+w /path/to/app/data/ # Path to the application directory; use a real path in your configuration + ``` + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) +the Review Board configuration for Unit (use real values for **share** and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/media/*", + "/static/*", + "/errordocs/*" + ] + }, + "_comment_match": "Static file directories", + "action": { + "share": "/path/to/app/htdocs$uri" + }, + "_comment_action": "Serves matching static files" + }, + { + "action": { + "pass": "applications/rb" + } + } + ], + + "applications": { + "rb": { + "type": "python 2", + "path": "/path/to/app/htdocs/", + "_comment_path": "Path to the application directory; use a real path in your configuration", + "module": "wsgi", + "_comment_module": "WSGI module basename with extension omitted" + } + } + } + ``` + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://www.reviewboard.org/docs/manual/dev/admin/#configuring-review-board) + your Review Board installation: + + ![Review Board on Unit - Dashboard Screen](/unit/images/reviewboard.png) diff --git a/content/unit/howto/apps/roundcube.md b/content/unit/howto/apps/roundcube.md new file mode 100644 index 000000000..58e52c77c --- /dev/null +++ b/content/unit/howto/apps/roundcube.md @@ -0,0 +1,89 @@ +--- +title: Roundcube +toc: true +weight: 2100 +--- + +To run the [Roundcube](https://roundcube.net) webmail platform using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure Roundcube's [prerequisites](https://github.com/roundcube/roundcubemail/wiki/Installation#install-dependencies) + +3. Install Roundcube's [core files](https://roundcube.net/download/). Here we install them at **/path/to/app**; + use a real path in your configuration. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Roundcube configuration for Unit + (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*/" + ] + }, + "_comment_match": "Serves direct requests for PHP scripts and directory-like URIs", + "action": { + "pass": "applications/roundcube" + } + }, + { + "action": { + "share": "/path/to/app$uri" + }, + "_comment_action": "Serves static files" + } + ], + + "applications": { + "roundcube": { + "type": "php", + "root": "/path/to/app/", + "_comment_root": "Path to the application directory; use a real path in your configuration" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://github.com/roundcube/roundcubemail/wiki/Installation#configuring-roundcube) + your Roundcube installation: + + ![Roundcube on Unit - Setup Screen](/unit/images/roundcube-setup.png) + + + +7. After installation, switch **share** and **root** to the + **public_html/** subdirectory to [protect](https://github.com/roundcube/roundcubemail/wiki/Installation#protect-your-installation) + sensitive data, run the following command as root: + + ```console + curl -X PUT -d '"/path/to/app/public_html$uri"' --unix-socket /path/to/control.unit.sock \ + http://localhost/config/routes/1/action/share + ``` + + ```console + curl -X PUT -d '"/path/to/app/public_html/"' --unix-socket /path/to/control.unit.sock \ + http://localhost/config/applications/roundcube/root + ``` + + Thus, Roundcube should be available on the listener’s IP address and port: + + ![Roundcube on Unit - Login Screen](/unit/images/roundcube.png) diff --git a/content/unit/howto/apps/trac.md b/content/unit/howto/apps/trac.md new file mode 100644 index 000000000..3403037be --- /dev/null +++ b/content/unit/howto/apps/trac.md @@ -0,0 +1,162 @@ +--- +title: Trac +toc: true +weight: 2200 +--- + +{{< warning >}} +So far, Unit doesn't support handling the **REMOTE_USER** headers +directly, so authentication should be implemented via external means. For +example, consider using [trac-oidc](https://pypi.org/project/trac-oidc/) or +[OAuth2Plugin](https://trac-hacks.org/wiki/OAuth2Plugin). +{{< /warning >}} + +To run the [Trac](https://trac.edgewall.org) issue tracking system using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 2 language module. + + {{< note >}} + As of now, Trac [doesn't fully support](https://trac.edgewall.org/ticket/12130) Python 3. Mind that Python 2 + is officially deprecated. + {{< /note >}} + +2. Prepare and activate a [virtual environment](https://virtualenv.pypa.io/en/latest/) to contain your installation + (assuming `virtualenv` is installed): + + ```console + mkdir -p /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + virtualenv venv + ``` + + ```console + source venv/bin/activate + ``` + +3. Next, [install Trac](https://trac.edgewall.org/wiki/TracInstall) and its + optional dependencies, then initialize a [Trac environment](https://trac.edgewall.org/wiki/TracEnvironment) and deploy static files: + + ```console + pip install Trac + ``` + + ```console + pip install babel docutils genshi \ + pygments pytz textile # optional dependencies + ``` + + ```console + mkdir static/ # Arbitrary directory name, will store Trac's /chrome/ tree + ``` + + ```console + mkdir trac_env/ # Arbitrary directory name + ``` + + ```console + trac-admin trac_env/ initenv # initialize Trac environment + ``` + + ```console + trac-admin trac_env/ deploy static/ # extract Trac's static files + ``` + + ```console + mv static/htdocs static/chrome # align static file paths + ``` + + ```console + rm -rf static/cgi-bin/ # remove unneeded files + ``` + + ```console + deactivate + ``` + +4. Unit [uses WSGI]({{< relref "/unit/configuration.md#configuration-python" >}}) + to run Python apps, so a + [wrapper](https://trac.edgewall.org/wiki/1.3/TracModWSGI#Averybasicscript) + script is required to run Trac as a Unit app; let's save it as + **/path/to/app/trac_wsgi.py**. Here, the **application** callable + serves as the entry point for the app: + + ```python + import trac.web.main + + def application(environ, start_response): + environ["trac.locale"] = "en_US.UTF8" + return trac.web.main.dispatch_request(environ, start_response) + ``` + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Trac configuration for Unit + (use real values for **share**, **path**, **home**, + **module**, **TRAC_ENV**, and **PYTHON_EGG_CACHE**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/chrome/*" + }, + "action": { + "share": "/path/to/app/static$uri" + }, + "_comment_action": "Serves matching static files | Path to the static files; use a real path in your configuration" + }, + { + "action": { + "pass": "applications/trac" + } + } + ], + + "applications": { + "trac": { + "type": "python 2", + "path": "/path/to/app/", + "_comment_path": "Path to the application directory; use a real path in your configuration", + "home": "/path/to/app/venv/", + "_comment_home": "Path to the application directory; use a real path in your configuration", + "module": "trac_wsgi", + "_comment_module": "WSGI module basename from Step 4 with extension omitted", + "environment": { + "TRAC_ENV": "/path/to/app/trac_env/", + "_comment_TRAC_ENV": "Path to the Trac environment; use a real path in your configuration", + "PYTHON_EGG_CACHE": "/path/to/app/trac_env/eggs/" + }, + "_comment_PYTHON_EGG_CACHE": "Path to the Python egg cache for Trac; use a real path in your configuration" + } + } + } + ``` + + The route serves requests for static files in Trac's **/chrome/** + [hierarchy](https://trac.edgewall.org/wiki/TracDev/TracURLs) from the + **static/** directory. + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, Trac should be available on the listener’s IP + address and port: + + ![Trac on Unit - New Ticket Screen](/unit/images/trac.png) diff --git a/content/unit/howto/apps/wordpress.md b/content/unit/howto/apps/wordpress.md new file mode 100644 index 000000000..8651167af --- /dev/null +++ b/content/unit/howto/apps/wordpress.md @@ -0,0 +1,102 @@ +--- +title: WordPress +toc: true +weight: 2300 +--- + +{{< note >}} +For a more specific walkthrough that includes SSL setup and NGINX as a +proxy, see our [blog post](https://www.nginx.com/blog/automating-installation-wordpress-with-nginx-unit-on-ubuntu/). +{{< /note >}} + +To run the [WordPress](https://wordpress.org) content management system +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 7.3+ language module. + +2. Install and configure WordPress's [prerequisites](https://wordpress.org/support/article/before-you-install/) + +3. Install WordPress's [core files](https://wordpress.org/download/). Here we install them at **/path/to/app**; + use a real path in your configuration. + +4. Update the **wp-config.php** [file](https://wordpress.org/support/article/editing-wp-config-php/) with your + database settings and other customizations. + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the WordPress configuration for Unit + (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*", + "/wp-admin/" + ] + }, + "action": { + "pass": "applications/wordpress/direct" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "fallback": { + "pass": "applications/wordpress/index" + } + } + } + ], + + "applications": { + "wordpress": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/" + }, + "index": { + "root": "/path/to/app/", + "script": "index.php" + } + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the URI omits it. + - The **index** target specifies the **script** that Unit runs + for *any* URIs the target receives. + {{< /note >}} + +7. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://wordpress.org/support/article/how-to-install-wordpress/#step-5-run-the-install-script) + your WordPress installation: + + ![WordPress on Unit - Setup Screen](/unit/images/wordpress.png) + + {{< note >}} + The resulting URI scheme will affect your WordPress configuration; updates + may require [extra steps](https://wordpress.org/support/article/changing-the-site-url/). + {{< /note >}} diff --git a/content/unit/howto/certbot.md b/content/unit/howto/certbot.md new file mode 100644 index 000000000..cf24d8444 --- /dev/null +++ b/content/unit/howto/certbot.md @@ -0,0 +1,283 @@ +--- +title: TLS with Certbot +toc: true +weight: 600 +--- + +To set up +[SSL/TLS access in Unit]({{< relref "/unit/certificates.md#configuration-ssl" >}}), +you need certificate bundles. Although you can use self-signed certificates, it's +advisable to obtain certificates for your website from a certificate authority +(CA). For this purpose, you may employ EFF's [Certbot](https://certbot.eff.org) that issues free certificates signed by [Let's +Encrypt](https://letsencrypt.org), a non-profit CA. + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Generating certificates + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) on your website's server. + +2. Install [Certbot](https://certbot.eff.org/instructions) on the same + server, choosing `None of the above` in the `Software` + dropdown list and the server's OS in the `System` dropdown list + at EFF's website. + +3. Run the `certbot` utility and follow its instructions to create the + certificate bundle. You'll be prompted to enter the domain name of the + website and [validate domain ownership](https://letsencrypt.org/docs/challenge-types/); the latter can be done + differently. Perhaps, the easiest approach is to use the [webroot](https://eff-certbot.readthedocs.io/en/stable/using.html#webroot) method + by having Certbot store a certain file locally and then access it by your + domain name. First, configure Unit with a temporary route at port 80: + + ```json + { + "listeners": { + "*:80": { + "pass": "routes/acme", + "comment_*:80": "Certbot attempts to reach the domain name at port 80" + } + }, + "routes": { + "acme": [ + { + "match": { + "uri": "/.well-known/acme-challenge/*", + "comment_uri": "The URI that Certbot probes to download the file" + }, + "action": { + "share": "/var/www/www.example.com$uri/", + "comment_share": "Arbitrary directory, preferably the one used for storing static files" + } + } + ] + } + } + ``` + + Make sure the **share** directory is accessible for Unit's + [router process]({{< relref "/unit/howto/security.md#security-apps" >}}) + user account, usually **unit:unit**. + + Next, run `certbot`, supplying the **share** directory as the + webroot path: + + ```console + # certbot certonly --webroot -w /var/www/www.example.com/ -d www.example.com # path where the file is stored and your domain name + ``` + + If you can't employ the previous method for some reason, try using DNS + records to validate your domain: + + ```console + # certbot certonly --manual --preferred-challenges dns -d www.example.com # your domain name + ``` + + Certbot will provide instructions on updating the DNS entries to prove + domain ownership. + + Any such `certbot` command stores the resulting **.pem** files + as follows: + + ```none + /etc/letsencrypt/ # Location can be configured, see Certbot help + └── live/ + └── www.example.com # Your website name + ├── cert.pem # Leaf website certificate + ├── chain.pem # Root CA certificate chain + ├── fullchain.pem # Concatenation of the two PEMs above + └── privkey.pem # Your private key, must be kept secret + + ``` + + {{< note >}} + Certbot offers other validation methods ([authenticators](https://eff-certbot.readthedocs.io/en/stable/using.html#getting-certificates-and-choosing-plugins)) + as well, but they're omitted here for brevity. + {{< /note >}} + +4. Create a certificate bundle fit for Unit and upload it to the + **certificates** section of Unit's + [control API]({{< relref "/unit/controlapi.md#configuration-api" >}}): + + ```console + # cat /etc/letsencrypt/live/www.example.com/fullchain.pem \ + /etc/letsencrypt/live/www.example.com/privkey.pem > bundle1.pem # Arbitrary certificate bundle's filename + ``` + + ```console + # curl -X PUT --data-binary @bundle1.pem \ # Certificate bundle's filename + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/certbot1 # Certificate bundle name in Unit's configuration + + { + "success": "Certificate chain uploaded." + } + ``` + +5. Create or update a [listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}) to use the + uploaded bundle in Unit: + + ```console + # curl -X PUT --data-binary \ + '{"pass": "applications/ssl_app", "tls": {"certificate": "certbot1"}}' \ # Certificate bundle name in Unit's configuration + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:443' # Listener's name in Unit's configuration + + ``` + +6. Try accessing your website via HTTPS: + + ```console + $ curl https://www.example.com -v + + ... + * TLSv1.3 (OUT), TLS handshake, Client hello (1): + * TLSv1.3 (IN), TLS handshake, Server hello (2): + * TLSv1.3 (IN), TLS Unknown, Certificate Status (22): + * TLSv1.3 (IN), TLS handshake, Unknown (8): + * TLSv1.3 (IN), TLS Unknown, Certificate Status (22): + * TLSv1.3 (IN), TLS handshake, Certificate (11): + * TLSv1.3 (IN), TLS Unknown, Certificate Status (22): + * TLSv1.3 (IN), TLS handshake, CERT verify (15): + * TLSv1.3 (IN), TLS Unknown, Certificate Status (22): + * TLSv1.3 (IN), TLS handshake, Finished (20): + * TLSv1.3 (OUT), TLS change cipher, Client hello (1): + * TLSv1.3 (OUT), TLS Unknown, Certificate Status (22): + * TLSv1.3 (OUT), TLS handshake, Finished (20): + * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 + * ALPN, server did not agree to a protocol + * Server certificate: + * subject: CN=www.example.com + * start date: Sep 21 22:10:42 2020 GMT + * expire date: Dec 20 22:10:42 2020 GMT + ... + ``` + +## Renewing certificates + +Certbot enables renewing the certificates [manually](https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates) +or [automatically](https://eff-certbot.readthedocs.io/en/stable/using.html#automated-renewals). +For manual renewal and rollover: + +1. Repeat the preceding steps to renew the certificates and upload the new + bundle under a different name: + + ```console + # certbot certonly --standalone + + What would you like to do? + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 1: Keep the existing certificate for now + 2: Renew & replace the cert (may be subject to CA rate limits) + ``` + + ```console + # cat /etc/letsencrypt/live/www.example.com/fullchain.pem \ + /etc/letsencrypt/live/www.example.com/privkey.pem > bundle2.pem # Arbitrary certificate bundle's filename + ``` + + ```console + # curl -X PUT --data-binary @bundle2.pem \ # Certificate bundle's filename + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/certbot2 # Certificate bundle name in Unit's configuration + + { + "success": "Certificate chain uploaded." + } + ``` + + Now you have two certificate bundles uploaded; Unit knows them as + **certbot1** and **certbot2**. Optionally, query the + **certificates** section to review common details such as expiry dates, + subjects, or issuers: + + ```console + # curl --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates + ``` + +2. Update the [listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}), switching it to the + renewed certificate bundle: + + ```console + # curl -X PUT --data-binary 'certbot2' \ # New certificate bundle name in Unit's configuration + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:443/tls/certificate' # Listener's name in Unit's configuration + ``` + + {{< note >}} + There's no need to shut Unit down; your server can stay online during the + rollover. + {{< /note >}} + +3. Delete the expired bundle: + + ```console + # curl -X DELETE --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/certificates/certbot1' # Old certificate bundle name in Unit's configuration + + { + "success": "Certificate deleted." + } + ``` + +4. You can also make use of Unit's + [SNI]({{< relref "/unit/configuration.md#configuration-listeners" >}}) + support by configuring several certificate bundles for a listener. + + Suppose you've successfully used Certbot to obtain Let's Encrypt + certificates for two domains, **www.example.com** and + **cdn.example.com**. First, upload them to Unit using the same steps as + earlier: + + ```console + # cat /etc/letsencrypt/live/cdn.example.com/fullchain.pem \ + /etc/letsencrypt/live/cdn.example.com/privkey.pem > cdn.example.com.pem # Arbitrary certificate bundle's filename + ``` + + ```console + # cat /etc/letsencrypt/live/www.example.com/fullchain.pem \ + /etc/letsencrypt/live/www.example.com/privkey.pem > www.example.com.pem # Arbitrary certificate bundle's filename + ``` + + ```console + # curl -X PUT --data-binary @cdn.example.com.pem \ # Certificate bundle's filename + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/cdn.example.com # Certificate bundle name in Unit's configuration + + { + "success": "Certificate chain uploaded." + } + ``` + + ```console + # curl -X PUT --data-binary @www.example.com.pem \ # Certificate bundle's filename + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + http://localhost/certificates/www.example.com # Certificate bundle name in Unit's configuration + + { + "success": "Certificate chain uploaded." + } + ``` + + Next, configure the listener, supplying both bundles as an array value for + the **tls/certificate** option: + + ```console + # curl -X PUT --data-binary '{"certificate": ["cdn.example.com", "www.example.com"]}' \ # Certificate bundle names in Unit's configuration + --unix-socket /path/to/control.unit.sock \ # Path to Unit's control socket in your installation + 'http://localhost/config/listeners/*:443/tls' # Listener's name in Unit's configuration + + ``` + + Unit does the rest of the job, automatically figuring out which bundle to + produce for each incoming connection to both domain names. + +{{< note >}} +Currently, Certbot doesn't have [installer plugins](https://eff-certbot.readthedocs.io/en/stable/using.html#getting-certificates-and-choosing-plugins) +that enable automatic certificate rollover in Unit. However, you can set up +Certbot's [hooks](https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates) +using the commands listed here to the same effect. +{{< /note >}} diff --git a/content/unit/howto/docker.md b/content/unit/howto/docker.md new file mode 100644 index 000000000..c19019ecc --- /dev/null +++ b/content/unit/howto/docker.md @@ -0,0 +1,468 @@ +--- +title: Unit in Docker +weight: 200 +toc: true +--- + +To run your apps in a containerized Unit using the +[images we provide]({{< relref "/unit/installation.md#installation-docker" >}}), +you need at least to: + +- Mount your application files to a directory in your container. +- Publish Unit's listener port to the host machine. + +For example: + +```console + $ export UNIT=$( \ + docker run -d --mount type=bind,src="$(pwd)",dst=/www \ + -p 8080:8000 unit:{{< param "unitversion" >}}-python3.11 \ + ) +``` + +The command mounts the host's current directory where your app files are stored +to the container's **/www/** directory and publishes the container's port +**8000** that the listener will use as port **8080** on the host, +saving the container's ID in the `UNIT` environment variable. + +Next, upload a configuration to Unit via the control socket: + +```console +$ docker exec -ti $UNIT curl -X PUT --data-binary @/www/config.json \ + --unix-socket /var/run/control.unit.sock \ # Socket path inside the container + http://localhost/config +``` + +This command assumes your configuration is stored as **config.json** in the +container-mounted directory on the host; if the file defines a listener on port +**8000**, your app is now accessible on port **8080** of the host. For +details of the Unit configuration, see +[Configuration]({{< relref "/unit/controlapi.md#configuration-api" >}}). + +{{< note >}} +For app containerization examples, refer to our sample [ Go](/unit/downloads/Dockerfile.go.txt), +[ Java](/unit/downloads/Dockerfile.java.txt), +[ Node.js](/unit/downloads/Dockerfile.nodejs.txt), +[ Perl](/unit/downloads/Dockerfile.perl.txt), +[ PHP](/unit/downloads/Dockerfile.php.txt), +[ Python](/unit/downloads/Dockerfile.python.txt), +and [ Ruby](/unit/downloads/Dockerfile.ruby.txt) Dockerfiles; +also, see a more elaborate discussion +[below]({{< relref "/unit/howto/docker.md#docker-apps" >}}) +{{< /note >}} + +Now for a few detailed scenarios. + +## Apps in a containerized Unit {#docker-apps-containerized-unit} + +Suppose we have a web app with a few dependencies, say +[Flask`s]({{< relref "/unit/howto/frameworks/flask.md" >}}) official **hello, world** app: + +```console +$ cd /path/to/app/ # Directory where all app-related files will be stored; use a real path in your configuration +``` + +```console +$ mkdir webapp +``` + +```console +$ cat << EOF > webapp/wsgi.py + +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello_world(): + return 'Hello, World!' +EOF +``` + +However basic it is, there's already a dependency, so let's list it in a file +called **requirements.txt**: + +```console +$ cat << EOF > requirements.txt + +flask +EOF +``` + +Next, create a simple Unit +[configuration]({{< relref "/unit/configuration.md#configuration-python" >}}) +file for the app: + +```console +$ mkdir config +``` + +```console +$ cat << EOF > config/config.json + +{ + "listeners": { + "*:8000": { + "pass": "applications/webapp" + } + }, + + "applications": { + "webapp": { + "type": "python 3", + "path": "/www", # Directory inside the container where the app files will be stored + "module": "wsgi", # WSGI module basename with extension omitted + "callable": "app" # Name of the callable in the module to run + } + } +} +EOF + +``` + +Finally, let's create **log/** and **state/** directories to store Unit +[log and state]({{< relref "/unit/howto/source.md#source-startup" >}}) + +```console +$ mkdir log +``` + +```console +$ touch log/unit.log +``` + +```console +$ mkdir state +``` + +Our file structure so far: + +```none +/path/to/app # Directory where all app-related files are stored; use a real path in your configuration +├── config +│ └── config.json +├── log +│ └── unit.log +├── requirements.txt +├── state +└── webapp + └── wsgi.py +``` + +Everything is ready for a containerized Unit. First, let's create a +**Dockerfile** to install app prerequisites: + +```dockerfile +FROM unit:{{< param "unitversion" >}}-python3.11 +COPY requirements.txt /config/requirements.txt +RUN python3 -m pip install -r /config/requirements.txt +``` + +```console +$ docker build --tag=unit-webapp . # Arbitrary image tag +``` + +Next, we start a container and map it to our directory structure: + +```console +$ export UNIT=$( \ + docker run -d \ + --mount type=bind,src="$(pwd)/config/",dst=/docker-entrypoint.d/ \ + --mount type=bind,src="$(pwd)/log/unit.log",dst=/var/log/unit.log \ + --mount type=bind,src="$(pwd)/state",dst=/var/lib/unit \ + --mount type=bind,src="$(pwd)/webapp",dst=/www \ + -p 8080:8000 unit-webapp \ + ) +``` + +{{< note >}} +With this mapping, Unit stores its state and log in your file structure. By +default, our Docker images forward their log output to the [Docker log +collector](https://docs.docker.com/config/containers/logging/). +{{< /note >}} + +We've mapped the source **config/** to **/docker-entrypoint.d/** in the +container; the official image +[uploads]({{< relref "/unit/installation.md#installation-docker-init" >}}) +any **.json** files found there into Unit's **config** section if the +state is empty. Now we can test the app: + +```console +$ curl -X GET localhost:8080 + + Hello, World! +``` + +To relocate the app in your file system, you only need to move the file +structure: + +```console +$ mv /path/to/app/ # Directory where all app-related files are stored + /new/path/to/app/ # New directory; use a real path in your configuration +``` + +To switch your app to a different Unit image, prepare a corresponding +**Dockerfile** first: + +```dockerfile +FROM unit:{{< param "unitversion" >}}-minimal +COPY requirements.txt /config/requirements.txt +# This time, we took a minimal Unit image to install a vanilla Python 3.9 +# module, run PIP, and perform cleanup just like we did earlier. + +# First, we install the required tooling and add Unit's repo. +RUN apt update && apt install -y curl apt-transport-https gnupg2 lsb-release \ + && curl -o /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] \ + https://packages.nginx.org/unit/debian/ `lsb_release -cs` unit" \ + > /etc/apt/sources.list.d/unit.list + +# Next, we install the module, download app requirements, and perform cleanup. +RUN apt update && apt install -y unit-python3.9 python3-pip \ + && python3 -m pip install -r /config/requirements.txt \ + && apt remove -y curl apt-transport-https gnupg2 lsb-release python3-pip \ + && apt autoremove --purge -y \ + && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list +``` + +```console +$ docker build --tag=unit-pruned-webapp . +``` + +Run a container from the new image; Unit picks up the mapped state +automatically: + +```console +$ export UNIT=$( \ + docker run -d \ + --mount type=bind,src="$(pwd)/log/unit.log",dst=/var/log/unit.log \ + --mount type=bind,src="$(pwd)/state",dst=/var/lib/unit \ + --mount type=bind,src="$(pwd)/webapp",dst=/www \ + -p 8080:8000 unit-pruned-webapp \ + ) +``` + + +## Containerized apps {#docker-apps} + +Suppose you have a Unit-ready +[Express]({{< relref "/unit/howto/frameworks/express.md" >}}) +app, stored in the **myapp/** directory as **app.js**: + +```javascript +#!/usr/bin/env node + +const http = require('http') +const express = require('express') +const app = express() + +app.get('/', (req, res) => res.send('Hello, Unit!')) +http.createServer(app).listen() +``` + +Its Unit configuration, stored as **config.json** in the same directory: + +```json +{ + "listeners": { + "*:8080": { + "pass": "applications/express" + } + }, + + "applications": { + "express": { + "type": "external", + "working_directory": "/www/", + "comment_working_directory": "Directory inside the container where the app files will be stored", + "executable": "/usr/bin/env", + "comment_executable": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "app.js" + ], + "comment_arguments": "The env executable runs Node.js, supplying Unit's loader module and your app code as arguments", + "comment_app.js": "Basename of the application file; be sure to make it executable" + } + } +} +``` + +The resulting file structure: + +```none +myapp/ +├── app.js +└── config.json +``` + +{{< note >}} +Don't forget to `chmod +x` the **app.js** file so Unit can run it. +{{< /note >}} + +Let's prepare a **Dockerfile** to install and configure the app in an +image: + +```dockerfile +# Keep our base image as specific as possible. +FROM unit:{{< param "unitversion" >}}-node15 + +# Same as "working_directory" in config.json. +COPY myapp/app.js /www/ + +# Install and link Express in the app directory. +RUN cd /www && npm install express && npm link unit-http + +# Port used by the listener in config.json. +EXPOSE 8080 +``` + +When you start a container based on this image, mount the **config.json** file to +[initialize]({{< relref "/unit/installation.md#installation-docker-init" >}}) +Unit's state: + +```console +$ docker build --tag=unit-expressapp . # Arbitrary image tag +``` + +```console +$ export UNIT=$( \ + docker run -d \ + --mount type=bind,src="$(pwd)/myapp/config.json",dst=/docker-entrypoint.d/config.json \ + -p 8080:8080 unit-expressapp \ + ) +``` + +```console +$ curl -X GET localhost:8080 + + Hello, Unit! +``` + +{{< note >}} +This mechanism allows to initialize Unit at container startup only if its +state is empty; otherwise, the contents of **/docker-entrypoint.d/** is +ignored. Continuing the previous sample: + +```console +$ docker commit $UNIT unit-expressapp # Store a non-empty Unit state in the image. + +# cat << EOF > myapp/new-config.json # Let's attempt re-initialization. + ... + EOF + +$ export UNIT=$( \ + docker run -d \ + --mount type=bind,src="$(pwd)/myapp/new-config.json",dst=/docker-entrypoint.d/new-config.json \ + -p 8080:8080 unit-expressapp \ + ) +``` + +Here, Unit *does not* pick up the **new-config.json** from the +**/docker-entrypoint.d/** directory when we run a container from the +updated image because Unit's state was initialized and saved earlier. +{{< /note >}} + +To configure the app after startup, supply a file or an explicit snippet via +the [control API]({{< relref "/unit/controlapi.md" >}}): + +```console +$ cat << EOF > myapp/new-config.json + ... + EOF +``` + +```console +$ export UNIT=$( \ + docker run -d \ + --mount type=bind,src="$(pwd)/myapp/new-config.json",dst=/cfg/new-config.json \ + unit-expressapp \ + ) +``` + +```console +$ docker exec -ti $UNIT curl -X PUT --data-binary @/cfg/new-config.json \ + --unix-socket /var/run/control.unit.sock \ + http://localhost/config +``` + +```console +$ docker exec -ti $UNIT curl -X PUT -d '"/www/newapp/"' \ + --unix-socket /var/run/control.unit.sock \ + http://localhost/config/applications/express/working_directory +``` + +This approach is applicable to any Unit-supported apps with external +dependencies. + +## Multilanguage images {#docker-multi} + +Earlier, Unit had a **-full** Docker image with modules for all supported +languages, but it was discontinued with version 1.22.0. If you still need a +multilanguage image, use the following **Dockerfile** template that starts +with the minimal Unit image based on +[Debian 11]({{< relref "/unit/installation.md#installation-debian-11" >}}): +and installs official language module packages: + +```dockerfile +{ + "listeners": { + "*:8080": { + "pass": "applications/express" + } + }, + + "applications": { + "express": { + "type": "external", + "working_directory": "/www/", + "comment_working_directory": "Directory inside the container where the app files will be stored", + "executable": "/usr/bin/env", + "comment_executable": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "app.js" + ], + "comment_arguments": "The env executable runs Node.js, supplying Unit's loader module and your app code as arguments", + "comment_last_argument": "Basename of the application file; be sure to make it executable" + } + } +} +``` + +Instead of packages, you can build custom +[modules]({{< relref "/unit/howto/source.md#source-modules" >}}); +use these **Dockerfile.\*** [templates](https://github.com/nginx/unit/tree/master/pkg/docker) as reference. + +## Startup customization {#docker-startup} + +Finally, you can customize the way Unit starts in a container by adding a new +Dockerfile layer: + +```dockerfile +FROM unit:{{< param "unitversion" >}}-minimal + +CMD ["unitd-debug","--no-daemon","--control","unix:/var/run/control.unit.sock"] +``` + +The **CMD** instruction above replaces the default `unitd` +executable with its debug version. Use Unit's +[command-line options]({{< relref "/unit/howto/source.md#source-startup" >}}) +to alter its startup behavior, for example: + +```dockerfile +FROM unit:{{< param "unitversion" >}}-minimal + +CMD ["unitd","--no-daemon","--control","0.0.0.0:8080"] +``` + +This replaces Unit's default UNIX domain control socket with an IP socket +address. diff --git a/content/unit/howto/frameworks/_index.md b/content/unit/howto/frameworks/_index.md new file mode 100644 index 000000000..0e70cc70f --- /dev/null +++ b/content/unit/howto/frameworks/_index.md @@ -0,0 +1,4 @@ +--- +title: Frameworks +weight: 1000 +--- \ No newline at end of file diff --git a/content/unit/howto/frameworks/bottle.md b/content/unit/howto/frameworks/bottle.md new file mode 100644 index 000000000..98088a2de --- /dev/null +++ b/content/unit/howto/frameworks/bottle.md @@ -0,0 +1,93 @@ +--- +title: Bottle +weight: 100 +toc: true +--- + +To run apps built with the [Bottle](https://bottlepy.org/docs/dev/) web +framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 2.7+ language module. + +2. Create a virtual environment to install Bottle's +[PIP package](https://bottlepy.org/docs/dev/tutorial.html#installation), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install bottle + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try an updated version of the [quickstart app](https://bottlepy.org/docs/dev/tutorial.html#the-default-application), + saving it as **/path/to/app/wsgi.py**: + + ```python + from bottle import Bottle, template + + app = Bottle() # Callable name used in Unit's configuration + + @app.route('/hello/') + def hello(name): + return template('Hello, {{name}}!', name=name) + + # run(app, host='localhost', port=8080) + ``` + + Note that we've dropped the server code. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Bottle configuration for + Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/bottle" + } + }, + "applications": { + "bottle": { + "type": "python X.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the WSGI module; use a real path in your configuration", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any; use a real path in your configuration", + "module": "wsgi", + "module_comment": "WSGI module basename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost/hello/Unit + + Hello, Unit! + ``` diff --git a/content/unit/howto/frameworks/cakephp.md b/content/unit/howto/frameworks/cakephp.md new file mode 100644 index 000000000..af88812b3 --- /dev/null +++ b/content/unit/howto/frameworks/cakephp.md @@ -0,0 +1,106 @@ +--- +title: CakePHP +weight: 200 +toc: true +--- + +To run apps based on the [CakePHP](https://cakephp.org) framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP 7.2+ language module. + +2. [Install](https://book.cakephp.org/4/en/installation.html) CakePHP and + create or deploy your app. Here, we use CakePHP's [basic template](https://book.cakephp.org/4/en/installation.html#create-a-cakephp-project) + and Composer: + + ```console + $ cd /path/to/ # Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ composer create-project --prefer-dist cakephp/app:4.* app # Arbitrary app name; becomes the application directory name + + ``` + + This creates the app's directory tree at **/path/to/app/**. Its + **webroot/** subdirectory contains both the root **index.php** and + the static files; if your app requires additional **.php** scripts, also + store them here. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, prepare the app [configuration]({{< relref "/unit/configuration.md#configuration-php" >}}) + for Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*" + ], + "uri_comment": "The second element '*.php/*' handles all requests that explicitly target PHP scripts" + }, + "action": { + "pass": "applications/cakephp/direct" + } + }, + { + "action": { + "share": "/path/to/app/webroot$uri", + "share_comment": "Unconditionally serves remaining requests that target static files", + "fallback": { + "pass": "applications/cakephp/index", + "fallback_comment": "Serves any requests not served with the 'share' immediately above" + } + } + } + ], + "applications": { + "cakephp": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/webroot/", + "root_comment": "Path to the webroot/ directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/webroot/", + "root_comment": "Path to the webroot/ directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the URI omits it. + - The **index** target specifies the **script** that Unit runs + for *any* URIs the target receives. + {{< /note >}} + + For a detailed discussion, see [Fire It Up](https://book.cakephp.org/4/en/installation.html#fire-it-up) in CakePHP docs. + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ![CakePHP Basic Template App on Unit](/unit/images/cakephp.png) diff --git a/content/unit/howto/frameworks/catalyst.md b/content/unit/howto/frameworks/catalyst.md new file mode 100644 index 000000000..987499b08 --- /dev/null +++ b/content/unit/howto/frameworks/catalyst.md @@ -0,0 +1,76 @@ +--- +title: Catalyst +weight: 300 +toc: true +--- + +To run apps based on the [Catalyst](https://metacpan.org/dist/Catalyst-Manual) 5.9+ framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Perl language module. + +2. Install Catalyst's [core files](https://metacpan.org/dist/Catalyst-Manual/view/lib/Catalyst/Manual/Intro.pod#Install). + +3. [Create](https://metacpan.org/dist/Catalyst-Manual/view/lib/Catalyst/Manual/Tutorial/02_CatalystBasics.pod#CREATE-A-CATALYST-PROJECT) + a Catalyst app. Here, let's store it at **/path/to/app/**: + + ```console + $ cd /path/to/ # Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ catalyst.pl app # Arbitrary app name; becomes the application directory name + ``` + + ```console + $ cd app + ``` + + ```console + $ perl Makefile.PL + ``` + + Make sure the app's **.psgi** file includes the **lib/** + directory: + + ```perl + use lib 'lib'; + use app; + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, + [prepare]({{< ref "/unit/configuration.md#configuration-perl" >}}) + the Catalyst configuration for Unit + (use real values for **script** and **working_directory**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/catalyst" + } + }, + + "applications": { + "catalyst": { + "type": "perl", + "working_directory": "/path/to/app/", + "_comment_working_directory": "Needed to use modules from the local lib directory; use a real path in your configuration", + "script": "/path/to/app/app.psgi", + "_comment_script": "Path to the application directory; use a real path in your configuration" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ![Catalyst Basic Template App on Unit](/unit/images/catalyst.png) diff --git a/content/unit/howto/frameworks/codeigniter.md b/content/unit/howto/frameworks/codeigniter.md new file mode 100644 index 000000000..d577522c6 --- /dev/null +++ b/content/unit/howto/frameworks/codeigniter.md @@ -0,0 +1,69 @@ +--- +title: CodeIgniter +weight: 400 +toc: true +--- + +To run apps built with the [CodeIgniter](https://codeigniter.com) web +framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Download CodeIgniter's [core files](https://codeigniter.com/user_guide/installation/index.html) and [build](https://codeigniter.com/user_guide/tutorial/index.html) your application. + Here, let's use a [basic app template](https://forum.codeigniter.com/thread-73103.html), installing it at + **/path/to/app/**. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +4. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) + the CodeIgniter configuration for Unit: + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "!/index.php" + }, + "_comment_match": "Denies access to index.php as a static file", + + "action": { + "share": "/path/to/app/public$uri", + "_comment_share": "Path to the application directory; use a real path in your configuration", + "fallback": { + "pass": "applications/codeigniter" + }, + "_comment_fallback": "Serves any requests not served with the 'share' immediately above" + } + } + ], + + "applications": { + "codeigniter": { + "type": "php", + "root": "/path/to/app/public/", + "_comment_root": "Path to the application directory; use a real path in your configuration", + "script": "index.php" + }, + "_comment_script": "All requests are served by a single script" + } + } + ``` + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ![CodeIgniter Sample App on Unit](/unit/images/codeigniter.png) diff --git a/content/unit/howto/frameworks/django.md b/content/unit/howto/frameworks/django.md new file mode 100644 index 000000000..896abe266 --- /dev/null +++ b/content/unit/howto/frameworks/django.md @@ -0,0 +1,176 @@ +--- +title: Django +toc: true +weight: 500 +--- + +To run apps based on the Django [framework](https://www.djangoproject.com) +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3 language module. + +2. Install and configure the Django [framework](https://www.djangoproject.com). The official docs [recommend](https://docs.djangoproject.com/en/stable/topics/install/#installing-an-official-release-with-pip) + setting up a virtual environment; if you do, list it as **home** when + configuring Unit later. Here, it's **/path/to/venv/**. + +3. Create a Django [project](https://docs.djangoproject.com/en/stable/intro/tutorial01/). Here, we + install it at **/path/to/app/**; use a real path in your configuration. + The following steps assume your project uses [basic directory structure](https://docs.djangoproject.com/en/stable/ref/django-admin/#django-admin-startproject): + + ```none + /path/to/app/ # Project directory + |-- manage.py + |-- django_app1/ # Individual app directory + | |-- ... + |-- django_app2/ # Individual app directory + | |-- ... + |-- project/ # Project subdirectory + | |-- ... + | |-- asgi.py # ASGI application module + | `-- wsgi.py # + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, prepare the Django [configuration]({{< relref "/unit/configuration.md#configuration-python" >}}) + for Unit. Here, the **/path/to/app/** directory is stored in the + **path** option; the virtual environment is **home**; the WSGI or + ASGI module in the **project/** subdirectory is + [imported](https://docs.python.org/3/reference/import.html) via **module**. + If you reorder your directories, + [set up]({{< relref "/unit/configuration.md#configuration-python" >}}) + **path**, **home**, and **module** accordingly. + + You can also set up some environment variables that your project relies on, + using the **environment** option. Finally, if your project uses Django's + [static files](https://docs.djangoproject.com/en/stable/howto/static-files/), + optionally add a + [route]({{< relref "/unit/configuration.md#configuration-routes" >}}) to + [serve]({{< relref "/unit/configuration.md#configuration-static" >}}) them with Unit. + + + Here's an example (use real values for **share**, **path**, + **environment**, **module**, and **home**): + + {{< tabs "interface" >}} + {{% tab name="WSGI" %}} + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/static/*" + }, + + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Thus, URIs starting with /static/ are served from /path/to/app/static/" + } + }, + { + "action": { + "pass": "applications/django" + } + } + ], + + "applications": { + "django": { + "type": "python 3.X", + "_comment_type": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "_comment_path": "Project directory; use a real path in your configuration", + "home": "/path/to/venv/", + "_comment_home": "Virtual environment directory; use a real path in your configuration", + "module": "project.wsgi", + "_comment_module": "Note the qualified name of the WSGI module; use a real project directory name in your configuration", + "environment": { + "_comment_environment": "App-specific environment variables", + "DJANGO_SETTINGS_MODULE": "project.settings", + "DB_ENGINE": "django.db.backends.postgresql", + "DB_NAME": "project", + "DB_HOST": "127.0.0.1", + "DB_PORT": "5432" + } + } + } + } + ``` + + {{% /tab %}} + {{% tab name="ASGI" %}} + + {{< note >}} + ASGI requires Python 3.5+ and Django 3.0+. + {{< /note >}} + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/static/*" + }, + + "action": { + "share": "/path/to/app$uri", + "_comment_share": "Serves static files. URIs starting with /static/ are served from /path/to/app/static/" + } + }, + { + "action": { + "pass": "applications/django" + } + } + ], + + "applications": { + "django": { + "type": "python 3.X", + "_comment_type": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "_comment_path": "Project directory; use a real path in your configuration", + "home": "/path/to/venv/", + "_comment_home": "Virtual environment directory; use a real path in your configuration", + "module": "project.asgi", + "_comment_module": "Note the qualified name of the ASGI module; use a real project directory name in your configuration", + "environment": { + "DJANGO_SETTINGS_MODULE": "project.settings", + "DB_ENGINE": "django.db.backends.postgresql", + "DB_NAME": "project", + "DB_HOST": "127.0.0.1", + "DB_PORT": "5432" + }, + "_comment_environment": "App-specific environment variables" + } + } + } + ``` + + {{% /tab %}} + {{< /tabs >}} + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your project and apps should be available on the + listener's IP address and port: + + ![Django on Unit - Admin Login Screen](/unit/images/django.png) + diff --git a/content/unit/howto/frameworks/djangochannels.md b/content/unit/howto/frameworks/djangochannels.md new file mode 100644 index 000000000..f2566ef41 --- /dev/null +++ b/content/unit/howto/frameworks/djangochannels.md @@ -0,0 +1,120 @@ +--- +title: Django Channels +toc: true +weight: 600 +--- + +To run Django apps using the Django Channels [framework](https://channels.readthedocs.io/en/stable/) with Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Install and configure the Django 3.0+ [framework](https://www.djangoproject.com). The official docs [recommend](https://docs.djangoproject.com/en/stable/topics/install/#installing-an-official-release-with-pip) + setting up a virtual environment; if you do, list it as **home** when + configuring Unit later. Here, it's **/path/to/venv/**. + +3. Install Django Channels in your virtual environment: + + ```console + $ cd /path/to/venv/ # Path to the virtual environment; use a real path in your configuration + ``` + + ```console + $ source bin/activate + ``` + + ```console + $ pip install channels + ``` + + ```console + $ deactivate + ``` + +4. Create a Django project. Here, we'll use the [tutorial chat app](https://channels.readthedocs.io/en/stable/tutorial/part_1.html#tutorial-part-1-basic-setup), + installing it at **/path/to/app/**; use a real path in your + configuration. The following steps assume your project uses [basic + directory structure](https://docs.djangoproject.com/en/stable/ref/django-admin/#django-admin-startproject): + + ```none + /path/to/app/ # Project directory + |-- manage.py + |-- chat/ # Individual app directory + | |-- ... + |-- mysite/ # Project subdirectory + | |-- ... + | `-- asgi.py # ASGI application module + `-- static/ # Static files subdirectory + ``` + +5. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +6. Integrate Django Channels into your project according to the official [Channels guide](https://channels.readthedocs.io/en/stable/tutorial/part_1.html#integrate-the-channels-library). + +7. Next, create the Django Channels [configuration]({{< relref "/unit/configuration.md#configuration-python" >}}) for + Unit. Here, the **/path/to/app/** directory is stored in the + **path** option; the virtual environment is **home**; the ASGI + module in the **mysite/** subdirectory is [imported](https://docs.python.org/3/reference/import.html) via **module**. If + you reorder your directories, [set up]({{< relref "/unit/configuration.md#configuration-python" >}}) + **path**, **home**, and **module** accordingly. + + You can also set up some environment variables that your project relies on, + using the **environment** option. Finally, if your project uses + Django's [static files](https://docs.djangoproject.com/en/stable/howto/static-files/), optionally + add a [route]({{< relref "/unit/configuration.md#configuration-routes" >}}) to + [serve]({{< relref "/unit/configuration.md#configuration-static" >}}) them with Unit. + + Here's an example (use real values for **share**, **path**, + **environment**, **module**, and **home**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": "/static/*" + }, + "action": { + "share": "/path/to/app$uri", + "share_comment": "Serves static files. Thus, URIs starting with /static/ are served from /path/to/app/static/; use a real path in your configuration" + } + }, + { + "action": { + "pass": "applications/djangochannels" + } + } + ], + "applications": { + "djangochannels": { + "type": "python 3.X", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Project directory; use a real path in your configuration", + "home": "/path/to/venv/", + "home_comment": "Virtual environment directory; use a real path in your configuration", + "module": "mysite.asgi", + "module_comment": "Note the qualified name of the ASGI module; use a real site directory name in your configuration", + "environment": { + "DJANGO_SETTINGS_MODULE": "mysite.settings" + }, + "environment_comment": "App-specific environment variables" + } + } + } + ``` + +8. Upload the updated configuration: + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your project and apps (here, a chat) run on + the listener's IP address and port: + + ![Django Channels on Unit - Tutorial App Screen](/unit/images/djangochannels.png) diff --git a/content/unit/howto/frameworks/express.md b/content/unit/howto/frameworks/express.md new file mode 100644 index 000000000..f18ef846d --- /dev/null +++ b/content/unit/howto/frameworks/express.md @@ -0,0 +1,111 @@ +--- +title: Express +toc: true +weight: 700 +--- + +To run apps built with the [Express](https://expressjs.com) web framework +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) + with the **unit-dev/unit-devel** package. Next, [install]({{< relref "unit/installation.md#installation-nodejs-package" >}}) Unit's **unit-http** package. Run the following + command as root: + + ```console + # npm install -g --unsafe-perm unit-http + ``` + +2. Create your app directory, [install](https://expressjs.com/en/starter/installing.html) Express, and link **unit-http**: + + ```console + $ mkdir -p :/path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ npm install express --save + ``` + + Run the following command as root: + + ```console + # npm link unit-http + ``` + +3. Create your Express [app](https://expressjs.com/en/starter/hello-world.html); + let's store it as **/path/to/app/app.js**. First, initialize the directory: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ npm init + ``` + + Next, add your application code: + + ```javascript + #!/usr/bin/env node + + const http = require('http') + const express = require('express') + const app = express() + + app.get('/', (req, res) => res.send('Hello, Express on Unit!')) + + http.createServer(app).listen() + ``` + + The file should be made executable so the application can run on Unit: + + ```console + $ chmod +x app.js # Application file; use a real path in your configuration + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) +the Express configuration for Unit: + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/express" + } + }, + "applications": { + "express": { + "type": "external", + "working_directory": "/path/to/app/", + "working_directory_comment": "Needed to use the installed NPM modules; use a real path in your configuration", + "executable": "/usr/bin/env", + "executable_comment": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "app.js" + ], + "arguments_comment": "The env executable runs Node.js, supplying Unit's loader module and your app code as arguments", + "app_js_comment": "Basename of the application file; be sure to make it executable" + } + } + } + ``` + +6. {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener's IP + address and port: + +![Express on Unit - Welcome Screen](/unit/images/express.png) diff --git a/content/unit/howto/frameworks/falcon.md b/content/unit/howto/frameworks/falcon.md new file mode 100644 index 000000000..8ee6419b6 --- /dev/null +++ b/content/unit/howto/frameworks/falcon.md @@ -0,0 +1,150 @@ +--- +title: Falcon +weight: 800 +toc: true +--- + +To run apps built with the [Falcon](https://falcon.readthedocs.io/en/stable/) +web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.5+ language module. + +2. Create a virtual environment to install Falcon's + [PIP package](https://falcon.readthedocs.io/en/stable/user/install.html), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install falcon + $ deactivate + ``` + + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try an updated version of the [quickstart app](https://falcon.readthedocs.io/en/stable/user/quickstart.html): + + {{< tabs name="falcon" >}} + {{% tab name="WSGI" %}} + + ```python + import falcon + + # Falcon follows the REST architectural style, meaning (among + # other things) that you think in terms of resources and state + # transitions, which map to HTTP verbs. + class HelloUnitResource: + def on_get(self, req, resp): + """Handles GET requests""" + resp.status = falcon.HTTP_200 # This is the default status + resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override + resp.text = ('Hello, Unit!') + + # falcon.App instances are callable WSGI apps + # in larger applications the app is created in a separate file + app = falcon.App() + + # Resources are represented by long-lived class instances + hellounit = HelloUnitResource() + + # hellounit will handle all requests to the '/unit' URL path + app.add_route('/unit', hellounit) + ``` + + Note that we’ve dropped the server code; save the file as + **/path/to/app/wsgi.py**. + + + {{% /tab %}} + {{% tab name="ASGI" %}} + + ```python + import falcon + import falcon.asgi + + + # Falcon follows the REST architectural style, meaning (among + # other things) that you think in terms of resources and state + # transitions, which map to HTTP verbs. + class HelloUnitResource: + async def on_get(self, req, resp): + """Handles GET requests""" + resp.status = falcon.HTTP_200 # This is the default status + resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override + resp.text = ('Hello, Unit!') + + # falcon.asgi.App instances are callable ASGI apps... + # in larger applications the app is created in a separate file + app = falcon.asgi.App() + + # Resources are represented by long-lived class instances + hellounit = HelloUnitResource() + + # hellounit will handle all requests to the '/unit' URL path + app.add_route('/unit', hellounit) + ``` + + Save the file as **/path/to/app/asgi.py**. + + {{% /tab %}} + {{< /tabs >}} + +--- + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the configuration for Unit (use real values for **type**, **home**, **module**, + **protocol**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/falcon" + } + }, + "applications": { + "falcon": { + "type": "python X.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the WSGI module; use a real path in your configuration", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any; use a real path in your configuration", + "module": "module_basename", + "module_comment": "WSGI/ASGI module basename with extension omitted, such as 'wsgi' or 'asgi' from Step 3", + "protocol": "wsgi_or_asgi", + "protocol_comment": "'wsgi' or 'asgi', as appropriate", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost/unit + + Hello, Unit! + ``` diff --git a/content/unit/howto/frameworks/fastapi.md b/content/unit/howto/frameworks/fastapi.md new file mode 100644 index 000000000..dfef6891a --- /dev/null +++ b/content/unit/howto/frameworks/fastapi.md @@ -0,0 +1,102 @@ +--- +title: FastAPI +toc: true +weight: 900 +--- + +To run apps built with the [FastAPI](https://fastapi.tiangolo.com) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Create a virtual environment to install FastAPI's + [PIP package](https://fastapi.tiangolo.com/tutorial/#install-fastapi), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install fastapi + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try a version of a [tutorial app](https://fastapi.tiangolo.com/tutorial/first-steps/), + saving it as **/path/to/app/asgi.py**: + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.get("/") + async def root(): + return {"message": "Hello, World!"} + ``` + + {{< note >}} + For something more true-to-life, try the + [RealWorld example app](https://github.com/nsidnev/fastapi-realworld-example-app); just + install all its dependencies in the same virtual environment where you've + installed FastAPI and add the app's **environment** {ref}`variables + ` like **DB_CONNECTION** or + **SECRET_KEY** directly to the app configuration in Unit instead of + the **.env** file. + {{< /note >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the FastAPI configuration for Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/fastapi" + } + }, + "applications": { + "fastapi": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + curl http://localhost + + Hello, World! + ``` + + Alternatively, try FastAPI's nifty self-documenting features: + + ![FastAPI on Unit - Swagger Screen](/unit/images/fastapi.png) diff --git a/content/unit/howto/frameworks/flask.md b/content/unit/howto/frameworks/flask.md new file mode 100644 index 000000000..caff1b38a --- /dev/null +++ b/content/unit/howto/frameworks/flask.md @@ -0,0 +1,87 @@ +--- +title: Flask +toc: true +weight: 1000 +--- + +To run apps built with the [Flask](https://flask.palletsprojects.com/en/1.1.x/) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3 language module. + +2. Create a virtual environment to install Flasks`s + [PIP package](https://flask.palletsprojects.com/en/1.1.x/installation/#install-flask), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install Flask + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try a basic version of the [quickstart app](https://flask.palletsprojects.com/en/1.1.x/quickstart/), + saving it as **/path/to/app/wsgi.py**: + + ```python + from flask import Flask + app = Flask(__name__) # Callable name used in Unit's condfiguration + + @app.route("/") + def hello_world(): + return "Hello, World!" + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the [ app ] configuration for + Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/flask" + } + }, + "applications": { + "flask": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the WSGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "wsgi", + "module_comment": "WSGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + + Hello, World! + ``` diff --git a/content/unit/howto/frameworks/guillotina.md b/content/unit/howto/frameworks/guillotina.md new file mode 100644 index 000000000..388b6e869 --- /dev/null +++ b/content/unit/howto/frameworks/guillotina.md @@ -0,0 +1,151 @@ +--- +title: Gillotina +toc: true +weight: 1000 +--- + +To run apps built with the [Guillotina](https://guillotina.readthedocs.io/en/latest/) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.7+ language module. + +2. Create a virtual environment to install Guillotina's + [PIP package](https://guillotina.readthedocs.io/en/latest/training/installation.html), + for instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install guillotina + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try a version of the [tutorial app](https://guillotina.readthedocs.io/en/latest/#build-a-guillotina-app), + saving it as **/path/to/app/asgi.py**: + + ```python + from guillotina import configure + from guillotina import content + from guillotina import schema + from guillotina.factory import make_app + from zope import interface + + + class IMyType(interface.Interface): + textline = schema.TextLine() + + + @configure.contenttype( + type_name="MyType", + schema=IMyType, + behaviors=["guillotina.behaviors.dublincore.IDublinCore"], + ) + class MyType(content.Resource): + pass + + + @configure.service( + context=IMyType, + method="GET", + permission="guillotina.ViewContent", + name="@textline", + ) + async def textline_service(context, request): + return {"textline": context.textline} + + + # Callable name that Unit looks for + application = make_app( + settings={ + "applications": ["__main__"], + "root_user": {"password": "root"}, + "databases": { + "db": {"storage": "DUMMY_FILE", "filename": "dummy_file.db"} + }, + } + ) + ``` + + Note that all server calls and imports are removed. + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Guillotina configuration for + Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/guillotina" + } + }, + "applications": { + "guillotina": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "protocol": "asgi", + "protocol_comment": "Protocol hint for Unit, required to run Guillotina apps" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl -XPOST --user root:root http://localhost/db \ + -d '{ "@type": "Container", "id": "container" }' + + {"@type":"Container","id":"container","title":"container"} + ``` + + ```console + $ curl --user root:root http://localhost/db/container + + { + "@id": "http://localhost/db/container", + "@type": "Container", + "@name": "container", + "@uid": "84651300b2f14170b2b2e4a0f004b1a3", + "@static_behaviors": [ + ], + "parent": { + }, + "is_folderish": true, + "creation_date": "2020-10-16T14:07:35.002780+00:00", + "modification_date": "2020-10-16T14:07:35.002780+00:00", + "type_name": "Container", + "title": "container", + "uuid": "84651300b2f14170b2b2e4a0f004b1a3", + "__behaviors__": [ + ], + "items": [ + ], + "length": 0 + } + ``` + diff --git a/content/unit/howto/frameworks/koa.md b/content/unit/howto/frameworks/koa.md new file mode 100644 index 000000000..04c6a5fcf --- /dev/null +++ b/content/unit/howto/frameworks/koa.md @@ -0,0 +1,140 @@ +--- +title: Koa +weight: 1100 +toc: true +--- + +To run apps built with the [Koa](https://koajs.com) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +with the **unit-dev/unit-devel** package. Next, +[install]({{< relref "unit/installation.md#installation-nodejs-package" >}}) +Unit's **unit-http** package. Run the following command as root: + + ```console + # npm install -g --unsafe-perm unit-http + ``` + +2. Create your app directory, [install](https://koajs.com/#introduction) + Koa, and link **unit-http**: + + ```console + $ mkdir -p /path/to/app/ #Path to the application directory; use a real path in your configuration + ``` + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ npm install koa + ``` + + Run the following command as root: + + ```console + # npm link unit-http + ``` + +3. Let’s try a version of the [tutorial app](https://koajs.com/#application), saving it as + **/path/to/app/app.js**: + + ```javascript + const Koa = require('koa'); + const app = new Koa(); + + // logger + + app.use(async (ctx, next) => { + await next(); + const rt = ctx.response.get('X-Response-Time'); + console.log(`${ctx.method} ${ctx.url} - ${rt}`); + }); + + // x-response-time + + app.use(async (ctx, next) => { + const start = Date.now(); + await next(); + const ms = Date.now() - start; + ctx.set('X-Response-Time', `${ms}ms`); + }); + + // response + + app.use(async ctx => { + ctx.body = 'Hello, Koa on Unit!'; + }); + + app.listen(); + ``` + + The file should be made executable so the application can run on Unit: + + ```console + $ chmod +x app.js # Application file; use a real path in your configuration + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) + the Koa configuration for Unit: + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/koa" + } + }, + "applications": { + "koa": { + "type": "external", + "working_directory": "/path/to/app/", + "working_directory_comment": "Needed to use the installed NPM modules; use a real path in your configuration", + "executable": "/usr/bin/env", + "executable_comment": "The external app type allows to run arbitrary executables, provided they establish communication with Unit", + "arguments": [ + "node", + "--loader", + "unit-http/loader.mjs", + "--require", + "unit-http/loader", + "app.js" + ], + "arguments_comment": "The env executable runs Node.js, supplying Unit's loader module and your app code as arguments", + "app_js_comment": "Basename of the application file; be sure to make it executable" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener's IP + address and port: + + ```console + $ curl http://localhost -v + + * Trying 127.0.0.1:80... + * TCP_NODELAY set + * Connected to localhost (127.0.0.1) port 80 (#0) + > GET / HTTP/1.1 + > Host: localhost + > User-Agent: curl/7.68.0 + > Accept: */* + > + * Mark bundle as not supporting multiuse + < HTTP/1.1 200 OK + < Content-Type: text/plain; charset=utf-8 + < Content-Length: 11 + < X-Response-Time: 0ms + < Server: Unit/{{< param "unitversion" >}} + + Hello, Koa on Unit! + ``` diff --git a/content/unit/howto/frameworks/laravel.md b/content/unit/howto/frameworks/laravel.md new file mode 100644 index 000000000..ff7d23e0b --- /dev/null +++ b/content/unit/howto/frameworks/laravel.md @@ -0,0 +1,77 @@ +--- +title: Laravel +weight: 1200 +toc: true +--- + +To run apps based on the [Laravel](https://laravel.com) framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure Laravel's [prerequisites](https://laravel.com/docs/deployment#server-requirements). + +3. Create a Laravel [project](https://laravel.com/docs/installation#creating-a-laravel-project). + For our purposes, the path is **/path/to/app/**: + + ```console + $ cd /path/to/ #Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ composer create-project laravel/laravel `app` #Arbitrary app name; becomes the application directory name + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + {{< note >}} + See the Laravel docs for further details on [directory structure](https://laravel.com/docs/structure). + {{< /note >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Laravel configuration for + Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": "!/index.php", + "uri_comment": "Denies access to index.php as a static file" + }, + "action": { + "share": "/path/to/app/public$uri", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/laravel", + "pass_comment": "Uses the index.php at the root as the last resort" + } + } + } + ], + "applications": { + "laravel": { + "type": "php", + "root": "/path/to/app/public/", + "root_comment": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://laravel.com/docs/configuration) your Laravel application: + + ![Laravel on Unit - Sample Screen](/unit/images/laravel.png) + diff --git a/content/unit/howto/frameworks/lumen.md b/content/unit/howto/frameworks/lumen.md new file mode 100644 index 000000000..d17beffe1 --- /dev/null +++ b/content/unit/howto/frameworks/lumen.md @@ -0,0 +1,71 @@ +--- +title: Lumen +weight: 1300 +toc: true +--- + +To run apps based on the [Lumen](https://lumen.laravel.com) framework using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Install and configure Lumen's [requirements](https://lumen.laravel.com/docs/8.x#server-requirements) + +3. Create a Lumen [project](https://lumen.laravel.com/docs/8.x#installing-lumen). + For our purposes, the path is **/path/to/app/**: + + ```console + $ cd /path/to/ # Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ composer create-project laravel/lumen app # Arbitrary app name; becomes the application directory name + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Lumen configuration for + Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": "!/index.php", + "uri_comment": "Denies access to index.php as a static file" + }, + "action": { + "share": "/path/to/app/public/", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/lumen", + "pass_comment": "Uses the index.php at the root as the last resort" + } + } + } + ], + "applications": { + "lumen": { + "type": "php", + "root": "/path/to/app/public/", + "root_comment": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, browse to and [set up](https://lumen.laravel.com/docs/8.x/configuration) your Lumen application. diff --git a/content/unit/howto/frameworks/pyramid.md b/content/unit/howto/frameworks/pyramid.md new file mode 100644 index 000000000..cfda4cdaa --- /dev/null +++ b/content/unit/howto/frameworks/pyramid.md @@ -0,0 +1,137 @@ +--- +title: Pyramid +weight: 1300 +toc: true +--- + +To run apps built with the [Pyramid](https://trypyramid.com) web framework +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3 language module. + +2. Create a virtual environment to install Pyramid`s + [PIP package](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-unix-system), for instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + + {{< note >}} + Here, **\$VENV** isn't set because Unit picks up the virtual + environment from **home** in Step 5. + {{< /note >}} + +3. Let's see how the apps from the Pyramid + [tutorial](https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial) + run on Unit. + + {{< tabs name="pyramid" >}} + {{% tab name="Single-File" %}} + + We modify the [tutorial app](https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial/hello_world.html#steps) + saving it as **/path/to/app/wsgi.py**: + + ```python + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('

Hello, World!

') + + with Configurator() as config: + config.add_route('hello', '/') + config.add_view(hello_world, route_name='hello') + + # Callables' name is used in Unit configuration + app = config.make_wsgi_app() + + # serve(app, host='0.0.0.0', port=6543) + ``` + + Note that we've dropped the server code; also, mind that Unit imports + the module, so the **if __name__ == '__main__'** idiom would be + irrelevant. + + {{% /tab %}} + {{% tab name="INI-Based" %}} + + To load the + [configuration](https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial/ini.html), + we place a **wsgi.py** file next to **development.ini** in **/path/to/app/**: + + ```python + from pyramid.paster import get_app, setup_logging + + # Callables' name is used in Unit configuration + app = get_app('development.ini') + setup_logging('development.ini') + ``` + + This [sets up](https://docs.pylonsproject.org/projects/pyramid/en/latest/api/paster.html) + the WSGI application for Unit; if the **.ini**'s pathname is + relative, provide the appropriate **working_directory** in Unit + configuration. + + {{% /tab %}} + {{< /tabs >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the Pyramid configuration for Unit (use real values for **type**, **home**, + and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/pyramid" + } + }, + "applications": { + "pyramid": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "working_directory": "/path/to/app/", + "working_directory_comment": "Path to the application directory; use a real path in your configuration", + "path": "/path/to/app/", + "path_comment": "Path to the application directory; use a real path in your configuration", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "wsgi", + "module_comment": "WSGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + +

Hello, World!

+ ``` diff --git a/content/unit/howto/frameworks/quart.md b/content/unit/howto/frameworks/quart.md new file mode 100644 index 000000000..f705a7a8e --- /dev/null +++ b/content/unit/howto/frameworks/quart.md @@ -0,0 +1,107 @@ +--- +title: Quart +weight: 1400 +toc: true +--- + + +To run apps built with the [Quart](https://pgjones.gitlab.io/quart/index.html) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.5+ language module. + +2. Create a virtual environment to install Quart`s + [PIP package](https://pgjones.gitlab.io/quart/tutorials/installation.html), + for instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install quart + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + + +3. Let's try a WebSocket-enabled version of a + [tutorial app](https://pgjones.gitlab.io/quart/tutorials/deployment.html), + saving it as **/path/to/app/asgi.py**: + + ```python + from quart import Quart, websocket + + app = Quart(__name__) + + @app.route('/') + async def hello(): + return '

Hello, World!

' + + # Let's add WebSocket support to the app as well + @app.websocket('/ws') + async def ws(): + while True: + await websocket.send('Hello, World!') + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the Quart configuration for Unit (use real values for **type**, **home**, + and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/quart" + } + }, + "applications": { + "quart": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + +

Hello, World!

+ ``` + + ```console + $ wscat -c ws://localhost/ws + + < Hello, World! + < Hello, World! + < Hello, World! + ... + ``` diff --git a/content/unit/howto/frameworks/rails.md b/content/unit/howto/frameworks/rails.md new file mode 100644 index 000000000..1e7e0b32a --- /dev/null +++ b/content/unit/howto/frameworks/rails.md @@ -0,0 +1,74 @@ +--- +title: Ruby on Rails +weight: 1600 +toc: true +--- + +To run apps based on the [Ruby on Rails](https://rubyonrails.org) framework +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Ruby language module. + +2. [Install](https://guides.rubyonrails.org/getting_started.html#creating-a-new-rails-project-installing-rails) + Ruby on Rails and create or deploy your app. Here, we use Ruby on Rails's [basic template](https://guides.rubyonrails.org/getting_started.html#creating-the-blog-application): + + ```console + $ cd /path/to/ # Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ rails new app # Arbitrary app name; becomes the application directory name + ``` + + This creates the app's directory tree at **/path/to/app/**; its + **public/** subdirectory contains the static files, while the entry + point is **/path/to/app/config.ru**. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, + [prepare]({{< relref "/unit/configuration.md#configuration-ruby" >}}) + the Ruby on Rails configuration (use real values for **share** and + **working_directory**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "action": { + "share": "/path/to/app/public$uri", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/rails" + } + } + } + ], + "applications": { + "rails": { + "type": "ruby", + "script": "config.ru", + "script_comment": "All requests are handled by a single script, relative to working_directory", + "working_directory": "/path/to/app/", + "working_directory_comment": "Path to the application directory, needed here for 'require_relative' directives; use a real path in your configuration" + } + } + } + ``` + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ![Ruby on Rails Basic Template App on Unit](/unit/images/rails.png) + diff --git a/content/unit/howto/frameworks/responder.md b/content/unit/howto/frameworks/responder.md new file mode 100644 index 000000000..9192953f9 --- /dev/null +++ b/content/unit/howto/frameworks/responder.md @@ -0,0 +1,108 @@ +--- +title: Responder +weight: 1500 +toc: true +--- + +# Responder + +To run apps built with the [Responder](https://responder.kennethreitz.org/) web framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Create a virtual environment to install Responder’s +[PIP package](https://responder.kennethreitz.org/#installing-responder), +for instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install responder + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + + +3. Let's try a Unit-friendly version of a [tutorial app](https://responder.kennethreitz.org/quickstart.html#declare-a-web-service), + saving it as **/path/to/app/asgi.py**: + + ```python + import responder + + app = responder.API() + + @app.route("/") + def hello_world(req, resp): + resp.text = "Hello, World!" + + @app.route("/hello/{who}") + def hello_to(req, resp, *, who): + resp.text = f"Hello, {who}!" + ``` + + The **app.run()** call is omitted because **app** will be directly + run by Unit as an ASGI [callable](https://github.com/kennethreitz/responder/blob/c6f3a7364cfa79805b0d51eea011fe34d9bd331a/responder/api.py#L501). + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Responder configuration for + Unit (use real values for **type**, **home**, and **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/responder" + } + }, + "applications": { + "responder": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "working_directory": "/path/to/app/", + "working_directory_comment": "Path to the directory where Responder creates static_dir and templates_dir", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + + Hello, World! + ``` + + ```console + $ curl http://localhost/hello/JohnDoe + + Hello, JohnDoe! + ``` + diff --git a/content/unit/howto/frameworks/sanic.md b/content/unit/howto/frameworks/sanic.md new file mode 100644 index 000000000..a71adce94 --- /dev/null +++ b/content/unit/howto/frameworks/sanic.md @@ -0,0 +1,85 @@ +--- +title: Sanic +weight: 1700 +toc: true +--- + +To run apps built with the [Sanic](https://sanic.dev/) web framework +using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.7+ language module. + +2. Create a virtual environment to install Sanic's + [PIP package](https://sanic.dev/en/guide/getting-started.html), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install sanic + $ deactivate + ``` + +3. Let's try a version of a [tutorial app](ttps://sanic.dev/en/guide/basics/response.html#methods), + saving it as **/path/to/app/asgi.py**: + + ```python + from sanic import Sanic + from sanic.response import json + + app = Sanic() + + @app.route("/") + async def test(request): + return json({"hello": "world"}) + ``` + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the Sanic configuration for Unit (use real values for **type**, **home**, and + **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/sanic" + } + }, + "applications": { + "sanic": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + + {"hello":"world"} + ``` + diff --git a/content/unit/howto/frameworks/springboot.md b/content/unit/howto/frameworks/springboot.md new file mode 100644 index 000000000..2d8407bfd --- /dev/null +++ b/content/unit/howto/frameworks/springboot.md @@ -0,0 +1,121 @@ +--- +title: Spring Boot +weight: 1800 +toc: true +--- + +To run apps based on the [Spring Boot](https://spring.io/projects/spring-boot) frameworks using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Java language module. + +2. Create your Spring Boot project; we'll use the [quickstart](https://spring.io/quickstart) + example, creating it at : + + ![Spring Initializr - Project Setup Screen](/unit/images/springboot.png) + + {{< note >}} + Choose the same Java version that your Unit language module has. + {{< /note >}} + + Download and extract the project files where you need them: + + ```console + $ unzip demo.zip -d /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + This creates a directory named **/path/to/app/demo/** for you to add + your app code to; in our [example](https://spring.io/quickstart), it's a + single file called + **/path/to/app/demo/src/main/java/com/example/demo/DemoApplication.java**: + + ```java + package com.example.demo; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.web.bind.annotation.GetMapping; + import org.springframework.web.bind.annotation.RequestParam; + import org.springframework.web.bind.annotation.RestController; + + @SpringBootApplication + @RestController + public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + + @GetMapping("/hello") + public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { + return String.format("Hello, %s!", name); + } + } + ``` + + Finally, assemble a **.war** file. + + If you chose [Gradle](https://gradle.org) as the build tool: + + ```console + $ cd /path/to/app/demo/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ ./gradlew bootWar + ``` + + If you chose [Maven](https://maven.apache.org): + + ```console + $ cd /path/to/app/demo/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ ./mvnw package + ``` + + {{< note >}} + By default, Gradle puts the **.war** file in the **build/libs/** + subdirectory, while Maven uses **target/**; note your path for later + use in Unit configuration. + {{< /note >}} + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, + [put together]({{< relref "/unit/configuration.md#configuration-java" >}}) + the Spring Boot configuration (use a real value for **working_directory**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/bootdemo" + } + }, + "applications": { + "bootdemo": { + "type": "java", + "webapp": "gradle-or-maven-build-dir/demo.war", + "webapp_comment": "Relative pathname of your .war file", + "working_directory": "/path/to/app/demo/", + "working_directory_comment": "Path to the application directory; use a real path in your configuration" + } + } + } + ``` + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener's IP + address and port: + + ```console + $ curl http://localhost/hello?name=Unit + + Hello, Unit! + ``` diff --git a/content/unit/howto/frameworks/starlette.md b/content/unit/howto/frameworks/starlette.md new file mode 100644 index 000000000..fa16f6735 --- /dev/null +++ b/content/unit/howto/frameworks/starlette.md @@ -0,0 +1,155 @@ +--- +title: Starlette +toc: true +weight: 1900 +--- + +# Starlette + +To run apps built with the [Starlette](https://www.starlette.io) web +framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.5+ language module. + +2. Create a virtual environment to install Starlette's + [PIP package](https://www.starlette.io/#installation), for + instance: + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + $ python --version # Make sure your virtual environment version matches the module version + Python X.Y.Z # Major version, minor version, and revision number + $ python -m venv venv # Arbitrary name of the virtual environment + $ source venv/bin/activate # Name of the virtual environment from the previous command + $ pip install 'starlette[full]' + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches the + language module from Step 1 up to the minor number (**X.Y** in + this example). Also, the app **type** in Step 5 must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + +3. Let's try a version of a [tutorial app](https://www.starlette.io/applications/), + saving it as **/path/to/app/asgi.py**: + + ```python + from starlette.applications import Starlette + from starlette.responses import PlainTextResponse + from starlette.routing import Route, Mount, WebSocketRoute + + + def homepage(request): + return PlainTextResponse('Hello, world!') + + def user_me(request): + username = "John Doe" + return PlainTextResponse('Hello, %s!' % username) + + def user(request): + username = request.path_params['username'] + return PlainTextResponse('Hello, %s!' % username) + + async def websocket_endpoint(websocket): + await websocket.accept() + await websocket.send_text('Hello, websocket!') + await websocket.close() + + def startup(): + print('Ready to go') + + + routes = [ + Route('/', homepage), + Route('/user/me', user_me), + Route('/user/{username}', user), + WebSocketRoute('/ws', websocket_endpoint) + ] + + app = Starlette(debug=True, routes=routes, on_startup=[startup]) + ``` + + {{< note >}} + This sample omits the static route because Unit's quite + [capable]({{< relref "/unit/configuration.md#configuration-static" >}}) + of serving static files itself if needed. + {{< /note >}} + +4. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + + +5. Next, [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) the Starlette configuration for Unit + (use real values for **type**, **home**, and **path**), adding a + [route]({{< relref "/unit/configuration.md#configuration-routes" >}}) to serve static content: + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": "/static/*" + }, + "action": { + "share": "/path/to/app$uri", + "share_comment": "Serves static files. Thus, URIs starting with /static/ are served from /path/to/app/static/; use a real path in your configuration" + } + }, + { + "action": { + "pass": "applications/starlette" + } + } + ], + "applications": { + "starlette": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/", + "path_comment": "Path to the ASGI module", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment, if any", + "module": "asgi", + "module_comment": "ASGI module filename with extension omitted", + "callable": "app", + "callable_comment": "Name of the callable in the module to run" + } + } + } + ``` + +6. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the listener’s IP + address and port: + + ```console + $ curl http://localhost + + Hello, world! + ``` + + ```console + $ curl http://localhost/user/me + + Hello, John Doe! + ``` + + ```console + $ wscat -c ws://localhost/ws + + Connected (press CTRL+C to quit) + < Hello, websocket! + Disconnected (code: 1000, reason: "") + ``` diff --git a/content/unit/howto/frameworks/symfony.md b/content/unit/howto/frameworks/symfony.md new file mode 100644 index 000000000..c07b5a68e --- /dev/null +++ b/content/unit/howto/frameworks/symfony.md @@ -0,0 +1,107 @@ +--- +title: Symfony +toc: true +weight: 1900 +--- + +To run apps built with the [Symfony](https://symfony.com) framework using Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP 8.2+ language module. + +2. Next, [install](https://symfony.com/doc/current/setup.html) Symfony + and create or deploy your app. Here, we use + Symfony's [reference app](https://symfony.com/doc/current/setup.html#the-symfony-demo-application): + + ```console + $ cd /path/to/ # Path where the application directory will be created; use a real path in your configuration + ``` + + ```console + $ symfony new --demo app # Arbitrary app name + ``` + + This creates the app's directory tree at **/path/to/app/**. Its + **public/** subdirectory contains both the root **index.php** and + the static files; if your app requires additional **.php** scripts, also + store them here. + +3. Change ownership: + + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) the Symfony configuration for Unit + (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*" + ], + "uri_comment": "Handles all direct script-based requests" + }, + "action": { + "pass": "applications/symfony/direct" + } + }, + { + "action": { + "share": "/path/to/app/public$uri", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/symfony/index", + "pass_comment": "Uses the index.php at the root as the last resort" + } + } + } + ], + "applications": { + "symfony": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/public/", + "root_comment": "Path to the application directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/public/", + "root_comment": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + } + } + ``` + + {{< note >}} + The difference between the **pass** targets is their usage of the + **script** [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the URI or + defaults to **index.php** if the URI omits it. + - The **index** target specifies the **script** that Unit runs + for *any* URIs the target receives. + {{< /note >}} + + For a detailed discussion, see [Configuring a Web Server](https://symfony.com/doc/current/setup/web_server_configuration.html) in + Symfony docs. + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your project and apps should be available on the + listener's IP address and port: + + ![Symfony Demo App on Unit - Admin Post Update](/unit/images/symfony.png) + diff --git a/content/unit/howto/frameworks/yii.md b/content/unit/howto/frameworks/yii.md new file mode 100644 index 000000000..ef92dc20d --- /dev/null +++ b/content/unit/howto/frameworks/yii.md @@ -0,0 +1,221 @@ +--- +title: Yii +weight: 2000 +toc: true +--- + + +To run apps based on the [Yii](https://www.yiiframework.com) framework +versions 1.1 or 2.0 using Unit: + +{{< tabs name="version" >}} +{{% tab name="Yii 2.0" %}} + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + + +2. Next, [install](https://www.yiiframework.com/doc/guide/2.0/en/start-installation) + Yii and create or deploy your app. + + Here, we use Yii's [basic project template](https://www.yiiframework.com/doc/guide/2.0/en/start-installation#installing-from-composer) + and Composer: + + ```console + $ cd /path/to/ # Partial path to the application directory; use a real path in your configuration + ``` + + ```console + $ composer create-project --prefer-dist yiisoft/yii2-app-basic app # Arbitrary app name + ``` + + This creates the app's directory tree at **/path/to/app/**. + Its **web/** subdirectory contains both the root + **index.php** and the static files; if your app requires + additional **.php** scripts, also store them here. + +3. Change ownership: + {{< include "unit/howto_change_ownership.md" >}} + +4. Next, + [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) + the Yii configuration for Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": [ + "!/assets/*", + "*.php", + "*.php/*" + ], + "uri_comment": "This path stores application data that shouldn't be run as code" + }, + "action": { + "pass": "applications/yii/direct" + } + }, + { + "action": { + "share": "/path/to/app/web$uri", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/yii/index" + } + } + } + ], + "applications": { + "yii": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/web/", + "root_comment": "Path to the application directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/web/", + "root_comment": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + } + } + ``` + + For a detailed discussion, see [Configuring Web Servers](https://www.yiiframework.com/doc/guide/2.0/en/start-installation#configuring-web-servers) + and [Running Applications](https://www.yiiframework.com/doc/guide/2.0/en/start-workflow) in Yii 2.0 docs. + +{{< note >}} + The difference between the **pass** targets is their usage of + the **script** + [setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + + - The **direct** target runs the **.php** script from the + URI or **index.php** if the URI omits it. + + - The **index** target specifies the **script** that Unit + runs for *any* URIs the target receives. +{{< /note >}} + + +5. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the + listener’s IP address and port: + + ![Yii Basic Template App on Unit](/unit/images/yii2.png) + +{{% /tab %}} +{{% tab name="Yii 1.1" %}} + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a PHP language module. + +2. Next, [install](https://www.yiiframework.com/doc/guide/1.1/en/quickstart.installation) +Yii and create or deploy your app. + + Here, we use Yii's [basic project template](https://www.yiiframework.com/doc/guide/1.1/en/quickstart.first-app) + and `yiic`: + + ```console + $ git clone git@github.com:yiisoft/yii.git /path/to/yii1.1/ # Arbitrary framework path + ``` + + ```code-block:: console + $ /path/to/yii1.1/ framework/yiic webapp /path/to/app/ #Path to the application directory; use a real path in your configuration + ``` + + This creates the app's directory tree at **/path/to/app/**. + +3. Next, + [prepare]({{< relref "/unit/configuration.md#configuration-php" >}}) + the Yii configuration for Unit (use real values for **share** and **root**): + + ```json + { + "listeners": { + "*:80": { + "pass": "routes" + } + }, + "routes": [ + { + "match": { + "uri": [ + "!/assets/*", + "!/protected/*", + "!/themes/*", + "*.php", + "*.php/*" + ], + "uri_comment": "This path stores application data that shouldn't be run as code" + }, + "action": { + "pass": "applications/yii/direct" + } + }, + { + "action": { + "share": "/path/to/app$uri", + "share_comment": "Serves static files", + "fallback": { + "pass": "applications/yii/index" + } + } + } + ], + "applications": { + "yii": { + "type": "php", + "targets": { + "direct": { + "root": "/path/to/app/", + "root_comment": "Path to the application directory; use a real path in your configuration" + }, + "index": { + "root": "/path/to/app/", + "root_comment": "Path to the application directory; use a real path in your configuration", + "script": "index.php", + "script_comment": "All requests are handled by a single script" + } + } + } + } + } + ``` + + For a detailed discussion, see Yii 1.1 [docs](https://www.yiiframework.com/doc/guide/1.1/en/quickstart.first-app). + +{{< note >}} +The difference between the **pass** targets is their usage of +the **script** +[setting]({{< relref "/unit/configuration.md#configuration-php" >}}): + +- The **direct** target runs the **.php** script from the + URI or **index.php** if the URI omits it. + +- The **index** target specifies the **script** that Unit + runs for *any* URIs the target receives. +{{< /note >}} + +4. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your app should be available on the + listener’s IP address and port: + + ![Yii Basic Template App on Unit](/unit/images/yii1.1.png) + +{{% /tab %}} +{{< /tabs >}} diff --git a/content/unit/howto/frameworks/zope.md b/content/unit/howto/frameworks/zope.md new file mode 100644 index 000000000..a02a62074 --- /dev/null +++ b/content/unit/howto/frameworks/zope.md @@ -0,0 +1,229 @@ +--- +title: Zope +weight: 2100 +toc: true +--- + +To run apps built with the [Zope](https://www.zope.dev/) web framework using +Unit: + +1. Install [Unit]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) with a Python 3.6+ language module. + +2. Install Zope. Here, we do this at **/path/to/app/**; use a real path + in your configuration. + + {{< tabs name="installation" >}} + {{% tab name="Buildout" %}} + + First, install Zope's + [core files](https://zope.readthedocs.io/en/latest/INSTALL.html#installing-zope-with-zc-buildout), + for example: + + ```console + $ pip install -U pip wheel zc.buildout + ``` + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ wget https://pypi.org/packages/source/Z/Zope/Zope-A.B.C.tar.gz # Zope version + ``` + + ```console + $ tar xfvz Zope-A.B.C.tar.gz --strip-components =1 # Avoids creating a redundant subdirectory + ``` + + ```console + $ buildout + ``` + + Next, add a new configuration file named **/path/to/app/wsgi.cfg**: + + ```cfg + [buildout] + extends = + buildout.cfg + + parts += + wsgi.py # The basename is arbitrary; the extension is required to make the resulting Python module discoverable + + [wsgi.py] + recipe = plone.recipe.zope2instance + user = admin:admin # Instance credentials; omit this line to configure them interactively + zodb-temporary-storage = off # Avoids compatibility issues + eggs = + scripts = + initialization = + from Zope2.Startup.run import make_wsgi_app + wsgiapp = make_wsgi_app({}, '${buildout:parts-directory}/wsgi.py/etc/zope.conf') # Path to the instance's configuration file + def application(*args, **kwargs):return wsgiapp(*args, **kwargs) + ``` + + It creates a new Zope instance. The part's name must end with + **.py** for the resulting instance script to be recognized as a + Python module; the **initialization** [option](https://pypi.org/project/plone.recipe.zope2instance/#common-options) + defines a WSGI entry point. + + Rerun Buildout, feeding it the new configuration file: + + ```console + $ buildout -c wsgi.cfg + + ... + Installing wsgi.py. + Generated script '/path/to/app/bin/wsgi.py'. + ``` + + Thus created, the instance script can be used with Unit. + + {{< include "unit/howto_change_ownership.md" >}} + + Last, + [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the Zope configuration for Unit (use a real value for **path**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/zope" + } + }, + + "applications": { + "zope": { + "type": "python 3", + "path": "/path/to/app/", + "comment_path": "Path to the application directory; use a real path in your configuration", + "module": "bin.wsgi", + "comment_module": "WSGI module's qualified name with extension omitted" + } + } + } + ``` + + {{% /tab %}} + {{% tab name="PIP" %}} + + Create a virtual environment to install Zope's [PIP package](https://pypi.org/project/Zope/) + + ```console + $ cd /path/to/app/ # Path to the application directory; use a real path in your configuration + ``` + + ```console + $ python3 --version # Make sure your virtual environment version matches the module version + Python 3.Y.Z # Major version, minor version, and revision number + ``` + + ```console + $ python3 -m venv venv # This is the virtual environment directory + ``` + + ```console + $ source venv/bin/activate + ``` + + ```console + $ pip install 'zope[wsgi]' + ``` + + ```console + $ deactivate + ``` + + {{< warning >}} + Create your virtual environment with a Python version that matches + the language module from Step 1 up to the minor number + (**3.Y** in this example). Also, the app **type** in Unit + configuration must + [resolve]({{< relref "/unit/configuration.md#configuration-apps-common" >}}) + to a similarly matching version; Unit doesn't infer it from the environment. + {{< /warning >}} + + After installation, create your Zope [instance](https://zope.readthedocs.io/en/latest/operation.html#creating-a-zope-instance): + + ```console + $ venv/bin/mkwsgiinstance -d instance # Using Zope's own script and the Zope instance's home directory + ``` + + To run the instance on Unit, create a WSGI entry point: + + ```python + from pathlib import Path + from Zope2.Startup.run import make_wsgi_app + + wsgiapp = make_wsgi_app({}, str(Path(__file__).parent / 'etc/zope.conf')) # Path to the instance's configuration file + def application(*args, **kwargs):return wsgiapp(*args, **kwargs) + ``` + + Save the script as **wsgi.py** in the instance home directory + (here, it's **/path/to/app/instance/**). + + {{< include "unit/howto_change_ownership.md" >}} + + Last, + [prepare]({{< relref "/unit/configuration.md#configuration-python" >}}) + the Zope configuration + for Unit (use real values for **path** and **home**): + + ```json + { + "listeners": { + "*:80": { + "pass": "applications/zope" + } + }, + "applications": { + "zope": { + "type": "python 3.Y", + "type_comment": "Must match language module version and virtual environment version", + "path": "/path/to/app/instance/", + "path_comment": "Path to the instance/ subdirectory; use a real path in your configuration", + "home": "/path/to/app/venv/", + "home_comment": "Path to the virtual environment; use a real path in your configuration", + "module": "wsgi", + "module_comment": "WSGI module basename with extension omitted" + } + } + } + + ``` + + {{% /tab %}} + {{< /tabs >}} + +--- + + +3. Upload the updated configuration. + + {{< include "unit/howto_upload_config.md" >}} + + After a successful update, your Zope instance should be available on the + listener’s IP address and port: + + ```console + $ curl http://localhost + + + + + + + Auto-generated default page + + + + +

Zope + Auto-generated default page

+ + This is Page Template index_html. + + + ``` + +[app-link]: https://zope.readthedocs.io/en/latest/INSTALL.html diff --git a/content/unit/howto/integration.md b/content/unit/howto/integration.md new file mode 100644 index 000000000..aa89d2267 --- /dev/null +++ b/content/unit/howto/integration.md @@ -0,0 +1,127 @@ +--- +title: NGINX integration +toc: true +weight: 500 +--- + +Unit is a potent and versatile server in its own right. However, if you're +used to NGINX's rich feature set, you can deploy it in front of Unit; one +notable use case for NGINX here is securing the Unit control socket. + +## Fronting Unit with NGINX + +Configure a [listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}) in Unit: + +```json +{ + "127.0.0.1:8080": { + "comment_127.0.0.1:8080": "Socket address where NGINX proxies requests", + "pass": "...", + "comment_pass": "Unit's internal request destination", + "forwarded": { + "client_ip": "X-Forwarded-For", + "comment_client_ip": "The header field set by NGINX", + "source": [ + "127.0.0.1" + ], + "comment_source": "The IP address where NGINX runs" + } + } +} +``` + +Here, **forwarded** is optional; it enables identifying the +[originating IPs]({{< relref "/unit/configuration.md#configuration-listeners-xff" >}}) +of requests proxied from **source**. + +In NGINX configuration, create an upstream in the **http** context, adding +the listener's socket as a **server**: + +```nginx +http { + upstream unit_backend { + server 127.0.0.1:8080; # Unit's listener socket address + } + + server { + location /unit/ { # Arbitrary location + proxy_pass http://unit_backend; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Unit's listener must list the same name in client_ip/header + } + } +} +``` + +A more compact alternative would be a direct **proxy_pass** in your +**location**: + +```nginx +http { + server { + location /unit/ { # Arbitrary location + proxy_pass http://127.0.0.1:8080; # Unit's listener socket address + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Unit's listener must list the same name in client_ip/header + } + } +} +``` + +The **proxy_set_header X-Forwarded-For** directives work together with the +listener's **client_ip** option. + +For details, see the [NGINX documentation](https://nginx.org). Commercial +support and advanced features are [also available](https://www.nginx.com). + +## Securely proxying Unit's control API {#nginx-secure-api} + +By default, Unit exposes its +[control API]({{< relref "/unit/controlapi.md#configuration-mgmt" >}}) +via a UNIX domain socket. These sockets aren't network accessible, so the API is +local only. To enable secure remote access, you can use NGINX as a reverse proxy. + +{{< warning >}} +Avoid exposing an unprotected control socket to public networks. Use NGINX +or a different solution such as SSH for security and authentication. +{{< /warning >}} + +Use this configuration template for NGINX (replace placeholders in +**ssl_certificate**, **ssl_certificate_key**, +**ssl_client_certificate**, **allow**, **auth_basic_user_file**, +and **proxy_pass** with real values): + +```nginx +server { + + # Configure SSL encryption + listen 443 ssl; + + ssl_certificate /path/to/ssl/cert.pem; # Path to your PEM file; use a real path in your configuration + ssl_certificate_key /path/to/ssl/cert.key; # Path to your key file; use a real path in your configuration + + # SSL client certificate validation + ssl_client_certificate /path/to/ca.pem; # Path to certification authority PEM file; use a real path in your configuration + ssl_verify_client on; + + # Network ACLs + allow 1.2.3.4; # Replicate and update as needed with allowed IPs and network CIDRs + deny all; + + # HTTP Basic authentication + auth_basic on; + auth_basic_user_file /path/to/htpasswd; # Path to your htpasswd file + + location / { + proxy_pass http://unix:/path/to/control.unit.sock; # Path to Unit's control socket + } +} +``` + +The same approach works for an IP-based control socket: + +```nginx +location / { + proxy_pass http://127.0.0.1:8080; # Unit's control socket address +} +``` diff --git a/content/unit/howto/modules.md b/content/unit/howto/modules.md new file mode 100644 index 000000000..a2c61b31c --- /dev/null +++ b/content/unit/howto/modules.md @@ -0,0 +1,350 @@ +--- +title: Working with language modules +weight: 700 +toc: true +--- + +Languages supported by Unit fall into these two categories: + +- [External]({{< relref "/unit/howto/modules.md#modules-ext" >}}) + (Go, Node.js): Run outside Unit with an + interface layer to the native runtime. +- [Embedded]({{< relref "/unit/howto/modules.md#modules-emb" >}}) + (Java, Perl, PHP, Python, Ruby, WebAssembly): + Execute in runtimes that Unit loads at startup. + +For any specific language and its version, Unit needs a language module. + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## External language modules {#modules-ext} + +External modules are regular language libraries or packages that you install +like any other. They provide common web functionality, communicating with Unit +from the app's runspace. + +In Go, Unit support is implemented with a package that you +[import]({{< relref "/unit/configuration.md#configuration-go" >}}) +in your apps to make them Unit-aware. + +In Node.js, Unit is supported by an `npm`-hosted [package](https://www.npmjs.com/package/unit-http) that you +[require]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) +in your app code. You can +[install]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) +the package from the `npm` repository; +otherwise, [build]({{< relref "/unit/howto/source.md#modules-nodejs" >}}) +it for your version of Node.js using Unit's sources. + +For WebAssembly, Unit delegates bytecode execution to the +[Wasmtime](https://wasmtime.dev/) runtime that is installed with the +[language module]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +module or during a +[source build]({{< relref "/unit/howto/source.md#source-wasm" >}}). + +## Embedded language modules {#modules-emb} + +Embedded modules are shared libraries that Unit loads at startup. Query Unit +to find them in your system: + +```console +$ unitd -h + + ... + --log FILE set log filename + default: /default/path/to/unit.log # This is the default log path which can be overridden at runtime + + --modules DIRECTORY set modules directory name + default: /default/modules/path/ # This is the default modules path which can be overridden at runtime + +$ ps ax | grep unitd # Check whether the defaults were overridden at launch + ... + unit: main v1.34.1 [unitd --log /runtime/path/to/unit.log --modules /runtime/modules/path/ ... ] # If this option is set, its value is used at runtime + +$ ls /path/to/modules # Use runtime value if the default was overridden + + java.unit.so php.unit.so ruby.unit.so wasm_wasi_component.unit.so + perl.unit.so python.unit.so wasm.unit.so + +``` + +To clarify the module versions, check the {ref}`Unit log ` +to see which modules were loaded at startup: + +```console +# less /path/to/unit.log # Path to log can be determined in the same manner as above + ... + discovery started + module: "/path/to/modules/.unit.so" + ... +``` + +If a language version is not listed, Unit can't run apps that rely on it; +however, you can add new modules: + +- If possible, use the official + [language packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) + for easy integration and maintenance. +- If you installed Unit via a + [third-party repo]({{< relref "/unit/installation.md#installation-community-repos" >}}), + check whether a suitable language package is + available there. +- If you want a customized yet reusable solution, + [prepare]({{< relref "/unit/howto/modules.md#modules-pkg" >}}) + your own package to be installed beside Unit. + +### Packaging Custom Modules {#modules-pkg} + +There's always a chance that you need to run a language version that isn't yet +available among the official Unit +[pacakges]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). +but still want to benefit from the convenience of a packaged installation. In +this case, you can build your own package to be installed alongside the +official distribution, adding the latter as a prerequisite. + +Here, we are packaging a custom PHP 7.3 +[module]({{< relref "/unit/howto/source.md#source-modules" >}}) +to be installed next to the official Unit package; +adjust the command samples as needed to fit your scenario. + +{{< note >}} +For details of building Unit language modules, see the source code +[now-to]({{< relref "/unit/howto/source.md#source-modules" >}}); it also describes building +[Unit]({{< relref "/unit/howto/source.md" >}}) itself. +For more packaging examples, see our package +[sources](https://github.com/nginx/unit/tree/master/pkg). +{{< /note >}} + + + + + +{{< tabs name="packages">}} +{{% tab name=".deb" %}} + +Assuming you are packaging for the current system and have the official +Unit package installed: + +1. Make sure to install the + [prerequisites]({{< relref "/unit/howto/source.md#source-prereq-build" >}}) + for the package. In our example, it's PHP 7.3 on Debian 10: + + ```console + # apt update + ``` + + ```console + # apt install ca-certificates apt-transport-https debian-archive-keyring # Needed to install the php7.3 package from the PHP repo + ``` + + ```console + # curl --output /usr/share/keyrings/php-keyring.gpg \ + https://packages.sury.org/php/apt.gpg # Adding the repo key to make it usable + ``` + + ```console + # echo "deb [signed-by=/usr/share/keyrings/php-keyring.gpg] \ + https://packages.sury.org/php/ buster main" > /etc/apt/sources.list.d/php.list + ``` + + ```console + # apt update + ``` + + ```console + # apt install php7.3 + ``` + + ```console + # apt install php-dev libphp-embed # Needed to build the module and the package + ``` + +1. Create a staging directory for your package: + + ```console + $ export UNITTMP=$(mktemp -d -p /tmp -t unit.XXXXXX) + $ mkdir -p $UNITTMP/unit-php7.3/DEBIAN + $ cd $UNITTMP + ``` + + This creates a folder structure fit for `dpkg-deb`; the + **DEBIAN** folder will store the package definition. + +1. Run `unitd --version` and note the `./configure` + [flags]({{< relref "/unit/howto/source.md#source-config-src" >}}) + for later use, omitting `--ld-opt` and `--njs`: + + ```console + $ unitd --version + + unit version: {{< param "unitversion" >}} + configured as ./configure # Note the flags, omitting --ld-opt and --njs + ``` + +1. Download Unit's sources, + [configure]({{< relref "/unit/howto/source.md#source-modules" >}}) + and build your custom module, then put it where Unit will find it: + + ```console + $ curl -O https://sources.nginx.org/unit/unit-{{< param "unitversion" >}}.tar.gz + $ tar xzf unit-{{< param "unitversion" >}}.tar.gz # Puts Unit's sources in the unit-{{< param "unitversion" >}} subdirectory + $ cd unit-{{< param "unitversion" >}} + $ ./configure FLAGS # Use the ./configure flags noted in the previous step + $ ./configure php --module=php7.3 --config=php-config # Configures the module itself + $ make php7.3 # Builds the module in the build/ subdirectory + $ mkdir -p $UNITTMP/unit-php7.3/MODULESPATH # Use the module path set by ./configure or by default + $ mv build/php7.3.unit.so $UNITTMP/unit-php7.3/MODULESPATH # Adds the module to the package + ``` + +1. Create a **$UNITTMP/unit-php7.3/DEBIAN/control** + [file](https://www.debian.org/doc/debian-policy/ch-controlfields.html), + listing **unit** with other dependencies: + + ```control + Package: unit-php7.3 + Version: {{< param "unitversion" >}} + Comment0: Use Unit's package version for consistency: 'apt show unit | grep Version' + Architecture: amd64 + Comment1: To get current architecture, run 'dpkg --print-architecture' + Comment2: For a list of other options, run 'dpkg-architecture -L' + Depends: unit (= {{< param "unitversion" >}}-1~buster), php7.3, libphp-embed + Comment3: Specify Unit's package version to avoid issues when Unit updates + Comment4: Again, run 'apt show unit | grep Version' to get this value + Maintainer: Jane Doe + Description: Custom PHP 7.3 language module for NGINX Unit {{< param "unitversion" >}} + ``` + + Save and close the file. + +1. Build and install the package: + + ```console + $ dpkg-deb -b $UNITTMP/unit-php7.3 + # dpkg -i $UNITTMP/unit-php7.3.deb + ``` + +{{% /tab %}} +{{% tab name=".rpm" %}} + +Assuming you are packaging for the current system and have the official +Unit package installed: + +1. Make sure to install the + [prerequisites]({{< relref "/unit/howto/source.md#source-prereq-build" >}}) + for the package. In our example, it's PHP 7.3 on Fedora 30: + + ```console + # yum install -y php-7.3.8 + ``` + + ```console + # yum install php-devel php-embedded + ``` + +1. Install RPM development tools and prepare the directory structure: + + ```console + # yum install -y rpmdevtools + ``` + + ```console + $ rpmdev-setuptree + ``` + +1. Create a **.spec** [file](https://rpm-packaging-guide.github.io/#what-is-a-spec-file) + to store build commands for your custom package: + + ```console + $ cd ~/rpmbuild/SPECS + ``` + + ```console + $ rpmdev-newspec unit-php7.3 + ``` + +1. Run `unitd --version` and note the `./configure` + [flags]({{< relref "/unit/howto/source.md#source-config-src" >}}) for later use, omitting + `--ld-opt` and `--njs`: + + ```console + $ unitd --version + + unit version: {{< param "unitversion" >}} + configured as ./configure FLAGS # Note the flags, omitting --ld-opt and --njs + ``` + +1. Edit the **unit-php7.3.spec** file, adding the commands that + download Unit's sources, + [configure]({{< relref "/unit/howto/source.md#source-modules" >}}) + and build your custom module, then put it where Unit will find it: + + ```spec + Name: unit-php7.3 + Version: {{< param "unitversion" >}} + # Use Unit's package version for consistency: 'yum info unit | grep Version' + Release: 1%{?dist} + Summary: Custom language module for NGINX Unit + + License: ASL 2.0 + # Unit uses ASL 2.0; your license depends on the language you are packaging + URL: https://example.com + BuildRequires: gcc + BuildRequires: make + BuildRequires: php-devel + BuildRequires: php-embedded + Requires: unit = {{< param "unitversion" >}} + # Specify Unit's package version to avoid issues when Unit updates + # Again, run 'yum info unit | grep Version' to get this value + Requires: php >= 7.3 + Requires: php-embedded + + %description + Custom language module for NGINX Unit {{< param "unitversion" >}} (https://unit.nginx.org). + + Maintainer: Jane Doe + + %prep + curl -O https://sources.nginx.org/unit/unit-{{< param "unitversion" >}}.tar.gz + # Downloads Unit's sources + tar --strip-components=1 -xzf unit-{{< param "unitversion" >}}.tar.gz + # Extracts them locally for compilation steps in the %build section + + %build + ./configure FLAGS W/O --LD-OPT & --NJS # The ./configure flags, except for --ld-opt and --njs + ./configure php --module=php7.3 --config=php-config + # Configures the module itself + make php7.3 + # Builds the module + + %install + DESTDIR=%{buildroot} make php7.3-install + # Adds the module to the package + + %files + %attr(0755, root, root) MODULESPATH/php7.3.unit.so + # Lists the module as package contents to include it in the package build + # Use the module path set by ./configure or by default + + ``` + + Save and close the file. + + +1. Build and install the package: + + ```console + $ rpmbuild -bb unit-php7.3.spec + + ... + Wrote: /home/user/rpmbuild/RPMS//unit-php7.3-..rpm + ... + ``` + + ```console + # yum install -y /home/user/rpmbuild/RPMS//unit-php7.3-..rpm + ``` + +{{% /tab %}} +{{< /tabs >}} diff --git a/content/unit/howto/overview.md b/content/unit/howto/overview.md new file mode 100644 index 000000000..fc9a92ba4 --- /dev/null +++ b/content/unit/howto/overview.md @@ -0,0 +1,100 @@ +--- +title: Overview +weight: 100 +toc: true +--- + +This section describes various real-life situations and issues that you may +experience with Unit. + + +## How-to guides + +- [Unit in Docker]({{< relref "/unit/howto/docker.md">}}) +: Configure a standalone Unit or a Unit-run app in a Docker + container. +- [Building from source]({{< relref "/unit/howto/source.md">}}) +: Build Unit and its language modules from source code. +- [Unit in Ansible]({{< relref "/unit/howto/ansible.md">}}) +: Use a third-party Ansible collection to automate Unit + deployment. +- [NGINX integration]({{< relref "/unit/howto/integration.md">}}) +: Front or secure Unit with NGINX. +- [TLS with Certbot]({{< relref "/unit/howto/certbot.md">}}) +: Use EFF's Certbot with Unit to simplify certificate + manipulation. +- [Working with language modules]({{< relref "/unit/howto/modules.md">}}) +: Build new modules or prepare custom packages for + Unit. +- [App samples]({{< relref "/unit/howto/samples.md">}}) +: Reuse sample app configurations for all languages + supported by Unit. +- [Security checklist]({{< relref "/unit/howto/security.md">}}) +: Recommendations and considerations for hardening Unit. +- [Walkthrough]({{< relref "/unit/howto/walkthrough.md">}}) +: Follow an end-to-end guide to application configuration + in Unit. + +## Frameworks {#howto-frameworks} + +With Unit, you can configure a diverse range of applications based on the +following frameworks: + +- [Bottle]({{< relref "/unit/howto/frameworks/bottle.md">}}) +- [CakePHP]({{< relref "/unit/howto/frameworks/cakephp.md">}}) +- [Catalyst]({{< relref "/unit/howto/frameworks/catalyst.md">}}) +- [CodeIgniter]({{< relref "/unit/howto/frameworks/codeigniter.md">}}) +- [Django]({{< relref "/unit/howto/frameworks/django.md">}}) +- [DjangoChannels]({{< relref "/unit/howto/frameworks/djangochannels.md">}}) +- [Express]({{< relref "/unit/howto/frameworks/express.md">}}) +- [Falcon]({{< relref "/unit/howto/frameworks/falcon.md">}}) +- [FastAPI]({{< relref "/unit/howto/frameworks/fastapi.md">}}) +- [Flask]({{< relref "/unit/howto/frameworks/flask.md">}}) +- [Guillotina]({{< relref "/unit/howto/frameworks/guillotina.md">}}) +- [Koa]({{< relref "/unit/howto/frameworks/koa.md">}}) +- [Laravel]({{< relref "/unit/howto/frameworks/laravel.md">}}) +- [Lumen]({{< relref "/unit/howto/frameworks/lumen.md">}}) +- [Pyramid]({{< relref "/unit/howto/frameworks/pyramid.md">}}) +- [Quart]({{< relref "/unit/howto/frameworks/quart.md">}}) +- [Responder]({{< relref "/unit/howto/frameworks/responder.md">}}) +- [Rails]({{< relref "/unit/howto/frameworks/rails.md">}}) +- [Sanic]({{< relref "/unit/howto/frameworks/sanic.md">}}) +- [SpringBoot]({{< relref "/unit/howto/frameworks/springboot.md">}}) +- [Starlette]({{< relref "/unit/howto/frameworks/starlette.md">}}) +- [Symfony]({{< relref "/unit/howto/frameworks/symfony.md">}}) +- [Yii]({{< relref "/unit/howto/frameworks/yii.md">}}) +- [Zope]({{< relref "/unit/howto/frameworks/zope.md">}}) + + +# Applications + +You can also make use of detailed setup instructions for popular web apps such +as: + +- [Apollo]({{< relref "/unit/howto/apps/apollo.md">}}) +- [Bugzilla]({{< relref "/unit/howto/apps/bugzilla.md">}}) +- [Datasette]({{< relref "/unit/howto/apps/datasette.md">}}) +- [DokuWiki]({{< relref "/unit/howto/apps/dokuwiki.md">}}) +- [Drupal]({{< relref "/unit/howto/apps/drupal.md">}}) +- [Grafana]({{< relref "/unit/howto/apps/grafana.md">}}) +- [Jira]({{< relref "/unit/howto/apps/jira.md">}}) +- [Joomla]({{< relref "/unit/howto/apps/joomla.md">}}) +- [Mailman]({{< relref "/unit/howto/apps/mailman.md">}}) +- [Matomo]({{< relref "/unit/howto/apps/matomo.md">}}) +- [MediaWiki]({{< relref "/unit/howto/apps/mediawiki.md">}}) +- [Mercurial]({{< relref "/unit/howto/apps/mercurial.md">}}) +- [MODX]({{< relref "/unit/howto/apps/modx.md">}}) +- [Moin]({{< relref "/unit/howto/apps/moin.md">}}) +- [Nextcloud]({{< relref "/unit/howto/apps/nextcloud.md">}}) +- [OpenGrok]({{< relref "/unit/howto/apps/opengrok.md">}}) +- [phpBB]({{< relref "/unit/howto/apps/phpbb.md">}}) +- [phpMyAdmin]({{< relref "/unit/howto/apps/phpmyadmin.md">}}) +- [Plone]({{< relref "/unit/howto/apps/plone.md">}}) +- [Redmine]({{< relref "/unit/howto/apps/redmine.md">}}) +- [ReviewBoard]({{< relref "/unit/howto/apps/reviewboard.md">}}) +- [Roundcube]({{< relref "/unit/howto/apps/roundcube.md">}}) +- [Trac]({{< relref "/unit/howto/apps/trac.md">}}) +- [WordPress]({{< relref "/unit/howto/apps/wordpress.md">}}) + +If you are interested in a specific use case not yet listed here, please [post +a feature request](https://github.com/nginx/documentation/issues) on GitHub. diff --git a/content/unit/howto/samples.md b/content/unit/howto/samples.md new file mode 100644 index 000000000..8f6c906b1 --- /dev/null +++ b/content/unit/howto/samples.md @@ -0,0 +1,822 @@ +--- +title: App samples +toc: true +weight: 800 +--- + + +{{< note >}} +These steps assume Unit was already +[installed]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +with the language module for each app. + +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Go {#sample-go} + +Let's configure the following basic app, saved as **/www/app.go**: + +```go +package main + +import ( + "io" + "net/http" + "unit.nginx.org/go" +) + +func main() { + http.HandleFunc("/",func (w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "Hello, Go on Unit!") + }) + unit.ListenAndServe(":8080", nil) +} +``` + +First, create a [Go module](https://go.dev/blog/using-go-modules), `go +get` Unit's package, and build your application: + +```console + $ go mod init example.com/app # Your Go module designation + $ go get unit.nginx.org/go@{{< param "unitversion" >}} + $ go build -o /www/app /www/app.go +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-go" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/go" + } + }, + "applications": { + "go": { + "type": "external", + "working_directory": "/www/", + "executable": "/www/app" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, Go on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.go.txt) +or use a more elaborate app example: + +```go + package main + + import ( + "crypto/sha256"; + "fmt"; + "io"; + "io/ioutil"; + "encoding/json"; + "net/http"; + "strings"; + "unit.nginx.org/go" + ) + + func formatRequest(r *http.Request) string { + + h := make(map[string]string) + m := make(map[string]string) + t := make(map[string]interface{}) + + m["message"] = "Unit reporting" + m["agent"] = "NGINX Unit {{< param "unitversion" >}}" + + body, _ := ioutil.ReadAll(r.Body) + m["body"] = fmt.Sprintf("%s", body) + + m["sha256"] = fmt.Sprintf("%x", sha256.Sum256([]byte(m["body"]))) + + data, _ := json.Marshal(m) + for name, _ := range r.Header { + h[strings.ToUpper(name)] = r.Header.Get(name) + } + _ = json.Unmarshal(data, &t) + t["headers"] = h + + js, _ := json.MarshalIndent(t, "", " ") + + return fmt.Sprintf("%s", js) + } + + func main() { + http.HandleFunc("/",func (w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + io.WriteString(w, formatRequest(r)) + }) + unit.ListenAndServe(":8080", nil) + } +``` + +## Java {#sample-java} + +Let's configure the following basic app, saved as **/www/index.jsp**: + +```jsp +<%@ page language="java" contentType="text/plain" %> +<%= "Hello, JSP on Unit!" %> +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-java" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/java" + } + }, + "applications": { + "java": { + "type": "java", + "webapp": "/www/" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, JSP on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.java.txt) +or use a more elaborate app example (you'll +need to [download](https://cliftonlabs.github.io/json-simple/) and +[add]({{< relref "/unit/configuration.md#configuration-java" >}}) the +`json-simple` library to your app's **classpath** option): + +```jsp + <%@ page language="java" contentType="application/json; charset=utf-8" %> + <%@ page import="com.github.cliftonlabs.json_simple.JsonObject" %> + <%@ page import="com.github.cliftonlabs.json_simple.Jsoner" %> + <%@ page import="java.io.BufferedReader" %> + <%@ page import="java.math.BigInteger" %> + <%@ page import="java.nio.charset.StandardCharsets" %> + <%@ page import="java.security.MessageDigest" %> + <%@ page import="java.util.Enumeration" %> + <% + JsonObject r = new JsonObject(); + + r.put("message", "Unit reporting"); + r.put("agent", "NGINX Unit {{< param "unitversion" >}}"); + + JsonObject headers = new JsonObject(); + Enumeration h = request.getHeaderNames(); + while (h.hasMoreElements()) { + String name = (String)h.nextElement(); + headers.put(name, request.getHeader(name)); + } + r.put("headers", headers); + + BufferedReader br = request.getReader(); + String body = ""; + String line = br.readLine(); + while (line != null) { + body += line; + line = br.readLine(); + } + r.put("body", body); + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] bytes = md.digest(body.getBytes(StandardCharsets.UTF_8)); + BigInteger number = new BigInteger(1, bytes); + StringBuilder hex = new StringBuilder(number.toString(16)); + r.put("sha256", hex.toString()); + + out.println(Jsoner.prettyPrint((Jsoner.serialize(r)))); + %> +``` + +## Node.js {#sample-nodejs} + +Let's configure the following basic app, saved as **/www/app.js**: + +```javascript +#!/usr/bin/env node + +require("unit-http").createServer(function (req, res) { + res.writeHead(200, {"Content-Type": "text/plain"}); + res.end("Hello, Node.js on Unit!") +}).listen() +``` + +It's important to use unit-http instead of the regular http module. + +Make it executable and link the Node.js language package you've +[installed]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) + +```console +$ cd /www +``` + +```console +$ chmod +x app.js +``` + +```console +$ npm link unit-http +``` + +Upload the [app config]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/node" + } + }, + "applications": { + "node": { + "type": "external", + "working_directory": "/www/", + "executable": "app.js" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, Node.js on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.nodejs.txt) +or use a more elaborate app example: + +```javascript + #!/usr/bin/env node + + const cr = require("crypto") + const bd = require("body") + require("unit-http").createServer(function (req, res) { + bd (req, res, function (err, body) { + res.writeHead(200, {"Content-Type": "application/json; charset=utf-8"}) + + var r = { + "agent": "NGINX Unit {{< param "unitversion" >}}", + "message": "Unit reporting" + } + + r["headers"] = req.headers + r["body"] = body + r["sha256"] = cr.createHash("sha256").update(r["body"]).digest("hex") + + res.end(JSON.stringify(r, null, " ").toString("utf8")) + }) + }).listen() +``` + +{{< note >}} +You can run a version of the same app +[without]({{< relref "/unit/configuration.md#configuration-nodejs-loader" >}}) +requiring the **unit-http** module explicitly. +{{< /note >}} + +## Perl {#sample-perl} + +Let's configure the following basic app, saved as **/www/app.psgi**: + +```perl +my $app = sub { + return [ + "200", + [ "Content-Type" => "text/plain" ], + [ "Hello, Perl on Unit!" ], + ]; +}; +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-perl" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/perl" + } + }, + "applications": { + "perl": { + "type": "perl", + "working_directory": "/www/", + "script": "/www/app.psgi" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, Perl on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.perl.txt) +or use a more elaborate app example: + +```perl + use strict; + + use Digest::SHA qw(sha256_hex); + use JSON; + use Plack; + use Plack::Request; + + my $app = sub { + my $env = shift; + my $req = Plack::Request->new($env); + my $res = $req->new_response(200); + $res->header("Content-Type" => "application/json; charset=utf-8"); + + my $r = { + "message" => "Unit reporting", + "agent" => "NGINX Unit {{< param "unitversion" >}}", + "headers" => $req->headers->psgi_flatten(), + "body" => $req->content, + "sha256" => sha256_hex($req->content), + }; + + my $json = JSON->new(); + $res->body($json->utf8->pretty->encode($r)); + + return $res->finalize(); + }; +``` + +## PHP {#sample-php} + +Let's configure the following basic app, saved as **/www/index.php**: + +```php + +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-php" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/php" + } + }, + "applications": { + "php": { + "type": "php", + "root": "/www/" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation + +$ curl http://localhost:8080 + + Hello, PHP on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.php.txt) +or use a more elaborate app example: + +```php + "Unit reporting", + "agent" => "NGINX Unit {{< param "unitversion" >}}" + ); + + foreach ($_SERVER as $header => $value) + if (strpos($header, "HTTP_") === 0) + $r["headers"][$header] = $value; + + $r["body"] = file_get_contents("php://input"); + $r["sha256"] = hash("sha256", $r["body"]); + + echo json_encode($r, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + ?> +``` + +## Python {#sample-python} + +Let's configure the following basic app, saved as **/www/wsgi.py**: + +```python +def application(environ, start_response): + start_response("200 OK", [("Content-Type", "text/plain")]) + return (b"Hello, Python on Unit!") +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-python" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/python" + } + }, + "applications": { + "python": { + "type": "python", + "path": "/www/", + "module": "wsgi" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, Python on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.python.txt) +or use a more elaborate app example: + +```python + import hashlib, json + + def application(env, start_response): + start_response("200 OK", [("Content-Type", + "application/json; charset=utf-8")]) + + r = {} + + r["message"] = "Unit reporting" + r["agent"] = "NGINX Unit {{< param "unitversion" >}}" + + r["headers"] = {} + for header in [_ for _ in env.keys() if _.startswith("HTTP_")]: + r["headers"][header] = env[header] + + bytes = env["wsgi.input"].read() + r["body"] = bytes.decode("utf-8") + r["sha256"] = hashlib.sha256(bytes).hexdigest() + + return json.dumps(r, indent=4).encode("utf-8") +``` + +## Ruby {#sample-ruby} + +Let's configure the following basic app, saved as **/www/config.ru**: + +```ruby +app = Proc.new do |env| + ["200", { + "Content-Type" => "text/plain", + }, ["Hello, Ruby on Unit!"]] +end + +run app +``` + +Upload the +[app config]({{< relref "/unit/configuration.md#configuration-ruby" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "*:8080": { + "pass": "applications/ruby" + } + }, + "applications": { + "ruby": { + "type": "ruby", + "working_directory": "/www/", + "script": "config.ru" + } + } + }' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + Hello, Ruby on Unit! +``` + +Try this sample out with the Dockerfile +[ here](/unit/downloads/Dockerfile.ruby.txt) +or use a more elaborate app example: + +```ruby + require "digest" + require "json" + + app = Proc.new do |env| + body = env["rack.input"].read + r = { + "message" => "Unit reporting", + "agent" => "NGINX Unit {{< param "unitversion" >}}", + "body" => body, + "headers" => env.select { |key, value| key.include?("HTTP_") }, + "sha256" => Digest::SHA256.hexdigest(body) + } + + ["200", { + "Content-Type" => "application/json; charset=utf-8", + }, [JSON.pretty_generate(r)]] + end; + + run app +``` + +## WebAssembly (Wasm) {#sample-wasm} + +{{< tabs name="web-assembly" >}} +{{% tab name="wasm-wasi-component" %}} + +Instead of dealing with bytecode, let's build a Unit-capable Rust app +and compile it into a WebAssembly (Wasm) component. + +Make sure you have the Rust toolchain (cargo, rustc, etc.) installed. +We recommend using [rustup](https://rustup.rs/) to get started. + +This example was built with **rustc** version 1.76.0. + +Start by adding the wasm32-wasi support as a compilation target for **rustc** + +```console +$ rustup target add wasm32-wasi +``` + +Next, install cargo component. This simplifies building a WebAssembly +component from Rust Code, making it the recommended method. + +```console +$ cargo install cargo-component +``` + +Currently, the fastest way to get started with WebAssembly components using WASI +0.2 wasi-http API is the **hello-wasi-http** demo application by +Dan Gohman. Clone the repository and build the component running +the following command: + +```console +$ git clone https://github.com/sunfishcode/hello-wasi-http +``` + +```console +$ cd hello-wasi-http +``` + +```console +$ cargo component build +``` + +The output of the build command should be similar to this: + +```console +$ cargo component build +Compiling hello-wasi-http v0.0.0 (/home/unit-build/hello-wasi-http) +Finished dev [unoptimized + debuginfo] target(s) in 0.17s +Creating component /home/unit-build/hello-wasi-http/target/wasm32-wasi/debug/hello_wasi_http.wasm +$ +``` + +This creates a WebAssembly component you can deploy on Unit using the +following Unit configuration. Make sure you point the **component** path +to the WebAssembly component you have just created. Create a +**config.json** file: + +```json +{ + "listeners": { + "127.0.0.1:8080": { + "pass": "applications/wasm" + } + }, + "applications": { + "wasm": { + "type": "wasm-wasi-component", + "component": "/home/unit-build/hello-wasi-http/target/wasm32-wasi/debug/hello_wasi_http.wasm" + } + } +} +``` + +Apply the Unit configuration by using the CLI: + +```console +$ unitc /config < config.json +``` + +Or by sending it manually to Units control API: + +```console +$ cat config.json | curl -X PUT -d @- --unix-socket /path/to/control.unit.sock http://localhost/config/ +``` + +Congratulations! You have created your very first WebAssembly component +on Unit! Send a GET Request to your configured listener. + +```console +$ curl http://localhost:8080 +``` + +{{% /tab %}} +{{% tab name="unit-wasm" %}} + +{{< warning >}} +Unit 1.32.0 and later support the WebAssembly component +Model and WASI 0.2 APIs. +We recommend to use the new implementation. +{{< /warning >}} + +Instead of dealing with bytecode, let's build a Unit-capable +Rust app and compile it into WebAssembly. + +{{< note >}} +Currently, WebAssembly support is provided as a Technology Preview. +This includes support for compiling Rust and C code into Unit-compatible WebAssembly, +using our SDK in the form of the the `libunit-wasm` library. +For details, see our `unit-wasm` [repository](https://github.com/nginx/unit-wasm) +on GitHub. +{{< /note >}} + +First, install the WebAssembly-specific Rust tooling: + +```console +$ rustup target add wasm32-wasi +``` + +Next, initialize a new Rust project with a library target +(apps are loaded by Unit's WebAssembly module as dynamic libraries). +Then, add our **unit-wasm** crate to enable the `libunit-wasm` library: + +```console +$ cargo init --lib wasm_on_unit +``` + +```console +$ cd wasm_on_unit/ +``` + +```console +$ cargo add unit-wasm +``` + +Append the following to **Cargo.toml**: + +```toml +[lib] +crate-type = ["cdylib"] +``` + +Save some sample code from our `unit-wasm` repo as **src/lib.rs**: + +```console +wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/main/examples/rust/echo-request/src/lib.rs +``` + +Build the Rust module with WebAssembly as the target: + +```console +$ cargo build --target wasm32-wasi +``` + +This yields the **target/wasm32-wasi/debug/wasm_on_unit.wasm** file +(path may depend on other options). + +Upload the [app config]({{< relref "/unit/configuration.md#configuration-wasm" >}}) +to Unit and test it: + +```console +# curl -X PUT --data-binary '{ + "listeners": { + "127.0.0.1:8080": { + "pass": "applications/wasm" + } + }, + + "applications": { + "wasm": { + "type": "wasm", + "module": "/path/to/wasm_on_unit/target/wasm32-wasi/debug/wasm_on_unit.wasm", + "request_handler": "uwr_request_handler", + "malloc_handler": "luw_malloc_handler", + "free_handler": "luw_free_handler", + "module_init_handler": "uwr_module_init_handler", + "module_end_handler": "uwr_module_end_handler" + } + } +}' --unix-socket /path/to/control.unit.sock http://localhost/config/ # Path to Unit's control socket in your installation +``` + +```console +$ curl http://localhost:8080 + + * Welcome to WebAssembly in Rust on Unit! [libunit-wasm (0.3.0/0x00030000)] * + + [Request Info] + REQUEST_PATH = / + METHOD = GET + VERSION = HTTP/1.1 + QUERY = + REMOTE = 127.0.0.1 + LOCAL_ADDR = 127.0.0.1 + LOCAL_PORT = 8080 + SERVER_NAME = localhost + + [Request Headers] + Host = localhost:8080 + User-Agent = curl/8.2.1 + Accept = */* +``` + +Further, you can research the Unit-based WebAssembly app internals in more depth. +Clone the `unit-wasm` repository and build the examples in C and Rust +(may require `clang` and `lld`): + +```console +$ git clone https://github.com/nginx/unit-wasm/ +``` + +```console +$ cd unit-wasm +``` + +```console +$ make help # Explore your options first +``` + +```console +$ make WASI_SYSROOT=/path/to/wasi-sysroot/ examples # C examples +``` + +```console +$ make WASI_SYSROOT=/path/to/wasi-sysroot/ examples-rust # Rust examples +``` + +{{< note >}} +If the above commands fail like this: + +```console +wasm-ld: error: cannot open .../lib/wasi/libclang_rt.builtins-wasm32.a: No such file or directory +clang: error: linker command failed with exit code 1 (use -v to see invocation) +``` + +Download and install the library to `clang`'s run-time dependency directory: + +```console +$ wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/libclang_rt.builtins-wasm32-wasi-20.0.tar.gz \ + | tar zxf - # Unpacks to lib/wasi/ in the current directory +``` + +```console +$ clang -print-runtime-dir # Double-check the run-time directory, which is OS-dependent + + /path/to/runtime/dir/linux +``` + +```console +# mkdir /path/to/runtime/dir/wasi # Note the last part of the pathname +``` + +```console +# cp lib/wasi/libclang_rt.builtins-wasm32.a /path/to/runtime/dir/wasi/ +``` + +{{< /note >}} + +{{% /tab %}} +{{< /tabs >}} diff --git a/content/unit/howto/security.md b/content/unit/howto/security.md new file mode 100644 index 000000000..790f5f9bd --- /dev/null +++ b/content/unit/howto/security.md @@ -0,0 +1,768 @@ +--- +title: Security checklist +weight: 800 +toc: true +--- + +At its core, Unit has security as one of its top priorities; our development +follows the appropriate best practices focused on making the code robust and +solid. However, even the most hardened system requires proper setup, +configuration, and maintenance. + +This guide lists the steps to protect your Unit from installation to individual +app configuration. + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Update Unit regularly {#security-update} + +**Rationale**: Each release introduces [bug fixes and new +features]({{< relref "/unit/changes.md" >}}) that improve your installation's security. + +**Actions**: Follow our latest [news](https://mailman.nginx.org/mailman3/lists/unit.nginx.org/) +and upgrade to new versions shortly after they are released. + +
+Details + + +Specific upgrade steps depend on your installation method: + +- The recommended option is to use our official + [packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) + or Docker + [images]({{< relref "/unit/installation.md#installation-docker" >}}); + with them, it's just a matter of updating + **unit-*** packages with your package manager of choice or + switching to a newer image. + +- If you use a third-party installation + [method]({{< relref "/unit/installation.md#installation-community-repos" >}}), + consult the maintainer's documentation for details. + +- If you install Unit from + [source files]({{< relref "/unit/howto/source.md" >}}), + rebuild and reinstall Unit and its modules from scratch. + +
+ +## Secure socket and state {#security-socket-state} + +**Rationale**: Your +[control socket and state directory]({{< relref "/unit/howto/source.md#source-dir" >}}) +provide unlimited access to Unit's configuration, which +calls for stringent protection. + +**Actions**: Default configuration in our +[official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +is usually sufficient; if you use another installation method, ensure the control +socket and the state directory are safe. + +
+Control socket + + + +If you use a UNIX control socket, ensure it is available to **root** +only: + +```console +$ unitd -h + + ... + --control ADDRESS set address of control API socket + default: "unix:/default/path/to/control.unit.sock" # Build-time setting, can be overridden + +$ ps ax | grep unitd + + ... unit: main {{< param "unitversionv" >}} [... --control /path/to/control.sock ...] # Make sure to check for runtime overrides + +# ls -l /path/to/control.unit.sock # If it's overridden, use the runtime setting + + srw------- 1 root root 0 ... /path/to/control.unit.sock + +``` + +UNIX domain sockets aren't network accessible; for remote access, use +[NGINX]({{< relref "/unit/howto/integration.md#nginx-secure-api" >}}) oor a solution such as SSH: + +```console +$ ssh -N -L ./here.sock:/path/to/control.unit.sock root@unit.example.com & # Local socket | Socket on the Unit server; use a real path in your command | Unit server hostname +``` + +```console +$ curl --unix-socket ./here.sock # Use the local socket to configure Unit + + { + "certificates": {}, + "config": { + "listeners": {}, + "applications": {} + } + } +``` + +If you prefer an IP-based control socket, avoid public IPs; they expose the +[control API]({{< relref "/unit/controlapi.md#configuration-api" >}}) +and all its capabilities. This means your Unit instance can be manipulated by +whomever is physically able to connect: + +```console +# unitd --control 203.0.113.14:8080 +``` + +```console +$ curl 203.0.113.14:8080 + + { + "certificates": {}, + "config": { + "listeners": {}, + "applications": {} + } + } +``` + +Instead, opt for the loopback address to ensure all access is local to your +server: + +```console +# unitd --control 127.0.0.1:8080 +``` + +```console +$ curl 203.0.113.14:8080 + + curl: (7) Failed to connect to 203.0.113.14 port 8080: Connection refused +``` + +However, any processes local to the same system can access the local socket, +which calls for additional measures. A go-to solution would be using NGINX +to [proxy]({{< relref "/unit/howto/integration.md#nginx-secure-api" >}}) +Unit's control API. + +
+ +
+State directory + + + +The state directory stores Unit's internal configuration between launches. +Avoid manipulating it or relying on its contents even if tempted to do so. +Instead, use only the control API to manage Unit's configuration. + +Also, the state directory should be available only to **root** (or the +user that the **main** +[process]({{< relref "/unit/howto/security.md#security-apps" >}}) +runs as): + +```console +$ unitd -h + + ... + --state DIRECTORY set state directory name + default: /default/path/to/unit/state/ # Build-time setting, can be overridden +``` + +```console +$ ps ax | grep unitd + + ... unit: main {{< param "unitversionv" >}} [... --state /path/to/unit/state/ ...] # Make sure to check for runtime overrides +``` + +```console +# ls -l /path/to/unit/state/ # If it's overridden, use the runtime setting + + drwx------ 2 root root 4096 ... +``` + +
+ +## Configure SSL/TLS {#security-ssl} + +**Rationale**: To protect your client connections in production scenarios, +configure SSL certificate bundles for your Unit installation. + +**Actions**: For details, see [SSL/TLS configuration]({{< relref "/unit/certificates.md#configuration-ssl" >}}) and [TLS with certbot]({{< relref "/unit/howto/certbot.md" >}}). + +## Error-proof your routes {#security-routes} + +**Rationale**: Arguably, [routes]({{< relref "/unit/configuration.md#configuration-routes" >}}) +are the most flexible and versatile part of the Unit configuration. Thus, they must be as +clear and robust as possible to avoid loose ends and gaping holes. + +**Actions**: Familiarize yourself with the +[matching]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}) +logic and double-check all +[patterns]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns" >}}) +that you use. + +
+Details + + + + +Some considerations: + +- Mind that + [variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}) + contain arbitrary user-supplied request values; variable-based **pass** values in + [listeners]({{< relref "/unit/configuration.md#configuration-listeners" >}}) + and + [routes]({{< relref "/unit/configuration.md#configuration-routes-action" >}}) + must account for malicious requests, or the requests must be properly filtered. + +- Create + [matching rules]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}) + to formalize the restrictions of your Unit instance and the apps it runs. + +- Configure + [shares]({{< relref "/unit/configuration.md#configuration-static" >}}) + only for directories and files you intend to make public. + +
+ +## Protect app data {#security-apps} + +**Rationale**: Unit's architecture involves many processes that operate +together during app delivery; improper process permissions can make sensitive +files available across apps or even publicly. + +**Actions**: Properly configure your app directories and shares: apps and the +router process need access to them. Still, avoid loose rights such as the +notorious **777**, instead assigning them on a need-to-know basis. + +
+File permissions + + + +To configure file permissions for your apps, check Unit's build-time and +run-time options first: + +```console +$ unitd -h + + ... + --user USER # set non-privileged processes to run as specified user | default: unit_user (Build-time setting, can be overridden) + + --group GROUP set non-privileged processes to run as specified group + default: user's primary group +``` + +```console +$ ps ax | grep unitd + + ... unit: main {{< param "unitversionv" >}} [... --user unit_user --group unit_group ...] # Make sure to check for runtime overrides +``` + +In particular, this is the account the router process runs as. Use this +information to set up permissions for the app code or binaries and shared +static files. The main idea is to limit each app to its own files and +directories while simultaneously allowing Unit's router process to access +static files for all apps. + +Specifically, the requirements are as follows: + +- All apps should run as different users so that the permissions can be + configured properly. Even if you run a single app, it's reasonable to + create a dedicated user for added flexibility. + +- An app's code or binaries should be reachable for the user the app runs + as; the static files should be reachable for the router process. Thus, + each part of an app's directory path must have execute permissions + assigned for the respective users. + +- An app's directories should not be available to other apps or + non-privileged system users. The router process should be able to access + the app's static file directories. Accordingly, the app's directories + must have read and execute permissions assigned for the respective users. + +- The files and directories that the app is designed to update should + be writable only for the user the app runs as. + +- The app code should be readable (and executable in case of + [external]({{< relref "/unit/howto/modules.md#modules-ext" >}}) apps) + for the user the app runs as; the static content should be readable for the + router process. + +A detailed walkthrough to guide you through each requirement: + +1. If you have several independent apps, running them with a single user +account poses a security risk. Consider adding a separate system user +and group per each app: + + ```console + # useradd -M app_user # Add user account without home directory + ``` + + ```console + # groupadd app_group + ``` + + ```console + # usermod -L app_user # Deny interactive login + ``` + + ```console + # usermod -a -G app_group app_user # Add user to the group + ``` + + Even if you run a single app, this helps if you add more apps or need to + decouple permissions later. + +1. It's important to add Unit's non-privileged user account to *each* app +group: + + ```console + # usermod -a -G app_group unit_user + ``` + + Thus, Unit's router process can access each app's directory and serve + files from each app's shares. + +1. A frequent source of issues is the lack of permissions for directories +inside a directory path needed to run the app, so check for that if in +doubt. Assuming your app code is stored at **/path/to/app/**: + + ```console + # ls -l / + + # drwxr-xr-x some_user some_group path # Permissions are OK + ``` + + ```console + # ls -l /path/ + + # drwxr-x--- some_user some_group to # Permissions are too restrictive + ``` + + This may be a problem because the **to/** directory isn't owned by + **app_user:app_group** and denies all permissions to non-owners (as + the **---** sequence tells us), so a fix can be warranted: + + ```console + # chmod o+rx /path/to/ # Add read/execute permissions for non-owners + ``` + + Another solution is to add **app_user** to **some_group** + (assuming this was not done before): + + ```console + # usermod -a -G some_group app_user + ``` + +1. Having checked the directory tree, assign ownership and permissions for +your app's directories, making them reachable for Unit and the app: + + ```console + # chown -R app_user:app_group /path/to/app/ # Assign ownership for the app code | Path to the application directory; use a real path in your command + ``` + + ```console + # chown -R app_user:app_group /path/to/static/app/files/ # Assign ownership for the static files | Can be outside the app directory tree; use a real path in your command + ``` + + ```console + # find /path/to/app/ -type d -exec chmod u=rx,g=rx,o= {} \; # Path to the application directory; use a real path in your command | Add read/execute permissions to app code directories for user and group + ``` + + ```console + # find /path/to/static/app/files/ -type d -exec chmod u=rx,g=rx,o= {} \; # Can be outside the app directory tree; use a real path in your command | Add read/execute permissions to static file directories for user and group + ``` + +1. If the app needs to update specific directories or files, make sure +they're writable for the app alone: + + ```console + # chmod u+w /path/to/writable/file/or/directory/ # Add write permissions for the user only; the group shouldn't have them | Repeat for each file or directory that must be writable + ``` + + In case of a writable directory, you may also want to prevent non-owners + from messing with its files: + + ```console + # chmod +t /path/to/writable/directory/ # Sticky bit prevents non-owners from deleting or renaming files | Repeat for each directory that must be writable + + ``` + + {{< note >}} + Usually, apps store and update their data outside the app code + directories, but some apps may mix code and data. In such a case, + assign permissions on an individual basis, making sure you understand + how the app uses each file or directory: is it code, read-only + content, or writable data. + {{< /note >}} + +1. For [embedded]({{< relref "/unit/howto/modules.md#modules-emb" >}}) + apps, it's usually enough to make the + app code and the static files readable: + + ```console + # find /path/to/app/code/ -type f -exec chmod u=r,g=r,o= {} \; # Path to the application's code directory; use a real path in your command | Add read rights to app code for user and group + ``` + + ```console + # find /path/to/static/app/files/ -type f -exec chmod u=r,g=r,o= {} \; # Can be outside the app directory tree; use a real path in your command | Add read rights to static files for user and group + ``` + +1. For + [external]({{< relref "/unit/howto/modules.md#modules-ext" >}}) + apps, additionally make the app code or binaries executable: + + ```console + # find /path/to/app/ -type f -exec chmod u=rx,g=rx,o= {} \; # Path to the application directory; use a real path in your command | Add read and execute rights to app code for user and group + ``` + + ```console + # find /path/to/static/app/files/ -type f -exec chmod u=r,g=r,o= {} \; # Can be outside the app directory tree; use a real path in your command | Add read rights to static files for user and group + ``` + +1. To run a single app, [configure]({{< relref "/unit/configuration.md" >}}) + Unit as follows: + + ```json + { + "listeners": { + "*:80": { /* Or another suitable socket address */ + "pass": "routes" + } + }, + + "routes": [ + { + "action": { + "share": "/path/to/static/app/files/$uri", + /* Router process needs read and execute permissions to serve static content from this directory */ + "fallback": { + "pass": "applications/app" + } + } + } + ], + + "applications": { + "app": { + "type": "...", + "user": "app_user", + "group": "app_group" + } + } + } + ``` + +1. To run several apps side by side, + [configure]({{< relref "/unit/configuration.md" >}}) + them with appropriate user and group names. The following + configuration distinguishes apps based on the request URI, but you can + implement another scheme such as different listeners: + + ```json + { + "listeners": { + "*:80": { /* Or another suitable socket address */ + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": "/app1/*" /* Arbitrary matching condition */ + }, + + "action": { + "share": "/path/to/static/app1/files/$uri", + /* Router process needs read and execute permissions to serve static content from this directory */ + "fallback": { + "pass": "applications/app1" + } + } + }, + + { + "match": { + "uri": "/app2/*" /* Arbitrary matching condition */ + }, + + "action": { + "share": "/path/to/static/app2/files/$uri", + /* Router process needs read and execute permissions to serve static content from this directory */ + "fallback": { + "pass": "applications/app2" + } + } + } + ], + + "applications": { + "app1": { + "type": "...", + "user": "app_user1", + "group": "app_group1" + }, + + "app2": { + "type": "...", + "user": "app_user2", + "group": "app_group2" + } + } + } + ``` + +{{< note >}} +As usual with permissions, different steps may be required if you use +ACLs. +{{< /note >}} +
+ +
+App internals + + + +Unfortunately, quite a few web apps are built in a manner that mixes their +source code, data, and configuration files with static content, which calls +for complex access restrictions. The situation is further aggravated by the +inevitable need for maintenance activities that may leave a footprint of +extra files and directories unrelated to the app's operation. The issue has +several aspects: + +- Storage of code and data at the same locations, which usually happens by +(insufficient) design. You neither want your internal data and code files +to be freely downloadable nor your user-uploaded data to be executable as +code, so configure your routes and apps to prevent both. + +- Exposure of configuration data. Your app-specific settings, **.ini** +or **.htaccess** files, and credentials are best kept hidden from +prying eyes, and your routing configuration should reflect that. + +- Presence of hidden files from versioning, backups by text editors, and +other temporary files. Instead of carving your configuration around +these, it's best to keep your app free of them altogether. + +If these can't be avoided, investigate the inner workings of the app to +prevent exposure, for example: + +```json + { + "routes": { + "app": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*" + ] + /* Handles requests that target PHP scripts to avoid having them served as static files */ + }, + + "action": { + "pass": "applications/app/direct" + } + }, + { + "match": { + "uri": [ + "!/sensitive/*", /* Restricts access to a directory with sensitive data */ + "!/data/*", /* Restricts access to a directory with sensitive data */ + "!/app_config_values.ini", /* Restricts access to a specific file */ + "!*/.*", /* Restricts access to hidden files and directories */ + "!*~" /* Restricts access to temporary files */ + ] + /* Protects files and directories best kept hidden */ + }, + + "action": { + "share": "/path/to/app/static$uri", + /* Serves valid requests with static content | Path to the application's static file directory; use a real path in your configuration */ + + "types": [ + "image/*", + "text/*", + "application/javascript" + ] + /* Limits file types served from the share */ + + "fallback": { + "pass": "applications/app/index" + } + /* Relays all requests not yet served to a catch-all app target */ + } + } + ] + } + } + +``` + +However, this does not replace the need to set up file permissions; use both +[matching rules]({{< relref "/unit/configuration.md#configuration-routes-matching" >}}) +and per-app user +permissions to manage access. For more info and real-life examples, refer +to our app [how-tos]({{< relref "/unit/howto/" >}}). +and the 'File Permissions' callout above. +
+ +
+Unit's process summary + + + +Unit's processes are detailed [elsewhere](https://www.nginx.com/blog/introducing-nginx-unit/), +but here's a synopsis of the different roles they have: + +{{}} +| Process | Privileged? | User and Group | Description | +|--------------|------------|---------------------------------------------------------|-------------| +| **Main** | Yes | Whoever starts the **unitd** executable

; by default, **root**. | Runs as a daemon, spawning Unit's non-privileged and app processes; requires numerous system capabilities and privileges for operation. | +| **Controller** | No | Set by `--user` and `--group` options at [build]({{< relref "/unit/howto/source.md#source-config-src" >}}) or [execution]({{< relref "/unit/howto/source.md#source-startup" >}})

; by default, **unit**. | Serves the control API, accepting reconfiguration requests, sanitizing them, and passing them to other processes for implementation. | +| **Discovery** | No | Set by `--user` and `--group` options at [build]({{< relref "/unit/howto/source.md#source-config-src" >}}) or [execution]({{< relref "/unit/howto/source.md#source-startup" >}})

; by default, **unit**. | Discovers the language modules in the module directory at startup, then quits. | +| **Router** | No | Set by `--user` and `--group` options at [build]({{< relref "/unit/howto/source.md#source-config-src" >}}) or [execution]({{< relref "/unit/howto/source.md#source-startup" >}})

; by default, **unit**. | Serves client requests, accepting them, processing them on the spot, passing them to app processes, or proxying them further; requires access to static content paths you configure. | +| **App processes** | No | Set by per-app **user** and **group** [options]({{< relref "/unit/configuration.md#configuration-applications" >}})

; by default, `--user` and `--group` values. | Serve client requests that are routed to apps; require access to paths and namespaces you configure for the app. | +{{
}} + + +You can check all of the above on your system when Unit is running: + +```console + $ ps aux | grep unit + + ... + root ... unit: main {{< param "unitversionv" >}} + unit ... unit: controller + unit ... unit: router + unit ... unit: "front" application +``` + +The important outtake here is to understand that Unit's non-privileged +processes don't require running as **root**. Instead, they should have +the minimal privileges required to operate, which so far means the ability +to open connections and access the application code and the static files +shared during routing. + +
+ +## Prune debug and access logs {#security-logs} + +**Rationale**: Unit stores potentially sensitive data in its general and access +logs; their size can also become a concern if debug mode is enabled. + +**Actions**: Secure access to the logs and ensure they don't exceed the allowed +disk space. + +
+Details + + + +Unit can maintain two different logs: + +- A general-purpose log that is enabled by default and can be switched to +debug mode for verbosity. + +- An access log that is off by default but can be enabled via the control +API. + +If you enable debug-mode or access logging, rotate these logs with tools +such as `logrotate` to avoid overgrowth. A sample +`logrotate` [configuration](https://man7.org/linux/man-pages/man8/logrotate.8.html#CONFIGURATION_FILE_DIRECTIVES): + +```none +/path/to/unit.log { # Use a real path in your configuration + daily + missingok + rotate 7 + compress + delaycompress + nocreate + notifempty + su root root + postrotate + if [ -f `/path/to/unit.pid` ]; then + /bin/kill -SIGUSR1 `cat /path/to/unit.pid` # Use a real path in your configuration + fi + endscript +} +``` + +To figure out the log and PID file paths: + +```console +$ unitd -h + + ... + --pid FILE set pid filename + default: "/default/path/to/unit.pid" # Build-time setting, can be overridden + + --log FILE set log filename + default: "/default/path/to/unit.log " # Build-time setting, can be overridden + +$ ps ax | grep unitd + + ... unit: main {{< param "unitversionv" >}} [... --pid /path/to/unit.pid --log /path/to/unit.log...] # Make sure to check for runtime overrides +``` + +Another issue is the logs' accessibility. Logs are opened and updated by +the +[main process]({{< relref "/unit/howto/security.md#security-apps" >}}) +that usually runs as **root**. +However, to make them available for a certain consumer, you may need to +enable access for a dedicated user that the consumer runs as. + +Perhaps, the most straightforward way to achieve this is to assign log +ownership to the consumer's account. Suppose you have a log utility running +as **log_user:log_group**: + +```console +# chown log_user:log_group :/path/to/unit.log # If it's overridden, use the runtime setting +``` + +```console +curl -X PUT -d '"/path/to/access.log"' \ + --unix-socket /path/to/control.unit.sock \ + http://localhost/config/access_log +``` + + +```console +# chown log_user:log_group /path/to/access.log # Use a real path in your command> +``` + +If you change the log file ownership, adjust your `logrotate` +settings accordingly: + +```none +/path/to/unit.log { + ... + su log_user log_group + ... +} +``` + +{{< note >}} +As usual with permissions, different steps may be required if you use +ACLs. +{{< /note >}} + +
+ +## Add restrictions, isolation {#security-isolation} + +**Rationale**: If the underlying OS allows, Unit provides features that create an +additional level of separation and containment for your apps, such as: + +- Share [path restrictions]({{< relref "/unit/configuration.md#configuration-share-path" >}}) +- Namespace and file system root + [isolation]({{< relref "/unit/configuration.md#configuration-proc-mgmt-isolation" >}}) + +**Actions**: For more details, see our blog posts on [path restrictions](https://www.nginx.com/blog/nginx-unit-updates-for-summer-2021-now-available/#Static-Content:-Chrooting-and-Path-Restrictions), +[namespace](https://www.nginx.com/blog/application-isolation-nginx-unit/) and +[file system](https://www.nginx.com/blog/filesystem-isolation-nginx-unit/) +isolation. diff --git a/content/unit/howto/source.md b/content/unit/howto/source.md new file mode 100644 index 000000000..917af6855 --- /dev/null +++ b/content/unit/howto/source.md @@ -0,0 +1,873 @@ +--- +title: Building from source +weight: 300 +toc: true +--- + +After you've obtained Unit's +[source code]({{< relref "/unit/installation.md#source" >}}), configure +and compile it to fine-tune and run a custom Unit build. + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Installing Required Software {#source-prereq-build} + +Before configuring and compiling Unit, install the required build tools and the +library files for the required languages (Go, Java, Node.js, PHP, +Perl, Python, and Ruby are supported) and all other features you want in your +Unit, such as TLS or regular expressions. + +The commands below assume you are configuring Unit with all supported languages +and features (**X**, **Y**, and **Z** denote major, minor, and +revision numbers, respectively); omit the packages you won't use. + +{{< tabs name="prereq" >}} + +{{% tab name="Debian, Ubuntu" %}} + +```console +# apt install build-essential +``` + +```console +# apt install golang +``` + +```console +# apt install curl && \ + curl -sL https://deb.nodesource.com/setup_VERSION.x | bash - && \ # Node.js 8.11 or later is supported + apt install nodejs +``` + +```console +# npm install -g node-gyp +``` + +```console +# apt install php-dev libphp-embed +``` + +```console +# apt install libperl-dev +``` + +```console +# apt install pythonX-dev # Both Python 2 and Python 3 are supported +``` + +```console +# apt install ruby-dev ruby-rack +``` + +```console +# apt install openjdk-X-jdk # Java 8 or later is supported. Different JDKs may be used +``` + +```console +# apt install libssl-dev +``` + +```console +# apt install libpcre2-dev +``` + +{{% /tab %}} + +{{% tab name="Amazon, Fedora, RHEL" %}} + +```console +# yum install gcc make +``` + +```console +# yum install golang +``` + +```console +# yum install curl && \ + curl -sL https://rpm.nodesource.com/setup_VERSION.x | bash - && \ # Node.js 8.11 or later is supported + yum install nodejs +``` + +```console +# npm install -g node-gyp +``` + +```console +# yum install php-devel php-embedded +``` + +```console +# yum install perl-devel perl-libs +``` + +```console +# yum install pythonX-devel # Both Python 2 and Python 3 are supported +``` + +```console +# yum install ruby-devel rubygem-rack +``` + +```console +# yum install java-X.Y.Z-openjdk-devel # Java 8 or later is supported. Different JDKs may be used +``` + +```console +# yum install openssl-devel +``` +```console +# yum install pcre2-devel +``` + +{{% /tab %}} + +{{% tab name="FreeBSD" %}} + +Ports: + +```console +# cd /usr/ports/lang/go/ && make install clean +``` + +```console +# cd /usr/ports/www/node/ && make install clean +``` + +```console +# cd /usr/ports/www/npm/ && make install clean && npm i -g node-gyp +``` + +```console +# cd /usr/ports/lang/phpXY/ && make install clean # PHP versions 5, 7, and 8 are supported +``` + +```console +# cd /usr/ports/lang/perlX.Y/ && make install clean # Perl 5.12 or later is supported +``` + +```console +# cd /usr/ports/lang/python/ && make install clean +``` + +```console +# cd /usr/ports/lang/rubyXY/ && make install clean # Ruby 2.0 or later is supported +``` + +```console +# cd /usr/ports/java/openjdkX/ && make install clean # Java 8 or later is supported. Different JDKs may be used +``` + +```console +# cd /usr/ports/security/openssl/ && make install clean +``` + +```console +# cd /usr/ports/devel/pcre2/ && make install clean +``` + +Packages: + +```console +# pkg install go +``` + +```console +# pkg install node && pkg install npm && npm i -g node-gyp +``` + +```console +# pkg install phpXY # PHP versions 5, 7, and 8 are supported +``` + +```console +# pkg install perlX # Perl 5.12 or later is supported +``` + +```console +# pkg install python +``` + +```console +# pkg install rubyXY # Ruby 2.0 is supported +``` + +```console +# pkg install openjdkX # Java 8 or later is supported. Different JDKs may be used +``` + +```console +# pkg install openssl +``` + +```console +# pkg install pcre2 +``` + +{{% /tab %}} + +{{% tab name="Solaris" %}} + +```console +# pkg install golang +``` + +```console +# pkg install php-XY # PHP versions 5, 7, and 8 are supported +``` + +```console +# pkg install ruby +``` + +```console +# pkg install jdk-X # Java 8 or later is supported. Different JDKs may be used +``` + +```console +# pkg install openssl +``` + +```console +# pkg install pcre +``` + +Also, use `gmake` instead of `make` when [building +and installing]({{< relref "/unit/howto/source.md#source-bld-src" >}}) Unit on Solaris. + +{{% /tab %}} + +{{< /tabs >}} + +
+Enabling njs + + + +To build Unit with [njs](https://nginx.org/en/docs/njs/) support, +download the `njs` code to the same parent directory as the Unit code. + +**0.8.2** is the latest version of `njs` that Unit supports. +Make sure you are in the correct branch before configuring the binaries. + +```console +$ git clone https://github.com/nginx/njs.git +``` + +```console +$ cd njs +``` + +```console +$ git checkout -b 0.8.2 0.8.2 +``` + +Next, configure and build the `njs` binaries. Make sure to use the +`--no-zlib` and `--no-libxml2` options to avoid +conflicts with Unit's dependencies: + +```console +$ ./configure --no-zlib --no-libxml2 && make # Ensures Unit can link against the resulting library +``` + +Point to the resulting source and build directories when +[configuring]({{< relref "/unit/howto/source.md#source-config-src-njs" >}}) +the Unit code. + +--- +
+ +
+Enabling WebAssembly + + +{{< tabs name="source-enable-webassembly" >}} +{{% tab name="wasm-wasi-component" %}} + +To build Unit with support for the WebAssembly Component Model, +you need **rust** version 1.76.0+, **cargo** and the developer +package for **clang** as mentioned in the +[Required Software]({{< relref "/unit/howto/source.md#source-prereq-build" >}}) +section. + +Next please refer to +[Configuring Modules - WebAssembly]({{< relref "/unit/howto/source.md#modules-webassembly" >}}) +for further instructions. + +{{% /tab %}} +{{% tab name="unit-wasm" %}} + +{{< warning >}} +The **unit-wasm** module is deprecated. We recommend using **wasm-wasi-component** instead, +available in Unit 1.32.0 and later, which supports WebAssembly Components using +standard WASI 0.2 interfaces. +{{< /warning >}} + +To build Unit with the [WebAssembly](https://webassembly.org) +language module, you need the [Wasmtime](https://wasmtime.dev) runtime. +Download the C API [files](https://github.com/bytecodealliance/wasmtime/releases/) +suitable for your OS and architecture to the same parent directory as the Unit code, +for example: + +```console + $ cd .. +``` + +```console +$ wget -O- https://github.com/bytecodealliance/wasmtime/releases/download/v12.0.0/wasmtime-v12.0.0-x86_64-linux-c-api.tar.xz \ + | tar Jxf - # Unpacks to the current directory +``` + +Point to the resulting **include** and **lib** directories when +[configuring]({{< relref "/unit/howto/source.md#source-modules-webassemble" >}}) +the Unit code. + +To build WebAssembly apps that run on Unit, you need +the +[wasi-sysroot](https://github.com/WebAssembly/wasi-sdk) SDK: + +```console +$ wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sysroot-20.0.tar.gz | tar zxf - +``` + +When building the apps, add the following environment variable: + +```console + WASI_SYSROOT=/path/to/wasi-sysroot-dir/ # wasi-sysroot directory +``` + +{{% /tab %}} +{{< /tabs >}} +
+ +--- + +## Configuring Sources {#source-config-src} + +To run system compatibility checks and generate a **Makefile** with core +build instructions for Unit: + +```console +$ ./configure COMPILE-TIME OPTIONS +``` + +See the table belo for common options. + +Finalize the resulting **Makefile** by configuring the +[language modules]({{< relref "/unit/howto/source.md#source-modules" >}}). +you need before proceeding further. + +General options and settings that control compilation, runtime privileges, +or support for certain features: + +{{}} + +| Option | Description | +|--------|------------| +| **--help** | Displays a summary of common `./configure` options.

For language-specific details, run `./configure --help` or see [below](#source-modules). | +| **--cc=pathname** | Custom C compiler pathname.

The default is **cc**. | +| **--cc-opt=options**, **--ld-opt=options** | Extra options for the C compiler and linker. | +| **--group=name**, **--user=name** | Group name and username to run Unit's non-privileged [processes]({{< relref "/unit/howto/security.md#security-apps" >}}).

The defaults are **--user**'s primary group and **nobody**, respectively. | +| **--debug** | Turns on the [debug log]({{< relref "/unit/troubleshooting.md#troubleshooting-dbg-log" >}}). | +| **--no-ipv6** | Turns off IPv6 support. | +| **--no-unix-sockets** | Turns off UNIX domain sockets support for control and routing. | +| **--openssl** | Turns on OpenSSL support. Make sure OpenSSL (1.0.1+) header files and libraries are in your compiler's path; it can be set with the **--cc-opt** and **--ld-opt** options or the **CFLAGS** and **LDFLAGS** environment variables when running `./configure`.

For details of TLS configuration in Unit, see [configuration-ssl]({{< relref "/unit/certificates#configuration-ssl" >}}). | + +{{
}} + + + + +By default, Unit relies on the locally installed version of the [PCRE](https://www.pcre.org) library to support regular expressions in +[routes]({{< relref "/unit/configuration.md#configuration-routes" >}}); +if both major versions are present, Unit selects PCRE2. Two additional options +alter this behavior: + +{{}} + +| Option | Description | +|--------|------------| +| **--no-regex** | Turns off regex support; any attempts to use a regex in Unit configuration cause an error. | +| **--no-pcre2** | Ignores PCRE2; the older PCRE 8.x library is used instead. | + +{{}} + + + + +Unit also supports the use of [njs](https://nginx.org/en/docs/njs/) scripts +in configuration; to enable this feature, use the respective option: + +{{}} + +| Option | Description | +|---------|------------| +| **--njs** | Turns on `njs` support; requires **--openssl**. | + +{{}} + + +When `--njs` is enabled, the `--cc-opt` and `--ld-opt` option values should +point to the **src/** and **build/** subdirectories of the `njs` source code. +For example, if you cloned the `njs` repo beside the Unit repo: + +```console +$ ./configure --njs --openssl \ + --cc-opt="-I../njs/src/ -I../njs/build/" \ + --ld-opt="-L../njs/build/" \ + ... +``` + +The next option group customizes Unit's runtime +[directory structure]({{< relref "/unit/howto/source.md#source-dir" >}}): + +{{}} + +| Option | Description | +|--------|------------| +| **--prefix=PREFIX** | Destination directory prefix for [path options]({{< relref "/unit/howto/source.md#source-dir" >}}): `--bindir`, `--sbindir`, `--includedir`, `--libdir`, `--modulesdir`, `--datarootdir`, `--mandir`, `--localstatedir`, `--libstatedir`, `--runstatedir`, `--logdir`, `--tmpdir`, `--control`, `--pid`, `--log`.

The default is **/usr/local**. | +| **--exec-prefix=EXEC_PREFIX** | Destination directory prefix for the executable directories only.

The default is the **PREFIX** value. | +| **--bindir=BINDIR**, **--sbindir=SBINDIR** | Directory paths for client and server executables.

The defaults are **EXEC_PREFIX/bin** and **EXEC_PREFIX/sbin**. | +| **--includedir=INCLUDEDIR**, **--libdir=LIBDIR** | Directory paths for `libunit` header files and libraries.

The defaults are **PREFIX/include** and **EXEC_PREFIX/lib**. | +| **--modulesdir=MODULESDIR** | Directory path for Unit's language [modules]({{< relref "/unit/howto/modules.md" >}}).

The default is **LIBDIR/unit/modules**. | +| **--datarootdir=DATAROOTDIR**, **--mandir=MANDIR** | Directory path for **unitd(8)** data storage and its subdirectory where the `man` page is installed.

The defaults are **PREFIX/share** and **DATAROOTDIR/man**. | +| **--localstatedir=LOCALSTATEDIR** | Directory path where Unit stores its runtime state, PID file, control socket, and logs.

The default is **PREFIX/var**. | +| **--libstatedir=LIBSTATEDIR** | Directory path where Unit's runtime state (configuration, certificates, other resources) is stored between runs. If you migrate your installation, copy the entire directory.

**Warning:** The directory is sensitive and must be owned by **root** with **700** permissions. Don't change its contents externally; use the config API to ensure integrity.

The default is **LOCALSTATEDIR/run/unit**. | +| **--logdir=LOGDIR**, **--log=LOGFILE** | Directory path and filename for Unit's [log]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}).

The defaults are **LOCALSTATEDIR/log/unit** and **LOGDIR/unit.log**. | +| **--runstatedir=RUNSTATEDIR** | Directory path where Unit stores its PID file and control socket.

The default is **LOCALSTATEDIR/run/unit**. | +| **--pid=pathname** | Pathname for the PID file of Unit's `main` [process]({{< relref "/unit/howto/security.md#security-apps" >}}).

The default is **RUNSTATEDIR/unit.pid**. | +| **--control=SOCKET** | [Control API]({{< relref "/unit/controlapi.md#configuration-mgmt" >}}) socket address in IPv4, IPv6, or UNIX domain format:

`$ ./configure --control=127.0.0.1:8080`
`$ ./configure --control=[::1]:8080`
`$ ./configure --control=unix:/path/to/control.unit.sock` (Note the `unix:` prefix).

**Warning:** Avoid exposing an unprotected control socket in public networks. Use [NGINX]({{< relref "/unit/howto/integration.md#nginx-secure-api" >}}) or a different solution such as SSH for security and authentication.

The default is **unix:RUNSTATEDIR/control.unit.sock**, created as **root** with **600** permissions. | +| **--tmpdir=TMPDIR** | Defines the temporary file storage location (used to dump large request bodies). The default value is **/tmp**. | + +{{
}} + +### Directory Structure {#source-dir} + +By default, `make install` installs Unit at the following pathnames: + +{{}} + +| Directory | Default Path | +|-----------|-------------| +| **bin** directory | **/usr/local/bin/** | +| **sbin** directory | **/usr/local/sbin/** | +| **lib** directory | **/usr/local/lib/** | +| **include** directory | **/usr/local/include/** | +| **tmp** directory | **/tmp/** | +| Man pages | **/usr/local/share/man/** | +| Language modules | **/usr/local/lib/unit/modules/** | +| Runtime state | **/usr/local/var/lib/unit/** | +| PID file | **/usr/local/var/run/unit/unit.pid** | +| Log file | **/usr/local/var/log/unit/unit.log** | +| Control API socket | **unix:/usr/local/var/run/unit/control.unit.sock** | + +{{}} + + +The defaults are designed to work for most cases; to customize this layout, +set the `--prefix` and its related options during +[configuration]({{< relref "/unit/howto/source.md#source-config-src-prefix" >}}). +defining the resulting file structure. + +## Configuring Modules {#source-modules} + +Next, configure a module for each language you want to use with Unit. The +`./configure ` commands set up individual language modules +and place module-specific instructions in the **Makefile**. + +{{< note >}} +To run apps in several versions of a language, build and install a module +for each version. To package custom modules, see the module +[howto]({{< relref "/unit/howto/modules.md#modules-pkg" >}}). +{{< /note >}} + +{{< tabs name="modules" >}} +{{% tab name="Go" %}} + +When you run `./configure go`, Unit sets up the Go package that +lets your applications +[run on Unit]({{< relref "/unit/configuration.md#configuration-go" >}}). +To use the package, +[install]({{< relref "/unit/howto/source.md#source-bld-src-ext" >}}) +it in your Go environment. Available configuration options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--go=pathname** | Specific Go executable pathname, also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-ext" >}}) targets.

The default is **go**. | +| **--go-path=directory** | Custom directory path for Go package installation.

The default is **$GOPATH**. | + +{{
}} + + +{{< note >}} +Running `./configure go` doesn't alter the `GOPATH` +[environment variable](https://github.com/golang/go/wiki/GOPATH), so +configure-time `--go-path` and compile-time `$GOPATH` +must be coherent for Go to find the resulting package. + +```console +$ GOPATH= GO111MODULE=auto go build -o app app.go # App executable name and source code>` +``` +{{< /note >}} + +{{% /tab %}} +{{% tab name="Java" %}} + +When you run `./configure java`, the script configures a module +to support running [Java Web Applications](https://download.oracle.com/otndocs/jcp/servlet-3_1-fr-spec/index.html) +on Unit. Available command options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--home=directory** | Directory path for Java utilities and header files to build the module.

The default is the **java.home** setting. | +| **--jars=directory** | Directory path for Unit's custom **.jar** files.

The default is the Java module path. | +| **--lib-path=directory** | Directory path for the **libjvm.so** library.

The default is based on JDK settings. | +| **--local-repo=directory** | Directory path for the local **.jar** repository.

The default is **$HOME/.m2/repository/**. | +| **--repo=directory** | URL path for the remote Maven repository.

The default is **http://central.maven.org/maven2/**. | +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is **java**. | + +{{
}} + +To configure a module called **java11.unit.so** with OpenJDK +11.0.1: + +```console +$ ./configure java --module=java11 \ + --home=/Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home +``` + +{{% /tab %}} +{{% tab name="Node.js" %}} + +When you run `./configure nodejs`, Unit sets up the +`unit-http` module that lets your applications +[run on Unit]({{< relref "/unit/configuration.md#configuration-nodejs" >}}). +Available configuration options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--local=directory** | Local directory path where the resulting module is installed.

By default, the module is installed globally [(recommended)](/unit/installation.md#installation-nodejs-package). | +| **--node=pathname** | Specific Node.js executable pathname, also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is **node**. | +| **--npm=pathname** | Specific `npm` executable pathname.

The default is **npm**. | +| **--node-gyp=pathname** | Specific `node-gyp` executable pathname.

The default is **node-gyp**. | + +{{
}} + +{{% /tab %}} +{{% tab name="Perl" %}} + +When you run `./configure perl`, the script configures a module +to support running Perl scripts as applications on Unit. Available +command options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--perl=pathname** | Specific Perl executable pathname.

The default is **perl**. | +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is the filename of the `--perl` executable. | + +{{
}} + +To configure a module called **perl-5.20.unit.so** for Perl +5.20.2: + +```console + $ ./configure perl --module=perl-5.20 \ + --perl=perl5.20.2 +``` + +{{% /tab %}} +{{% tab name="PHP" %}} + +When you run `./configure php`, the script configures a custom +SAPI module linked with the `libphp` library to support running +PHP applications on Unit. Available command options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--config=pathname** | Pathname of the `php-config` script used to set up the resulting module.

The default is **php-config**. | +| **--lib-path=directory** | Directory path of the `libphp` library file (**libphp*.so** or **libphp*.a**), usually available with an `--enable-embed` PHP build. | +| **--lib-static** | Links the static `libphp` library (**libphp*.a**) instead of the dynamic one (**libphp*.so**); requires `--lib-path`. | +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is `--config`'s filename minus the `-config` suffix; thus, **--config=/path/php7-config** yields **php7.unit.so**. | + +{{
}} + + +To configure a module called **php70.unit.so** for PHP 7.0: + +```console + $ ./configure php --module=php70 \ + --config=/usr/lib64/php7.0/bin/php-config \ + --lib-path=/usr/lib64/php7.0/lib64 +``` + +{{% /tab %}} +{{% tab name="Python" %}} + +When you run `./configure python`, the script configures a +module to support running Python scripts as applications on Unit. +Available command options: + +{{}} + +| Option | Description | +|--------|-------------| +| **--config=pathname** | Pathname of the `python-config` script used to set up the resulting module.

The default is **python-config**. | +| **--lib-path=directory** | Custom directory path of the Python runtime library to use with Unit. | +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is `--config`'s filename minus the `-config` suffix; thus, **/path/python3-config** turns into **python3**. | + +{{
}} + + +{{< note >}} +The Python interpreter set by `python-config` must be +compiled with the `--enable-shared` [option](https://docs.python.org/3/using/configure.html#linker-options). +{{< /note >}} + +To configure a module called **py33.unit.so** for Python 3.3: + +```console +$ ./configure python --module=py33 \ + --config=python-config-3.3 +``` +{{% /tab %}} +{{% tab name="Ruby" %}} + +When you run `./configure ruby`, the script configures a module +to support running Ruby scripts as applications on Unit. Available +command options: + +{{}} +| Option | Description | +|-----------------------|-----------------------------------------------------------------------------| +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets.

The default is the filename of the `--ruby` executable. | +| **--ruby=pathname** | Specific Ruby executable pathname.

The default is **ruby**. | +{{
}} + + +To configure a module called **ru23.unit.so** for Ruby 2.3: + +```console + $ ./configure ruby --module=ru23 \ + --ruby=ruby23 +``` + +{{% /tab %}} +{{% tab name="WebAssembly" %}} + +When you run `./configure wasm-wasi-component`, +the script configures a module to support running WebAssembly +components on Unit. + +The module doesn't accept any extra configuration parameters. +The module's basename is wasm-wasi-component. + +{{% /tab %}} +{{% tab name="Unit-Wasm" %}} + +{{< warning >}} +Unit 1.32.0 and later support the WebAssembly Component Model and WASI +0.2 APIs. +We recommend using the new implementation. +{{< /warning >}} + +When you run `./configure wasm`, the script configures a module +to support running WebAssembly applications on Unit. +Available command options: + +{{}} +| Option | Description | +|-----------------------|-----------------------------------------------------------------------------| +| **--module=basename** | Resulting module's name (**.unit.so**), also used in [make]({{< relref "/unit/howto/source.md#source-bld-src-emb" >}}) targets. | +| **--runtime=basename** | The WebAssembly runtime to use.

The default is **wasmtime**. | +| **--include-path=path**| The directory path to the runtime's header files. | +| **--lib-path=path** | The directory path to the runtime's library files. | +| **--rpath=** | The directory path that designates the run-time library search path.

If specified without a value, assumes the **--lib-path** value. | +{{
}} + + +To configure a module called **wasm.unit.so**: + +```console +$ ./configure wasm --include-path=/path/to/wasmtime/include \ + --lib-path=/path/to/wasmtime/lib \ + --rpath +``` + +{{% /tab %}} +{{< /tabs >}} + +--- + +## Building and Installing Unit {#source-bld-src} + +To build and install Unit's executables and language modules that you have +`./configure`'d earlier: + +```console +$ make +``` + +```console +# make install +``` + +Mind that **make install** requires setting up Unit's +[directory structure]({{< relref "/unit/howto/source.md#source-dir" >}}) +with `./configure` first. +To run Unit from the build directory tree without installing: + +```console +$ ./configure --prefix=./build +``` + +```console +$ make +``` + +```console +$ ./build/sbin/unitd +``` + +You can also build and install language modules individually; the specific +method depends on whether the language module is embedded in Unit (Java, Perl, +PHP, Python, Ruby) or packaged externally (Go, Node.js). + +{{< note >}} +For further details about Unit's language modules, see +[Working with language modules]({{< relref "/unit/howto/modules.md" >}}) +{{< /note >}} + +### Embedded Language Modules {#source-bld-src-emb} + +To build and install the modules for Java, PHP, Perl, Python, or Ruby after +configuration, run `make ` and `make -install`, +for example: + +```console +$ make perl-5.20 # This is the --module option value from ./configure perl +``` + +```console +# make perl-5.20-install # This is the --module option value from ./configure perl +``` + +### External Language Modules {#source-bld-src-ext} + +To build and install the modules for Go and Node.js globally after +configuration, run `make -install` and `make -install`, for example: + +```console +# make go-install # This is the --go option value from ./configure go +``` + +```console +# make node-install # This is the --node option value from ./configure nodejs +``` + +{{< note >}} +To install the Node.js module locally, run `make -local-install`: + +```console +# make node-local-install # This is the --node option value from ./configure nodejs +``` + +If you haven't specified the `--local` directory with `./configure nodejs` +earlier, provide it here: + +```console +# DESTDIR=/your/project/directory/ make node-local-install +``` + +If both options are specified, `DESTDIR` prefixes the +`--local` value set by `./configure nodejs`. + +Finally, mind that global installation is preferable for the Node.js module. +{{< /note >}} + +If you customized the executable pathname with `--go` or `--node`, use the +following pattern: + +```console +$ ./configure nodejs --node=/usr/local/bin/node8.12 # Executable pathname +``` + +```console +# make /usr/local/bin/node8.12-install # Executable pathname becomes a part of the target +``` + +```console +$ ./configure go --go=/usr/local/bin/go1.7 # Executable pathname +``` + +```console +# make /usr/local/bin/go1.7-install # Executable pathname becomes a part of the target +``` + +## Startup and Shutdown {#source-startup} + +{{< warning >}} +We advise installing Unit from +[precompiled packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}); +in this case, startup is +[configured]({{< relref "/unit/installation.md#installation-precomp-startup" >}}) +automatically. + +Even if you install Unit otherwise, avoid manual startup. Instead, configure a +service manager (`OpenRC`, `systemd`, and so on) or create an `rc.d` script to +launch the Unit daemon using the options below. +{{< /warning >}} + +The startup command depends on the directories you set with `./configure`, but +their default values place the `unitd` binary in a well-known place, so: + +```console +# unitd RUNTIME OPTIONS # Your PATH environment variable should list a path to unitd +``` + +See the table below for common runtime options. + +Run `unitd -h` or `unitd --version` to list Unit's +compile-time settings. Usually, the defaults don't require overrides; still, +the following runtime options are available. For their compile-time +counterparts, see +[here]({{< relref "/unit/howto/source.md#source-config-src" >}}). + +{{}} +| Option | Description | +|--------------------------------|-----------------------------------------------------------------------------| +| **--help**, **-h** | Displays a summary of the command-line options and their defaults. | +| **--version** | Displays Unit's version and the `./configure` settings it was built with. | +| **--no-daemon** | Runs Unit in non-daemon mode. | +| **--control socket** | Control API socket address in IPv4, IPv6, or UNIX domain format:

`# unitd --control 127.0.0.1:8080`

`# unitd --control [::1]:8080`

`# unitd --control unix:/path/to/control.unit.sock` | +| **--control-mode** | Sets the permission of the UNIX domain control socket. Default: 0600 | +| **--control-user** | Sets the owner of the UNIX domain control socket. | +| **--control-group** | Sets the group of the UNIX domain control socket. | +| **--group name**, **--user name**| Group name and user name used to run Unit's non-privileged [processes]({{< relref "/unit/howto/security.md#security-apps" >}}). | +| **--log pathname** | Pathname for Unit's [log]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}). | +| **--modules directory** | Directory path for Unit's language [modules]({{< relref "/unit/howto/modules.md" >}}) (***.unit.so** files). | +| **--pid pathname** | Pathname for the PID file of Unit's **main** [process]({{< relref "/unit/howto/security.md#security-apps" >}}). | +| **--state directory** | Directory path for Unit's state storage. | +| **--tmp directory** | Directory path for Unit's temporary file storage. | +{{
}} + + +Finally, to stop a running Unit: + +```console +# pkill unitd +``` + +This command signals all Unit's processes to terminate in a graceful manner. diff --git a/content/unit/howto/walkthrough.md b/content/unit/howto/walkthrough.md new file mode 100644 index 000000000..d39ca9358 --- /dev/null +++ b/content/unit/howto/walkthrough.md @@ -0,0 +1,84 @@ +--- +title: Walkthrough +weight: 900 +toc: true +--- + +OK, so you've decided to give Unit a try with your web app of choice. You may +be looking for ways to run it faster with less config overhead, streamlining +your technology stack, or simply be tech-curious. In any case: + +## Check the prerequisites + +1. Verify that Unit + [supports]({{< relref "/unit/installation.md#source-prereqs" >}}) + your platform and app language version. + +1. If possible, ensure the app can run beside Unit to rule out + external issues. + +## Get Unit on the system + + Install Unit with the language modules you need. Your options: + + - Official .deb/.rpm + [packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) + + - Docker + [images]({{< relref "/unit/installation.md#installation-docker" >}}) + + - Third-party + [packages]({{< relref "/unit/installation.md#installation-community-repos" >}}) + + - Source + [build]({{< relref "/unit/installation.md#source" >}}) + +1. Configure and launch Unit on your system: + + - Our own and third-party packages + [rely on]({{< relref "/unit/installation.md#installation-precomp-startup" >}}) + `systemctl` or `service`. + + - Containerized Unit can be + [run]({{< relref "/unit/howto/docker.md" >}}) with common `docker` commands. + + - If none of the above applies, customize Unit's + [startup]({{< relref "/unit/howto/source.md#source-startup" >}}) + manually. + +## Prepare the app for Unit + +1. *(Only applies to + [Go]({{< relref "/unit/configuration.md#configuration-go" >}}))* Patch + your app to run on Unit. + +1. Choose + [common]({{< relref "/unit/configuration.md#configuration-applications" >}}) + options such as app type, working directory, user/group. + +1. Add + [language-specific]({{< relref "/unit/configuration.md#configuration-languages" >}}) + settings such as index, entry module, or executable. + +## Plug the app into Unit + +1. *(Optional)* Add Unit-wide [settings]({{< relref "/unit/configuration.md#configuration-stngs" >}}). to + your app's config to run it smoothly. + +1. [Upload]({{< relref "/unit/controlapi.md#configuration-mgmt" >}}) + your config into Unit to spin up the app. + +1. *(Optional)* Set up a + [route]({{< relref "/unit/configuration.md#configuration-routes" >}}) + to your app to benefit from internal routing. + +1. *(Optional)* Upload a + [certificate bundle]({{< relref "/unit/certificates.md#configuration-ssl" >}}) + if you want to support SSL/TLS. + +1. Finally, set up a + [listener]({{< relref "/unit/configuration.md#configuration-listeners" >}}) + to make your app publicly available. + + +For the details of each step, see specific documentation sections. diff --git a/content/unit/installation.md b/content/unit/installation.md new file mode 100644 index 000000000..c345106df --- /dev/null +++ b/content/unit/installation.md @@ -0,0 +1,2726 @@ +--- +title: Installation +weight: 400 +toc: true +--- + +You can install NGINX Unit in four alternative ways: + +- Choose from our official [binary packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) for a few popular systems. + They're as easy to use as any other packaged software and suit most purposes + straight out of the box. +- If your preferred OS or language version is missing from the official package list, + try [third-party repositories]({{< relref "/unit/installation.md#installation-community-repos" >}}). + Be warned, though: we don't maintain them. +- Run our [Docker official images]({{< relref "/unit/installation.md#installation-docker" >}}), + prepackaged with varied language combinations. +- To fine-tune Unit to your goals, + download the [sources]({{< relref "/unit/installation.md#source" >}}), + install the [toolchain]({{< relref "/unit/howto/source.md#source-prereq-build" >}}), + and [build]({{< relref "/unit/howto/source.md#source-config-src" >}}) a custom binary from scratch; just make sure you know what you're doing. + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Prerequisites {#source-prereqs} + +Unit compiles and runs on various Unix-like operating systems, including: + +- FreeBSD 10 or later +- Linux 2.6 or later +- macOS 10.6 or later +- Solaris 11 + +It also supports most modern instruction set architectures, such as: + +- ARM +- IA-32 +- PowerPC +- MIPS +- S390X +- x86-64 + +App languages and platforms that Unit can run +(including several versions of the same language): + +- Go 1.6 or later +- Java 8 or later +- Node.js 8.11 or later +- PHP 5, 7, 8 +- Perl 5.12 or later +- Python 2.6, 2.7, 3 +- Ruby 2.0 or later +- WebAssembly Components WASI 0.2 + +Optional dependencies: + +- OpenSSL 1.0.1 or later for [TLS]({{< relref "/unit/certificates.md#configuration-ssl" >}}) support +- PCRE (8.0 or later) or PCRE2 (10.23 or later) for + [regular expression matching]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns">}}) +- The [njs](https://nginx.org/en/docs/njs/) scripting language +- Wasmtime for WebAssembly Support + +## Official packages {#installation-precomp-pkgs} + +Installing an official precompiled Unit binary package +is best on most occasions; +they're available for: + +- Amazon Linux [AMI]({{< relref "/unit/installation.md#installation-amazon-ami" >}}), + Amazon Linux [2]({{< relref "/unit/installation.md#installation-amazon-20lts" >}}), + Amazon Linux [2023]({{< relref "/unit/installation.md#installation-amazon-2023" >}}) +- Debian [9]({{< relref "/unit/installation.md#installation-debian-9" >}}), + [10]({{< relref "/unit/installation.md#installation-debian-10" >}}), + [11]({{< relref "/unit/installation.md#installation-debian-11" >}}), + [12]({{< relref "/unit/installation.md#installation-debian-12" >}}) +- Fedora [29]({{< relref "/unit/installation.md#installation-fedora-29" >}}), + [30]({{< relref "/unit/installation.md#installation-fedora-3130" >}}), + [31]({{< relref "/unit/installation.md#installation-fedora-3130" >}}), + [32]({{< relref "/unit/installation.md#installation-fedora-32" >}}), + [33]({{< relref "/unit/installation.md#installation-fedora-3433" >}}), + [34]({{< relref "/unit/installation.md#installation-fedora-3433" >}}), + [35]({{< relref "/unit/installation.md#installation-fedora-3635" >}}), + [36]({{< relref "/unit/installation.md#installation-fedora-3635" >}}), + [37]({{< relref "/unit/installation.md#installation-fedora-37" >}}), + [38]({{< relref "/unit/installation.md#installation-fedora-38" >}}) +- RHEL [6]({{< relref "/unit/installation.md#installation-rhel-6x" >}}), + [7]({{< relref "/unit/installation.md#installation-rhel-7x" >}}), + [8]({{< relref "/unit/installation.md#installation-rhel-8x" >}}), + [9]({{< relref "/unit/installation.md#installation-rhel-9x" >}}) +- Ubuntu [16.04]({{< relref "/unit/installation.md#installation-ubuntu-1604" >}}), + [18.04]({{< relref "/unit/installation.md#installation-ubuntu-1804" >}}), + [19.10]({{< relref "/unit/installation.md#installation-ubuntu-1910" >}}), + [20.04]({{< relref "/unit/installation.md#installation-ubuntu-2004" >}}), + [20.10]({{< relref "/unit/installation.md#installation-ubuntu-2010" >}}), + [21.04]({{< relref "/unit/installation.md#installation-ubuntu-2104" >}}), + [21.10]({{< relref "/unit/installation.md#installation-ubuntu-2110" >}}), + [22.04]({{< relref "/unit/installation.md#installation-ubuntu-2204" >}}), + [22.10]({{< relref "/unit/installation.md#installation-ubuntu-2210" >}}), + [23.04]({{< relref "/unit/installation.md#installation-ubuntu-2304" >}}) + +The packages include core executables, developer files,and support for individual +languages. +We also maintain a Homebrew [tap]({{< relref "/unit/installation.md#installation-macos-homebrew" >}}) for +macOS users and a [module]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) for Node.js +at the [npm](https://www.npmjs.com/package/unit-http) registry. + +{{< note >}} +For details of packaging custom modules that install alongside the official Unit, +see [here]({{< relref "/unit/howto/modules.md#modules-pkg" >}}). +{{< /note >}} + +### Repository installation script {#repo-install} + +
+Repo installation script + + We provide a [script](https://github.com/nginx/unit/tree/master/tools>) + that adds our official repos on the systems we support: + + ```console + wget https://raw.githubusercontent.com/nginx/unit/master/tools/setup-unit && chmod +x setup-unit + ``` + + Run the following command as root: + + ```console + ./setup-unit repo-config + ``` + + Use it at your discretion; explicit steps are provided below + for each distribution. +
+ +--- + +### Amazon Linux {#installation-precomp-amazon} + +{{}} + +{{%tab name="2023"%}} +Supported architecture: x86-64. + +1. To configure Unit's repository, + create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/amzn/2023/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc17 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python39 unit-python311 unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + + + +{{}} +| Runtime details: | Description | +|--------------------|--------------------------------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} +{{%/tab%}} + +{{%tab name="2022 LTS"%}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/amzn2/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package + and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python27 unit-python37 unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details: | Description | +|--------------------|--------------------------------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + +{{%/tab%}} + +{{%tab name="AMI"%}} + +{{< warning >}} +Unit's 1.22+ packages aren't built for Amazon Linux AMI. This distribution is +obsolete; please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, + create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/amzn/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + +1. Install the core package + and other packages you need: + + ```console + # yum install unit + + ```console + # yum install unit-devel unit-jsc8 unit-perl unit-php \ # unit-devel is required to install the Node.js module + unit-python27 unit-python34 unit-python35 unit-python36 + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + +{{}} + +| Runtime details: | Description | +|--------------------|--------------------------------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{}} + +--- + +### Debian {#installation-precomp-deb} + +{{}} +{{%tab name="12"%}} +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit + ``` + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc17 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python3.11 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` +{{}} +| Runtime details: | Description | +|--------------------|--------------------------------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | +{{}} + +{{%/tab%}} +{{%tab name="11"%}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bullseye unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bullseye unit + ``` + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.9 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} +| Runtime details: | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | +{{}} + +{{%/tab%}} +{{%tab name="10"%}} + +{{< warning >}} +Unit's 1.28+ packages aren't built for Debian 10. This distribution is +obsolete; please update. +{{< /warning >}} + +Supported architectures: i386, x86-64. + +1. Download and save NGINX's signing key: + +```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + +This eliminates the +"packages cannot be authenticated" +warnings +during installation. + +1. To configure Unit's repository, +create the following file named +**/etc/apt/sources.list.d/unit.list**: + +```none + + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ buster unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ buster unit + +1. Install the core package +and other packages you need: + +```console + # apt update + +```console + # apt install unit + +```console + # apt install unit-dev unit-jsc11 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.7 unit-ruby + +```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + +{{}} +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | +{{}} + +{{%/tab%}} +{{%tab name="9"%}} +{{< warning >}} +Unit's 1.28+ packages aren't built for Debian 9. This distribution is +obsolete; please update. +{{< /warning >}} + +Supported architectures: i386, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ stretch unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ stretch unit + ``` + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc8 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.5 unit-ruby + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | +{{}} + +{{%/tab%}} +{{}} + +--- + +### Fedora {#installation-precomp-fedora} + +{{}} +{{%tab name="38"%}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python311 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + +{{}} +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | +{{}} + +{{%/tab%}} +{{%tab name="37"%}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python311 unit-ruby unit-wasm + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} +{{%/tab%}} +{{%tab name="36, 35"%}} + +{{< warning >}} +Unit's 1.31+ packages aren't built for Fedora 36 and Fedora 35. +These distributions are obsolete; +please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python39 unit-python310 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="34, 33"%}} + +{{< warning >}} +Unit's 1.27+ packages aren't built for Fedora 33 and Fedora 34. +These distributions are obsolete; please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python39 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + + .. list-table:: + + * - Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) + - **/var/run/unit/control.sock** + + * - Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) + - **/var/log/unit/unit.log** + + * - Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) + - **unit** + +{{%/tab%}} +{{%tab name="32"%}} + +{{< warning >}} +Unit's 1.24+ packages aren't built for Fedora 32. These distributions are obsolete; +please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python38 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="31, 30"%}} + +{{< warning >}} +Unit's 1.20+ packages aren't built for Fedora 30; 1.22+ packages aren't built for +Fedora 31. These distributions are obsolete; please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python27 unit-python37 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="29"%}} + +{{< warning >}} +Unit's 1.20+ packages aren't built for Fedora 29. +This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/fedora/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python27 unit-python37 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{}} + +--- + +### RHEL and derivatives {#installation-precomp-rhel} + +{{< note >}} +Use these steps for binary-compatible distributions: AlmaLinux, CentOS, +Oracle Linux, or Rocky Linux. +{{< /note >}} + +{{}} +{{%tab name="9.x"%}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/rhel/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-go unit-jsc8 unit-jsc11 \ # unit-devel is required to install the Node.js module and build Go apps + unit-perl unit-php unit-python39 unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="8.x"%}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/rhel/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 \ # unit-devel is required to install the Node.js module + unit-perl unit-php unit-python27 unit-python36 unit-python38 unit-python39 unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="7.x"%}} + +{{< note >}} +Official packages for CentOS 7.x are also available. +{{< /note >}} + +Supported architecture: x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/rhel/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-jsc11 \ # unit-devel is required to install the Node.js module + unit-perl unit-php unit-python27 unit-python36 + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|---------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="6.x"%}} + +{{< warning >}} +Unit's 1.20+ packages aren't built for RHEL 6. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: i386, x86-64. + +1. To configure Unit's repository, create the following file named + **/etc/yum.repos.d/unit.repo**: + + ```ini + [unit] + name=unit repo + baseurl=https://packages.nginx.org/unit/rhel/$releasever/$basearch/ + gpgkey=https://unit.nginx.org/keys/nginx-keyring.gpg + gpgcheck=1 + enabled=1 + ``` + +1. Install the core package and other packages you need: + + ```console + # yum install unit + ``` + + ```console + # yum install unit-devel unit-jsc8 unit-perl \ # unit-devel is required to install the Node.js module + unit-php unit-python + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{}} + +--- + +### Ubuntu {#installation-precomp-ubuntu} +{{}} +{{%tab name="23.04"%}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ lunar unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ lunar unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-go unit-jsc11 unit-jsc17 unit-jsc18 unit-jsc19 unit-jsc20 \ # unit-dev is required to install the Node.js module and build Go apps + unit-perl unit-php unit-python3.11 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="22.10"%}} + +{{< warning >}} +Unit's 1.31+ packages aren't built for Ubuntu 22.10. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ kinetic unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ kinetic unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-go unit-jsc11 unit-jsc17 unit-jsc18 unit-jsc19 \ # unit-dev is required to install the Node.js module and build Go apps + unit-perl unit-php unit-python2.7 unit-python3.10 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="22.04"%}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ jammy unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-go unit-jsc11 unit-jsc16 unit-jsc17 unit-jsc18 \ # unit-dev is required to install the Node.js module and build Go apps + unit-perl unit-php unit-python2.7 unit-python3.10 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="21.10"%}} + +{{< warning >}} +Unit's 1.28+ packages aren't built for Ubuntu 21.10. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + + 1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ impish unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ impish unit + + 1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-jsc16 unit-jsc17 unit-jsc18 \ # unit-dev is required to install the Node.js module + unit-perl unit-php unit-python2.7 unit-python3.9 unit-python3.10 unit-ruby + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + + Runtime details: + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + + + .. tab:: 21.04 + +{{< warning >}} +Unit's 1.27+ packages aren't built for Ubuntu 21.04. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ hirsute unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ hirsute unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-jsc15 unit-jsc16 unit-jsc17 \ # unit-dev is required to install the Node.js module + unit-perl unit-php unit-python2.7 unit-python3.9 unit-ruby + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="20.10"%}} + +{{< warning >}} +Unit's 1.25+ packages aren't built for Ubuntu 20.10. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ groovy unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ groovy unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-jsc13 unit-jsc14 unit-jsc15 \ # unit-dev is required to install the Node.js module + unit-perl unit-php unit-python3.8 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="20.04"%}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ focal unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ focal unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.8 unit-ruby unit-wasm + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="19.10"%}} + +{{< warning >}} +Unit's 1.20+ packages aren't built for Ubuntu 19.10. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architecture: x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ eoan unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ eoan unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc11 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.7 unit-python3.8 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="18.04"%}} + +{{< warning >}} +Unit's 1.31+ packages aren't built for Ubuntu 18.04. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ bionic unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ bionic unit + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc8 unit-jsc11 unit-perl \ # unit-dev is required to install the Node.js module + unit-php unit-python2.7 unit-python3.6 unit-python3.7 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{%tab name="16.04"%}} + +{{< warning >}} +Unit's 1.24+ packages aren't built for Ubuntu 16.04. This distribution is obsolete; +please update. +{{< /warning >}} + +Supported architectures: arm64, i386, x86-64. + +1. Download and save NGINX's signing key: + + ```console + # curl --output /usr/share/keyrings/nginx-keyring.gpg \ + https://unit.nginx.org/keys/nginx-keyring.gpg + ``` + + This eliminates the "packages cannot be authenticated" warnings + during installation. + +1. To configure Unit's repository, create the following file named + **/etc/apt/sources.list.d/unit.list**: + + ```none + deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ xenial unit + deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/ubuntu/ xenial unit + ``` + +1. Install the core package and other packages you need: + + ```console + # apt update + ``` + + ```console + # apt install unit + ``` + + ```console + # apt install unit-dev unit-jsc8 unit-perl unit-php \ # unit-dev is required to install the Node.js module + unit-python2.7 unit-python3.5 unit-ruby + ``` + + ```console + # systemctl restart unit # Necessary for Unit to pick up any changes in language module setup + ``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|--------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + +{{%/tab%}} +{{}} + +--- + +### macOS {#installation-macos} + +To install Unit on macOS, use the official Homebrew +[tap](https://github.com/nginx/homebrew-unit). + +```console + $ brew install nginx/unit/unit +``` + +This deploys the core Unit binary and the prerequisites for the +[Node.js]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) +language module. + +To install the Java, Perl, Python, and Ruby language modules from Homebrew: + +```console +$ brew install unit-java unit-perl unit-php unit-python unit-python3 unit-ruby +``` + +```console +# pkill unitd # Stop Unit +``` + +```console +# unitd # Start Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/usr/local/var/run/unit/control.sock** (Intel), **/opt/homebrew/var/run/unit/control.sock** (Apple Silicon) | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/usr/local/var/log/unit/unit.log** (Intel), **/opt/homebrew/var/log/unit/unit.log** (Apple Silicon) | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **nobody** | + +{{}} + + +{{< note >}} +To run Unit as **root** on macOS: + +```console +$ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES +``` + +```console +$ sudo --preserve-env=OBJC_DISABLE_INITIALIZE_FORK_SAFETY /path/to/unitd ... +``` +{{< /note >}} + +### Node.js {#installation-nodejs-package} + +Unit's npm-hosted Node.js [module](https://www.npmjs.com/package/unit-http) +is called `unit-http`. Install it to run Node.js apps on Unit: + +1. First, install the **unit-dev/unit-devel** + [package]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}); it's needed to build `unit-http`. + +2. Next, build and install `unit-http` globally (this requires + `npm` and `node-gyp`): + + ```console + # npm install -g --unsafe-perm unit-http + ``` + + {{< warning >}} + The `unit-http` module is platform dependent due to optimizations; + you can't move it across systems with the rest of **node-modules**. + Global installation avoids such scenarios; just [relink]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) + the migrated app. + {{< /warning >}} + +3. It's entirely possible to run + [Node.js apps]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) + on Unit without mentioning **unit-http** in your app sources. + However, you can explicitly use **unit-http** in your code instead of the + built-in **http**, but mind that such frameworks as Express may require extra + [changes]({{< relref "/unit/howto/frameworks/express.md" >}}). + +{{< warning >}} +The `unit-http` module and `Unit` must have matching version numbers. +{{< /warning >}} + +If you update Unit later, make sure to update the module as well: + +```console +# npm update -g --unsafe-perm unit-http +``` + +{{< note >}} +You can also [configure]({{< relref "/unit/howto/modules.md#howto/source-modules-nodejs" >}}) and +[install]({{< relref "/unit/installation.md#source-bld-src-ext" >}}) the `unit-http` module from sources. +{{< /note >}} + +#### Working with multiple Node.js versions {#multiple-nodejs-versions} +
+Working with multiple Node.js versions + +To use Unit with multiple Node.js versions side by side, we recommend +[Node Version Manager](https://github.com/nvm-sh/nvm). + +```console +$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/x.y.z/install.sh | bash # Replace x.y.z with the nvm version +``` + +Install the versions you need and select the one you want to use with Unit: + +```console +$ nvm install 18 +``` + +```console +$ nvm install 16 +``` + +```console +$ nvm use 18 + Now using node v18.12.1 (npm v8.19.2) # Note the version numbers +``` + +Having selected the specific version, install the `node-gyp` module: + +```console +$ npm install -g node-gyp +``` + +Next, clone the Unit source code to build a `unit-http` module for the selected +Node.js version: + +```console +$ git clone https://github.com/nginx/unit +``` + +```console +$ cd unit +``` + +```console +$ pwd + /home/user/unit # Note the path to the source code +``` + +```console +$ ./configure +``` + +```console +$ ./configure nodejs + + configuring nodejs module + checking for node ... found + + node version v18.12.1 # Should be the version selected with nvm + checking for npm ... found + + npm version `8.19.2 # Should be the version selected with npm + checking for node-gyp ... found + + node-gyp version v9.3.0 +``` + +Point to Unit's header files and libraries in the source code directory +to build the module: + +```console +$ CPPFLAGS="-I/home/user/unit/include/" LDFLAGS="-L/home/user/unit/lib/" \ + make node-install +``` + +```console +$ npm list -g + + /home/vagrant/.nvm/versions/node/v18.12.1/lib + ├── corepack@0.14.2 + ├── node-gyp@9.3.0 + ├── npm@8.19.2 + └── unit-http@1.29.0 +``` + +That's all; use the newly built module to run your +[Node.js apps]({{< relref "/unit/configuration.md#configuration-nodejs" >}}) +on Unit as usual. +
+ +### Startup and shutdown {#installation-precomp-startup} + +{{< tabs name="Startup and shutdown" >}} +{{% tab name="Amazon, Debian, Fedora, RHEL, Ubuntu" %}} + +Enable Unit to launch automatically at system startup: + +```console +# systemctl enable unit +``` + +Start or restart Unit: + +```console +# systemctl restart unit +``` + +Stop a running Unit: + +```console +# systemctl stop unit +``` + +Disable Unit's automatic startup: + +```console +# systemctl disable unit +``` + +{{%/tab%}} +{{% tab name="macOS (Homebrew)" %}} + +Start Unit as a daemon: + +```console +# unitd +``` + +Stop all Unit's processes: + +```console +# pkill unitd +``` + +For startup options, see the +[Building from source]({{< relref "/unit/howto/source.md#source-startup" >}}) +documentation. +{{%/tab%}} +{{}} + +--- + +{{< note >}} Restarting Unit is necessary after installing or uninstalling any +language modules to pick up the changes. +{{< /note >}} + +## Community Repositories {#installation-community-repos} + +{{< warning >}} +These distributions are maintained by their respective communities, +not NGINX. Use them with caution. +{{< /warning >}} + +{{< tabs name="Community Repositories" >}} +{{% tab name="Alpine" %}} + +To install Unit's core executables from the Alpine Linux +[packages](https://pkgs.alpinelinux.org/packages?name=unit*): + +```console +# apk update +``` + +```console +# apk upgrade +``` + +```console +# apk add unit +``` + +To install service manager files and specific language modules: + +```console +# apk add unit-openrc unit-perl unit-php7 unit-python3 unit-ruby +``` + +```console +# service unit restart # Necessary for Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + +- **Startup and shutdown:** + + ```console + # service unit enable # Enable Unit to launch automatically at system startup + ``` + + ```console + # service unit restart # Start or restart Unit; one-time action + ``` + + ```console + # service unit stop # Stop a running Unit; one-time action + ``` + + ```console + # service unit disable # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="Alt" %}} + +To install Unit's core executables and specific language modules +from the Sisyphus [packages](https://packages.altlinux.org/en/sisyphus/srpms/unit): + +```console +# apt-get update +``` + +```console +# apt-get install unit +``` + +```console +# apt-get install unit-perl unit-php unit-python3 unit-ruby +``` + +```console +# service unit restart # Necessary for Unit to pick up any changes in language module setup +``` + +Versions of these packages with the ***-debuginfo** suffix contain symbols for +[debugging]({{< relref "/unit/troubleshooting.md#troubleshooting-core-dumps" >}}). + + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **_unit** (mind the **_** prefix) | + +{{}} + + +- **Startup and shutdown:** + + ```console + # service unit enable # Enable Unit to launch automatically at system startup + ``` + + ```console + # service unit restart # Start or restart Unit; one-time action + ``` + + ```console + # service unit stop # Stop a running Unit; one-time action + ``` + + ```console + # service unit disable # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="Arch" %}} + + +To install Unit's core executables and all language modules, +clone the [Arch User Repository (AUR)](https://aur.archlinux.org/pkgbase/nginx-unit/). + +```console +$ git clone https://aur.archlinux.org/nginx-unit.git +$ cd nginx-unit +``` + +Before proceeding further, verify that the **PKGBUILD** and the accompanying files +aren't malicious or untrustworthy. AUR packages are user produced without +pre-moderation; use them at your own risk. + +Next, build the package: + +```console +$ makepkg -si +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/nginx-unit.control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/nginx-unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **nobody** | + +{{}} + +- **Startup and shutdown:** + + ```console + # systemctl enable unit # Enable Unit to launch automatically at system startup + ``` + + ```console + # systemctl restart unit # Start or restart Unit; one-time action + ``` + + ```console + # systemctl stop unit # Stop a running Unit; one-time action + ``` + + ```console + # systemctl disable unit # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="FreeBSD" %}} + +To install Unit from +[FreeBSD packages](https://docs.freebsd.org/en/books/handbook/ports/#pkgng-intro>) +get the core package and other packages you need: + +```console +# pkg install -y unit +``` + +```console +# pkg install -y `libunit # Required to install the Node.js module +``` + +```console +# pkg install -y unit-java8 \ + unit-perl5.36 \ + unit-php81 unit-php82 unit-php83 \ + unit-python39 \ + unit-ruby3.2 \ + unit-wasm +``` + +```console +# service unitd restart # Necessary for Unit to pick up any changes in language module setup +``` + +To install Unit from [FreeBSD ports](https://docs.freebsd.org/en/books/handbook/ports/#ports-using), +start by updating your port collection. + +With `portsnap`: + +```console +# portsnap fetch update +``` + +With `git`: + +```console +# cd /usr/ports && git pull +``` + +Next, browse to the port path to build and install the core Unit port: + +```console +# cd /usr/ports/www/unit/ +``` + +```console +# make +``` + +```console +# make install +``` + +Repeat the steps for the other ports you need: +[libunit](https://www.freshports.org/devel/libunit/) +(required to install the Node.js +[module]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) +and build +[Go apps]({{< relref "/unit/configuration.md#configuration-go" >}}), +, +[unit-java](https://www.freshports.org/www/unit-java/), +[unit-perl](https://www.freshports.org/www/unit-perl/), +[unit-php](https://www.freshports.org/www/unit-php/), +[unit-python](https://www.freshports.org/www/unit-python/), +[unit-ruby](https://www.freshports.org/www/unit-ruby/), +or +[unit-wasm](https://www.freshports.org/www/unit-wasm/). + +After that, restart Unit: + +```console +# service unitd restart # Necessary for Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/var/run/unit/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **www** | + +{{}} + + +- **Startup and shutdown:** + + ```console + # service unitd enable # Enable Unit to launch automatically at system startup + ``` + + ```console + # service unitd restart # Start or restart Unit; one-time action + ``` + + ```console + # service unitd stop # Stop a running Unit; one-time action + ``` + + ```console + # service unitd disable # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="Gentoo" %}} + +To install Unit using [Portage](https://wiki.gentoo.org/wiki/Handbook:X86/Full/Portage), +update the repositoryand install the + +```console +# emerge --sync +``` + +```console +# emerge www-servers/nginx-unit +``` + +To install specific language modules and features, apply the corresponding +[USE flags](https://packages.gentoo.org/packages/www-servers/nginx-unit). + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/nginx-unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/nginx-unit** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **nobody** | + +{{}} + +- **Startup and shutdown:** + + ```console + # rc-update add nginx-unit # Enable Unit to launch automatically at system startup + ``` + + ```console + # rc-service nginx-unit restart # Start or restart Unit; one-time action + ``` + + ```console + # rc-service nginx-unit stop # Stop a running Unit; one-time action + ``` + + ```console + # rc-update del nginx-unit # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="NetBSD" %}} + +To install Unit's core package and the other packages you need +from the [NetBSD Packages Collection](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/www/unit/index.html): + +```console +# pkg_add unit +``` + +```console +# pkg_add libunit # Required to install the Node.js module +``` + +```console +# pkg_add unit-perl \ + unit-python2.7 \ + unit-python3.8 unit-python3.9 unit-python3.10 unit-python3.11 unit-python3.12 \ + unit-ruby31 unit-ruby32 unit-ruby33 +``` + +```console +# service unit restart # Necessary for Unit to pick up any changes in language module setup +``` + +To build Unit manually, start by updating the package collection: + +```console +# cd /usr/pkgsrc && cvs update -dP +``` + +Next, browse to the package path to build and install the core Unit binaries: + +```console +# cd /usr/pkgsrc/www/unit/ +``` + +```console +# make build install +``` + +Repeat the steps for the other packages you need: +[libunit](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/devel/libunit/index.html) +(required to install the Node.js +[module]({{< relref "/unit/installation.md#installation-nodejs-package" >}}) and build +[Go apps]({{< relref "/unit/configuration.md#configuration-go" >}}), +[unit-perl](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/www/unit-perl/index.html), +[unit-php](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/www/unit-php/index.html), +[unit-python](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/www/unit-python/index.html), +or +[unit-ruby](https://cdn.netbsd.org/pub/pkgsrc/current/pkgsrc/www/unit-ruby/index.html). + +Note that **unit-php** packages require the PHP package to be built with the **php-embed** option. To enable the option for **lang/php82**: + +```console +# echo "PKG_OPTIONS.php82=php-embed" >> /etc/mk.conf +``` + +After that, restart Unit: + +```console +# service unit restart # Necessary for Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/var/run/unit/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + +- **Startup and shutdown:** + +First, add Unit's startup script to the **/etc/rc.d/** directory: + + ```console + # cp /usr/pkg/share/examples/rc.d/unit /etc/rc.d/ + ``` + + After that, you can start and stop Unit as follows: + + ```console + # service unit restart # Start or restart Unit; one-time action + ``` + + ```console + # service unit stop # Stop a running Unit; one-time action + ``` + +To enable or disable Unit's automatic startup, edit **/etc/rc.conf**: + +```ini +# Enable service: +unit=YES + +# Disable service: +unit=NO +``` + +{{%/tab%}} +{{%tab name="Nix" %}} + +To install Unit's core executables and all language modules using the +[Nix](https://nixos.org) package manager, update the channel, check if Unit's +available, and install the [package](https://github.com/NixOS/nixpkgs/tree/master/pkgs/servers/http/unit) + +```console +$ nix-channel --update +$ nix-env -qa 'unit' +$ nix-env -i unit +``` + +This installs most embedded language modules and such features as SSL or IPv6 support. +For a full list of optionals, see the +[package definition]https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/http/unit/default.nix); +for a **.nix** configuration file defining an app, see +[this sample](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/web-servers/unit-php.nix). + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/unit/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + +- **Startup and shutdown:** + + Add **services.unit.enable = true;** to **/etc/nixos/configuration.nix** + and rebuild the system configuration: + + ```console + # nixos-rebuild switch + ``` + + After that, use `systemctl`: + + ```console + # systemctl enable unit # Enable Unit to launch automatically at system startup + ``` + + ```console + # systemctl restart unit # Start or restart Unit; one-time action + ``` + + ```console + # systemctl stop unit # Stop a running Unit; one-time action + ``` + + ```console + # systemctl disable unit # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{%tab name="OpenBSD" %}} + +To install Unit from [OpenBSD packages](https://www.openbsd.org/faq/faq15.html) +get the core package and other packages you need: + +```console +# pkg_add unit +``` + +```console +# pkg_add unit-perl +``` + +```console +# pkg_add unit-php74 +``` + +```console +# pkg_add unit-php80 +``` + +```console +# pkg_add unit-php81 +``` + +```console +# pkg_add unit-php82 +``` + +```console +# pkg_add unit-php83 +``` + +```console +# pkg_add unit-python +``` + +```console +# pkg_add unit-ruby +``` + +```console +# rcctl restart unit # Necessary for Unit to pick up any changes in language module setup +``` + +To install Unit from [OpenBSD ports](https://pkgsrc.se/www/unit), +start by updating your port collection, for example: + +```console +$ cd /usr/ +``` + +```console +$ cvs -d anoncvs@anoncvs.spacehopper.org:/cvs checkout -P ports +``` + +Next, browse to the port path to build and install Unit: + +```console +$ cd /usr/ports/www/unit/ +``` + +```console +$ make +``` + +```console +# make install +``` + +This also installs the language modules for Perl, PHP, Python, and Ruby; +other modules can be built and installed from +[source]({{< relref "/unit/howto/source.md" >}}). + +After that, restart Unit: + +```console +# rcctl restart unit # Necessary for Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-----------------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/var/run/unit/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **_unit** | + +{{}} + + +- **Startup and shutdown:** + + ```console + # rcctl enable unit # Enable Unit to launch automatically at system startup + ``` + + ```console + # rcctl restart unit # Start or restart Unit; one-time action + ``` + + ```console + # rcctl stop unit # Stop a running Unit; one-time action + ``` + + ```console + # rcctl disable unit # Disable Unit's automatic startup + +{{%/tab%}} +{{%tab name="Remi's RPM" %}} + +[Remi's RPM repository](https://blog.remirepo.net/post/2019/01/14/PHP-with-the-NGINX-unit-application-server>), +which hosts the latest versions of the PHP stack for Fedora and RHEL, +also has the core Unit package and the PHP modules. + +To use Remi's versions of Unit's packages, configure the +[repository](https://blog.remirepo.net/pages/Config-en) +first. +Remi's PHP language modules are also compatible with the core Unit package from +[our own repository]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}). + +Next, install Unit and the PHP modules you want: + +```console +# yum install --enablerepo=remi unit \ + php54-unit-php php55-unit-php php56-unit-php \ + php70-unit-php php71-unit-php php72-unit-php php73-unit-php php74-unit-php \ + php80-unit-php php81-unit-php php82-unit-php +``` + +```console +# systemctl restart unit # Necessary for Unit to pick up any changes in language module setup +``` + +{{}} + +| Runtime details | Description | +|-------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/source.md#source-startup" >}}) | **/run/unit/control.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | **/var/log/unit/unit.log** | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **nobody** | + +{{}} + + +- **Startup and shutdown:** + + ```console + # systemctl enable unit # Enable Unit to launch automatically at system startup + ``` + + ```console + # systemctl restart unit # Start or restart Unit; one-time action + ``` + + ```console + # systemctl stop unit # Stop a running Unit; one-time action + ``` + + ```console + # systemctl disable unit # Disable Unit's automatic startup + ``` + +{{%/tab%}} +{{}} + +--- + +## Docker Images {#installation-docker} + +Unit's Docker images +come in several language-specific flavors: + +{{}} + +| Tag | Description | +|--------------------------------------|--------------------------------------------------------------------------------------------------| +| `{{< param "unitversion" >}}-minimal` | No language modules; based on the **debian:bullseye-slim** [image](https://hub.docker.com/_/debian). | +| `{{< param "unitversion" >}}-go1.21` | Single-language; based on the **golang:1.21** [image](https://hub.docker.com/_/golang). | +| `{{< param "unitversion" >}}-jsc11` | Single-language; based on the **eclipse-temurin:11-jdk** [image](https://hub.docker.com/_/eclipse-temurin). | +| `{{< param "unitversion" >}}-node20` | Single-language; based on the **node:20** [image](https://hub.docker.com/_/node). | +| `{{< param "unitversion" >}}-perl5.38`| Single-language; based on the **perl:5.38** [image](https://hub.docker.com/_/perl). | +| `{{< param "unitversion" >}}-php8.2` | Single-language; based on the **php:8.2-cli** [image](https://hub.docker.com/_/php). | +| `{{< param "unitversion" >}}-python3.11` | Single-language; based on the **python:3.11** [image](https://hub.docker.com/_/python). | +| `{{< param "unitversion" >}}-ruby3.2` | Single-language; based on the **ruby:3.2** [image](https://hub.docker.com/_/ruby). | +| `{{< param "unitversion" >}}-wasm` | Single-language; based on the **debian:bullseye-slim** [image](https://hub.docker.com/_/debian). | + +{{}} + + +### Customizing language versions in Docker images {#inst-lang-docker} + +
+Customizing language versions in Docker images + +To build a custom language version image, clone and rebuild the sources locally +with Docker installed: + +```console +$ make build- VERSIONS_= +``` + +The `make` utility parses the command line to extract the language name and version; +these values must reference an existing official language image to be used as the base +for the build. +If not sure whether an official image exists for a specific language version, +follow the links in the tag table above. + +{{< note >}} +Unit relies on the official Docker images, so any customization method offered by their +maintainers is equally applicable; to tailor a Unit image to your needs, +see the quick reference for its base image. +{{< /note >}} + +The language name can be **go**, **jsc**, **node**, **perl**, **php**, +**python**, or **ruby**; the version is defined as **\.\**, +except for **jsc** and **node** that take only major version numbers (as seen in +the tag table). + +Thus, to create an image with Python 3.10 +and tag it as **unit:{{< param "unitversion" >}}-python3.10**: + +```console +$ git clone https://github.com/nginx/unit +``` + +```console +$ cd unit +``` + +```console +$ git checkout {{< param "unitversion" >}} # Optional; use to choose a specific Unit version +``` + +```console +$ cd pkg/docker/ +``` + +```console +$ make build-python3.10 VERSIONS_python=3.10 # Language and version +``` + +For details, see the [Makefile](https://github.com/nginx/unit/blob/master/pkg/docker/Makefile). +For other customization scenarios, see the [Docker howto]({{< relref "/unit/howto/docker.md">}}). +
+ +
+Image tags for pre-1.29.1 Unit versions +Before Unit 1.29.1 was released, our Docker images were available +from the official [NGINX repository](https://hub.docker.com/r/nginx/unit/) +on Docker Hub. +
+ +
+Images with pre-1.22.0 Unit versions +Before Unit 1.22.0 was released, the following tagging scheme was used: + +{{}} + +| Tag | Description | +|----------------------|-----------------------------------------------------------------------------| +| **\-full** | Contains modules for all languages that Unit then supported. | +| **\-minimal**| No language modules were included. | +| **\-\** | A specific language module such as **1.21.0-ruby2.3** or **1.21.0-python2.7**. | + +{{}} + + +
+ +You can obtain the images from these sources: + +{{< tabs name="Docker" >}} +{{% tab name="Docker Hub" %}} + + +To install and run Unit from [official builds](https://hub.docker.com/_/unit) +at Docker Hub: + +```console +$ docker pull unit:TAG # Specific image tag; see above for a complete list +``` + +```console +$ docker run -d unit:TAG # Specific image tag; see above for a complete list +``` + +{{%/tab%}} +{{% tab name="Amazon ECR Public Gallery" %}} + +To install and run Unit from NGINX's [repository](https://gallery.ecr.aws/nginx/unit) +at Amazon ECR Public Gallery: + +```console +$ docker pull public.ecr.aws/nginx/unit:TAG # Specific image tag; see above for a complete list +``` + +```console +$ docker run -d public.ecr.aws/nginx/unit:TAG # Specific image tag; see above for a complete list +``` + +{{%/tab%}} +{{% tab name="packages.nginx.org" %}} + +{{< warning >}} +Unit's 1.30+ image tarballs aren't published on the website; this channel is deprecated. +{{< /warning >}} + +To install and run Unit from the tarballs stored on our +[website](https://packages.nginx.org/unit/docker/): + +```console +$ curl -O https://packages.nginx.org/unit/docker/1.29.1/nginx-unit-TAG.tar.gz # Specific image tag; see above for a complete list +``` + +```console +$ curl -O https://packages.nginx.org/unit/docker/1.29.1/nginx-unit-TAG.tar.gz.sha512 # Specific image tag; see above for a complete list +``` + +```console +$ sha512sum -c nginx-unit-TAG.tar.gz.sha512 # Specific image tag; see above for a complete list + nginx-unit-TAG.tar.gz: OK # Specific image tag; see above for a complete list +``` + +```console +$ docker load < nginx-unit-TAG.tar.gz # Specific image tag; see above for a complete list +``` + +{{%/tab%}} +{{}} + +--- + +{{}} +| Runtime details | Description | +|-------------------------------------|-----------------------------------------------| +| Control [socket]({{< relref "/unit/howto/security.md#sec-socket" >}}) | **/var/run/control.unit.sock** | +| Log [file]({{< relref "/unit/troubleshooting.md#troubleshooting-log" >}}) | Forwarded to the [Docker log collector](https://docs.docker.com/config/containers/logging/) | +| Non-privileged [user and group]({{< relref "/unit/howto/security.md#security-apps" >}}) | **unit** | + +{{}} + + +For more details, see the repository pages ([Docker Hub](https://hub.docker.com/_/unit), +[Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/unit)) +and our [Docker howto]({{< relref "/unit/howto/docker.md">}}). + +### Initial configuration {#installation-docker-init} + +The official images support initial container configuration, +implemented with an **ENTRYPOINT** +[script](https://docs.docker.com/engine/reference/builder/#entrypoint). +First, the script checks the Unit +[state directory]({{< relref "/unit/howto/source.md#source-config-src-state" >}}) +in the container +(**/var/lib/unit/**). + +If it's empty, +the script processes certain file types +in the container's **/docker-entrypoint.d/** directory: + +{{}} + +| File Type | Purpose/Action | +|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **.pem** | [Certificate bundles]({{< relref "/unit/certificates.md">}}), uploaded under respective names:

**cert.pem** to **/certificates/cert**. | +| **.json** | [Configuration snippets]({{< relref "/unit/controlapi.md#configuration-mgmt" >}}), uploaded to the **/config** section of Unit's configuration. | +| **.sh** | Shell scripts, run after the **.pem** and **.json** files are uploaded. Use shebang in your scripts to specify a custom shell;

must be executable. | + +{{
}} + + +The script warns about any other file types +in **/docker-entrypoint.d/**. + +This mechanism enables +customizing your containers at startup, +reusing configurations, +and automating workflows to reduce manual effort. +To use the feature, +add **COPY** directives for certificate bundles, +configuration fragments, +and shell scripts +to your **Dockerfile** derived from an official image: + +```dockerfile +FROM unit:{{< param "unitversion" >}}-minimal +COPY ./*.pem /docker-entrypoint.d/ +COPY ./*.json /docker-entrypoint.d/ +COPY ./*.sh /docker-entrypoint.d/ +``` + +{{< note >}} +Mind that running Unit even once populates its state directory; +this prevents the script from executing, so this script-based initialization must occur +before you run Unit in your derived container. +{{< /note >}} + +This feature comes in handy if you want to tie Unit to a certain app configuration +for later use. For ad-hoc initialization, you can mount a directory with configuration files to a container at startup: + +```console +$ docker run -d --mount \ + type=bind,src=/path/to/config/files/,dst=/docker-entrypoint.d/ \ # Use a real path instead + unit:{{< param "unitversion" >}}-minimal +``` + +## Source Code {#source} + +You can get Unit's source code from our official GitHub repository or as a tarball. + +{{< tabs name="Source Code" >}} +{{% tab name="Git" %}} + +```console +$ git clone https://github.com/nginx/unit # Latest updates to the repository +``` + +```console +$ # -- or -- +``` + +```console +$ git clone -b {{< param "unitversion" >}} https://github.com/nginx/unit # Specific version tag; see https://github.com/nginx/unit/tags +``` + +```console +$ cd unit +``` + +{{%/tab%}} +{{% tab name="Tarball" %}} + +```console +$ curl -O https://sources.nginx.org/unit/unit-{{< param "unitversion" >}}.tar.gz +``` + +```console +$ tar xzf unit-{{< param "unitversion" >}}.tar.gz +``` + +```console +$ cd unit-{{< param "unitversion" >}} +``` + +{{%/tab%}} +{{}} + +--- + +To build Unit and specific language modules from these sources, +refer to the [source code howto]({{< relref "/unit/howto/source.md" >}}). +to package custom modules, see the +[module howto]({{< relref "/unit/howto/modules.md#modules-pkg" >}}). diff --git a/content/unit/keyfeatures.md b/content/unit/keyfeatures.md new file mode 100644 index 000000000..613bb327f --- /dev/null +++ b/content/unit/keyfeatures.md @@ -0,0 +1,53 @@ +--- +title: Key features +weight: 200 +toc: true +--- + +From the start, our vision for Unit was versatility, speed, and reliability. Here's how we tackle these goals. + +## Flexibility + +- The [entire configuration]({{< relref "/unit/controlapi.md#configuration-api/">}}) is managed dynamically over HTTP via a friendly [RESTful JSON API]({{< relref "/unit/controlapi.md#configuration-mgmt">}}). +- Updates to the configuration are performed granularly at runtime with zero interruption. +- Requests are [routed]({{< relref "/unit/configuration.md#configuration-routes">}}) between [static content]({{< relref "/unit/configuration.md#configuration-static">}}), upstream [servers]({{< relref "/unit/configuration.md#configuration-proxy">}}), and local [apps]({{< relref "/unit/configuration.md#configuration-applications">}}). +- Request filtering and dispatching uses elaborate [matching rules]({{< relref "/unit/configuration.md#configuration-routes-matching">}}) that enable [regular expressions]({{< relref "/unit/configuration.md#configuration-routes-matching-patterns">}}), [response header]({{< relref "/unit/configuration.md#configuration-response-headers">}}) awareness, and `njs` [scripting]({{< relref "/unit/scripting/">}}). +- Apps in multiple languages and language versions run [side by side]({{< relref "/unit/configuration.md#configuration-applications">}}). +- Server-side [WebAssembly]({{< relref "/unit/configuration.md#configuration-wasm">}}) is natively supported. +- Common [language-specific APIs]({{< relref "/unit/howto/overview.md#howto-frameworks">}}) for all supported languages run seamlessly. +- Upstream [server groups]({{< relref "/unit/configuration.md#configuration-upstreams">}}) provide dynamic load balancing using a weighted round-robin method. +- Originating IP identification [supports]({{< relref "/unit/configuration.md#configuration-listeners-xff">}}) **X-Forwarded-For** and similar header fields. + +## Performance + +- Requests are asynchronously processed in threads with efficient event loops (`epoll`, `kqueue`). +- Syscalls and data copy operations are kept to a necessary minimum. +- 10,000 inactive HTTP keep-alive connections take up only a few MBs of memory. +- Router and app processes rely on low-latency IPC built with lock-free queues over shared memory. +- Built-in [statistics]({{< relref "/unit/statusapi.md">}}) provide insights into Unit's performance. +- The number of per-app processes is defined statically or [scales]({{< relref "/unit/configuration.md#configuration-proc-mgmt-prcs">}}) preemptively within given limits. +- App and instance usage statistics are collected and [exposed]({{< relref "/unit/statusapi.md">}}) via the API. +- Multithreaded request processing is supported for [Java]({{< relref "/unit/configuration.md#configuration-java">}}), [Perl]({{< relref "/unit/configuration.md#configuration-perl">}}), [Python]({{< relref "/unit/configuration.md#configuration-python">}}), and [Ruby]({{< relref "/unit/configuration.md#configuration-ruby">}}) apps. + +## Security & robustness + +- Client connections are handled by a separate non-privileged router process. +- Low-resource conditions (out of memory or descriptors) and app crashes are handled gracefully. +- [SSL/TLS]({{< relref "/unit/certificates.md">}}) with [SNI]({{< relref "/unit/configuration.md#configuration-listeners-ssl">}}), [session cache and tickets]({{< relref "/unit/configuration.md#configuration-listeners-ssl-sessions">}}) is integrated (OpenSSL 1.0.1 and later). +- Different apps are isolated in separate processes. +- Apps can be additionally containerized with namespace and file system [isolation]({{< relref "/unit/configuration.md#configuration-proc-mgmt-isolation">}}). +- Static file serving benefits from [chrooting]({{< relref "/unit/configuration.md#configuration-share-path">}}), symlink and mount point [traversal restrictions]({{< relref "/unit/configuration.md#configuration-share-resolution">}}). + +## Supported app languages + +Unit interoperates with: + +- [Binary-compiled languages](https://www.nginx.com/blog/nginx-unit-adds-assembly-language-support/) in general: using the embedded `libunit` library. +- [Go]({{< relref "/unit/configuration.md#configuration-go">}}): by [overriding]({{< relref "/unit/configuration.md#updating-go-apps">}}) the `http` module. +- [JavaScript (Node.js)]({{< relref "/unit/configuration.md#configuration-nodejs">}}): by automatically [overloading]({{< relref "/unit/installation.md#installation-nodejs-package">}}) the `http` and `websocket` modules. +- [Java]({{< relref "/unit/configuration.md#configuration-java">}}): by using the Servlet Specification 3.1 and WebSocket APIs. +- [Perl]({{< relref "/unit/configuration.md#configuration-perl">}}): by using PSGI. +- [PHP]({{< relref "/unit/configuration.md#configuration-php">}}): by using a custom SAPI module. +- [Python]({{< relref "/unit/configuration.md#configuration-python">}}): by using WSGI or ASGI with WebSocket support. +- [Ruby]({{< relref "/unit/configuration.md#configuration-ruby">}}): by using the Rack API. +- [WebAssembly]({{< relref "/unit/configuration.md#configuration-wasm">}}): by using Wasmtime. diff --git a/content/unit/news/2023/_index.md b/content/unit/news/2023/_index.md new file mode 100644 index 000000000..d452ec906 --- /dev/null +++ b/content/unit/news/2023/_index.md @@ -0,0 +1,4 @@ +--- +title: 2023 +weight: 300 +--- \ No newline at end of file diff --git a/content/unit/news/2023/unit-1.30.0-released.md b/content/unit/news/2023/unit-1.30.0-released.md new file mode 100644 index 000000000..fcd7fae4c --- /dev/null +++ b/content/unit/news/2023/unit-1.30.0-released.md @@ -0,0 +1,344 @@ +--- +title: Unit 1.30.0 Released +weight: 300 +--- + +We are happy to announce Unit 1.30.0! This release brings a new level of +sophistication to Unit’s configuration and enhances logging controls. + +- Incoming URIs can now be rewritten as part of the routing process +- Configuration values can now be evaluated by referencing JavaScript modules + and functions +- Each application can now write its diagnostic output to log files (or not at + all) +- The steps taken by the router can now be logged for diagnostic purposes + +Alongside 1.30.0, we are pleased to present [Docker official Images for NGINX +Unit](https://hub.docker.com/_/unit) and the new [OpenAPI specification](https://github.com/nginx/unit/blob/master/docs/unit-openapi.yaml) for +Unit’s control API. + +## URI Rewrite + +Rewriting the incoming URI to match the expectations of the application or +filesystem layout has been a much-requested feature. Now, the URI can be +rewritten by specifying a `rewrite` rule in a +[route action]({{< relref "/unit/configuration.md#configuration-routes-action" >}}). +. + +In this example, all requests have `/api/v2` prepended to the original +URI before being passed to the application: + +```json +{ + "routes": [ + { + "action": { + "rewrite": "/api/v2$uri", + "pass": "applications/my_api" + } + } + ] +} +``` + +With web applications and APIs, some URIs may need to be removed or replaced. +Unit simplifies the management of these URIs by utilizing the recursive nature +of routing. Rewritten requests can be sent back to start the routing process +again. + +```json +{ + "routes": [ + { + "match": { + "uri": ["/api/v1/lookup", "/api/v2beta/search"] + }, + "action": { + "rewrite": "/api/v2/search", + "pass": "routes" + } + }, + { + "action": { + "pass": "applications/my_api" + } + } + ] +} +``` + +The rewritten URI can also be constructed programmatically by using JavaScript +expressions in the value. In this example, we convert all request URIs to +lowercase using the built-in capabilities of JavaScript: + +```json +{ + "routes": [ + { + "action": { + "rewrite": "`${uri.toLowerCase()}`", + "share": "/var/www$uri" + } + } + ] +} +``` + +{{< note >}} +The path portion of the incoming URI is the only part that is rewritten, +and any original queries or arguments (anything following the `?` +character) are preserved. + +The `$uri` and `$request_uri` variables are both updated during +the rewrite process. When using custom log formatting, use the +\$request_line variable to log the original URI. +{{< /note >}} + +## Application Logging + +Another new feature addresses a crucial need for diagnostics in application +management. One of the strengths of Unit is its ability to host multiple +applications simultaneously under a single daemon. However, this capability is +also a challenge when managing multiple application and error logs. + +Today, we're excited to introduce per-application logging that allows you to +define the file where `stdout` and `stderr` streams will be +directed for each application. This makes it easy to access necessary log +entries when troubleshooting issues. The logging interface is independent of +the application module or language you use. As an example, let's consider a +Java SpringBoot application. + +```json +"applications": { + "my_spring_app": { + "type": "java", + "stdout": "/var/log/catalina.out", + "stderr": "/var/log/spring_err.log", + "webapp": "spring-0.0.1-SNAPSHOT.war", + "working_directory": "/var/www/" + } +} +``` + +Now, in an application configuration object, you can define a file target for +`stdout` as well as `stderr`. + +By default, application logging is directed to {file}`/dev/null` (no output). +However, if `unitd` is started with the `--no-daemon` option, +application logging is sent to the console. + +## Router Diagnostic Logging + +Unit’s router is a powerful tool for handling incoming requests and taking +appropriate action. It's often used to offload request routing from application +frameworks, allowing Unit to serve static files and freeing up the framework to +focus on what it does best, i.e. dynamic content. However, as the +`routes` object grows in size and complexity, diagnosing why a request +was not handled as expected can be daunting. + +With this release, you can now enable diagnostic logging of the routing process +to have full transparency on how each request is handled, including URI +rewrites. This feature is enabled by the `log_route` option in the +`settings/http` configuration object: + +```json +{ + "settings": { + "http": { + "log_route": true + } + } +} +``` + +Remember the second URI rewrite example above? This is how its logs might look +after a request: + +```none + [notice] 79575#31129125 *52 http request line "GET /api/v1/search?q=help HTTP/1.1" + [notice] 79575#31129125 *52 "routes/0" selected + [notice] 79575#31129125 *52 URI rewritten to "/api/v2/search" + [info] 79575#31129125 *52 "routes/0" discarded + [notice] 79575#31129125 *52 "routes/1" selected +``` + +Here, quoted route identifiers are URIs to the `/config` object in Unit's +control API, allowing direct access to more information about the route and its +corresponding action: + +```console +$ unitc /config/routes/1 + + { + "action": { + "pass": "applications/my_api" + } + } +``` + +## JavaScript Modules + +Unit 1.29.0 added {doc}`NGINX JavaScript integration +<../2022/unit-1.29.0-released>`, allowing the use of JavaScript expressions in +configuration values. But managing complex JavaScript code within configuration +values can be difficult. With the latest release, JavaScript code can be +separated from configuration and managed as a standalone entity. Then, you can +use your JavaScript functions in configuration values, unlocking the full power +of configuration scripting. + +JavaScript functions can extend Unit’s functionality in the following ways: + +- Performing complex URI rewrites or sending `3xx` redirects +- Extracting attributes from cookies or authentication tokens for logging or + routing +- Augmenting the router with business logic that goes beyond what `match` + offers + +As an example, let’s split clients across two versions of an application as a +[blue/green deployment](https://en.wikipedia.org/wiki/Blue-green_deployment) +on a single Unit instance. + +This JavaScript module ({file}`split.js`) exports a single function that +accepts two parameters, `variant` (string) and `proportion` (number +0..1). The string `"green"` is returned if an MD5 hash of the +`variant` falls within a certain `proportion` of the address space. +MD5 produces a 128-bit value; we convert that to a positive integer and +multiply the proportion by 65536 for comparability. + +```javascript +function clients(variant, proportion) { + var c = require('crypto'); + var i = c.createHash('md5').update(variant).digest().readInt16BE() + 32768; + return (proportion * 65536) > i ? 'green' : 'blue'; +} + +export default { clients } +``` + +JavaScript modules are managed similarly to TLS certificates in that they can +be uploaded via the control API to be referenced in the configuration. To +upload a module, `PUT` it as a resource under `/js_modules`: + +```json +$ curl --unix-socket /path/to/control.sock -X PUT -d@split.js http://localhost/js_modules/split +$ # --- OR --- +$ unitc /js_modules/split < split.js +``` + +Now let’s use our function to decide which version of the application each +request is sent to: + +```json +{ + "settings": { + "js_module": "split" + }, + + "listeners": { + "*:8080": { + "pass": "`applications/${split.clients(remoteAddr,0.25)}`" + } + }, + + "applications": { + "blue": { … }, + "green": { … } + } +} +``` + +With this configuration in place, 25% of all client IP addresses will be +directed to the `green` application, the remainder to the `blue` +application. The proportion can be changed on the fly by updating the value of +`/config/listeners/*:8080/pass`. Notice that the uploaded JavaScript +module must also be enabled in `settings`. For multiple modules, the +`js_module` value is an array of strings. + +## Configurable Server Header + +Unit includes a `Server` header in every response by default, identifying +itself and its version. It's recommended to exclude the version number for +security reasons in production environments. Now, this can be achieved by +setting the `server_version` value in +[settings]({{< relref "/unit/configuration.md#configuration-stngs" >}}). + +## Updates for Language Modules + +- Java 20 is now available on Ubuntu 23.04 +- Expanded PHP’s HTTP response code range: missing files now return + `404`, inaccessible files return `403` +- Added support for `filter_input()` + +## Docker Official Images + +We are thrilled that the Unit project has been recognized by Docker with +[official images on Docker Hub](https://hub.docker.com/_/unit). + +Each of the Unit Docker Official Images are built from the official images for +the programming language in question. They now also support x86 and arm64 +platforms. + +Getting started with Unit on Docker is now as simple as: + +```console +$ docker pull unit +``` + +Please update your automation pipelines to acquire images from +. + +## OpenAPI Specification + +We are glad to announce the first public release of our [OpenAPI specification](https://github.com/nginx/unit/blob/master/docs/unit-openapi.yaml) for NGINX +Unit. It aims to simplify configuring and integrating NGINX Unit deployments +and provide an authoritative source of knowledge about the control API. + +Although the specification is still in the early beta stage, it is a promising +step forward for the NGINX Unit community. While working on it, we kindly ask +you to experiment and provide feedback to help improve its functionality and +usability. + +## Changes in Behavior + +The `configure` script for building Unit from source now has a default +`/usr/local/` value for `--prefix`, simplifying the process of +creating installable builds or packages. + +Also, new configure options allow more precise control of Unit's directories, +replacing the deprecated `--incdir`, `--modules`, `--state`, +and `--tmp` options that will be removed in the future. See +`configure --help` for details. + +Finally, Unix domain listen sockets are now removed when `unitd` shuts +down. + +## Full Changelog + +```none +Changes with Unit 1.30.0 10 May 2023 + + *) Change: remove Unix domain listen sockets upon reconfiguration. + + *) Feature: basic URI rewrite support. + + *) Feature: NJS loadable modules support. + + *) Feature: per-application logging. + + *) Feature: conditional logging of route selection. + + *) Feature: support the keys API on the request objects in NJS. + + *) Feature: default values for 'make install' pathnames such as prefix; + this allows to './configure && make && sudo make install'. + + *) Feature: "server_version" setting to omit the version token from + "Server" header field. + + *) Bugfix: request header field values could be corrupted in some cases; + the bug had appeared in 1.29.0. + + *) Bugfix: PHP error handling (added missing 403 and 404 errors). + + *) Bugfix: Perl applications crash on second responder call. +``` diff --git a/content/unit/news/2023/unit-1.31.0-released.md b/content/unit/news/2023/unit-1.31.0-released.md new file mode 100644 index 000000000..2b9360d5d --- /dev/null +++ b/content/unit/news/2023/unit-1.31.0-released.md @@ -0,0 +1,240 @@ +--- +title: Unit 1.31.0 Released +weight: 200 +--- + +We are delighted to announce Unit 1.31.0, which marks a significant milestone +following the release of 1.30 in May. Over the past 15 weeks, our dedicated +team has been hard at work developing an innovative language module for NGINX +Unit. We are excited to introduce the Unit WebAssembly (WASM) feature as part +of this release, which represents a significant leap forward in Unit's +capabilities. + +This is a technology preview of WebAssembly support in Unit, and we look +forward to learning more about the use cases and ideas that the community will +share with us. + +This release also brings a notable addition to our repertoire - the ability to +send response headers and harness the power of response header variables within +the configuration. These enhancements will greatly increase the flexibility and +customization options available to you. + +Complementing these key advancements, we have carefully addressed a spectrum of +minor bug fixes and introduced additional refinements to ensure a seamlessly +enhanced user experience. + +- Thanks to our newest contributor, + [synodriver](https://github.com/nginx/unit/commits?author=synodriver) + for adding Python support to ASGI `lifespan_state` +- the `unitc` CLI tool now provides interactive editing of + configuration URIs + +## Server-Side WebAssembly: Technology Preview + +WebAssembly adoption has grown rapidly over the past two years. The flexibility +that comes with this new binary format is remarkable. In particular, +server-side WebAssembly offers many advantages for application developers. +Since Unit already provides native support for various programming language +runtimes, it was a natural progression to take on the challenge of adding +server-side WebAssembly support to NGINX Unit. + +Unit can now run WebAssembly modules as a native application type. Read more in +our blog post: +[Introducing a Technology Preview for Server-Side WebAssembly on NGINX Unit](https://www.nginx.com/blog/server-side-webassembly-nginx-unit/). + +## Working with Response Headers + +Having full control over the HTTP response headers sent back to the client is a +feature our community has been waiting for. With 1.31, we are adding support +for adding, removing, or overriding HTTP response headers using the Unit router +and using the values in dedicated response header variables. Let's see what we +can do with 1.31 and response headers. + +### Set Response Headers + +As mentioned above, you use Unit’s router to add, remove, or override response +headers. In most cases, the router will already be in use. If your listener +points to a router object like this, you are good to go. + +```json +{ + "listeners": { + "*:80": { + "pass": "routes" + } + } +} +``` + +If not, and you are new to the world of Unit Routes, be sure to read the +[documentation]({{< relref "/unit/configuration.md#configuration-routes" >}}) +before diving into this new +feature. + +Let's start with a simple use case. We are using Unit to host a frontend along +with a web API. The languages or the Unit application object do not really +matter in this case. The current configuration looks like this: + +```json +{ + "listeners": { + "*:8080": { + "pass": "routes/app" + } + }, + + "routes": { + "app": [ + { + "match": { + "uri": [ + "/api/*" + ] + }, + + "action": { + "pass": "applications/api" + } + }, + { + "action": { + "share": [ + "/var/www/frontend$uri", + "/var/www/frontend/index.html" + ] + } + } + ] + } +} +``` + +A newly introduced `response_headers` object can be added to any +`action` object. The `response_headers` object contains a list of +key/value pairs, each of which defines a single header. If a header name +matches a response header already present in the response, its value is +replaced. Otherwise, a new response header is created. A value of `null` +omits the header from the response. An empty string does not. Let's change the +configuration to demonstrate what all this means. First, we want to hide an +`X-Version` header sent by the API application: + +```json +{ + "action": { + "pass": "applications/api", + "response_headers": { + "X-Version": null + } + } +} +``` + +For our front-end, we want to add a version hash to identify the deployed +version without digging into the sources: + +```json +{ + "action": { + "share": [ + "/var/www/frontend$uri", + "/var/www/frontend/index.html" + ], + "response_headers": { + "X-FE-Version": "abc1234def" + } + } +} +``` + +In addition to fixed values, you can call an NJS function to create a value +using some more complex rules. To do this, use a template literal: + +```json +"Upper-Case": "`${host.toUpperCase()}`" +``` + +If this sounds all new to you, read more about the NGINX JavaScript Engine in +Unit in our [documentation]({{< relref "/unit/scripting.md" >}}). + +### Use Response Header Variables + +With 1.31 and the ability to control response headers, we have added a new set +of variables. When Unit receives a response from an application hosted on Unit, +and you want to modify an existing response header based on the value that was +shared by the application, it becomes imperative for Unit to retain that +specific value. This is where the newly introduced response header variables +come into play. + +The format of the new variables is based on other variables that Unit already +supports in the router. To retrieve the value of a particular HTTP header, use +`response_header` as the key identifier, followed by +`name_of_the_header` enclosed in `${}`. If you are new to using +variables with Unit during request processing, use this +[documentation]({{< relref "/unit/configuration.md#configuration-variables" >}}) +to learn more. Let's look at this through an example +use case. + +In the following configuration, we want to add a charset to the +`Content-Type` response header that was already set by the application: + +```json +[ + { + "action": { + "pass": "applications/calc", + "response_headers": { + "Content-Type": "${response_header_content_type};charset=iso-8859-1" + } + } + } +] +``` + +Since the `Content-Type` header already exists in the response, Unit will +change its value. + +## CLI Interactive Mode + +In 1.29, we introduced a wrapper script for curl to simplify interaction with +the Unit API. In 1.31, we added an interactive edit mode to this script: + +```console +$ unitc EDIT /config +``` + +This opens the given endpoint's JSON configuration in the editor currently +defined in `$EDITOR`. In most cases this will default to +`nano`. If you want to use something else, like `vim`: + +```console +$ EDITOR=vim unitc EDIT /config +``` + +Saving the changes automatically applies the changes and reconfigures Unit. + +## Changes in Behavior + +Nothing new here. + +## Full Changelog + +```none +Changes with Unit 1.31.0 31 Aug 2023 + + *) Change: if building with njs, version 0.8.0 or later is now required. + + *) Feature: technology preview of WebAssembly application module. + + *) Feature: "response_headers" option to manage headers in the action + and fallback. + + *) Feature: HTTP response header variables. + + *) Feature: ASGI lifespan state support. Thanks to synodriver. + + *) Bugfix: ensure that $uri variable is not cached. + + *) Bugfix: deprecated options were unavailable. + + *) Bugfix: ASGI applications inaccessible over IPv6. +``` diff --git a/content/unit/news/2023/unit-1.31.1-released.md b/content/unit/news/2023/unit-1.31.1-released.md new file mode 100644 index 000000000..aa7476551 --- /dev/null +++ b/content/unit/news/2023/unit-1.31.1-released.md @@ -0,0 +1,63 @@ +--- +title: Unit 1.31.1 Released +weight: 100 +--- + +We are delighted to announce Unit 1.31.1, a maintenance release that fixes +several bugs, enhances the WebAssembly technology preview, and updates tools +and packaging. + +## WebAssembly Technology Preview Enhancements + +After a very successful launch of our WebAssembly integration in +{doc}`1.31.0 ` +[1.31.0]({{< relref "/unit/news/2023/unit-1.31.0-released.md" >}}), +, +we have made some minor improvements.With the `unit-wasm` SDK, +you can now explicitly set the HTTP return code of a given request. +Also, requests with payloads larger than 4 GB are now handled properly. + +For more information, see the `unit-wasm` SDK +[documentation](https://github.com/nginx/unit-wasm). + +## Updates to the Unit CLI Tool `unitc` + +The +[unitc](https://github.com/nginx/unit/tree/master/tools) +command line tool is now able to convert Unit configuration +between JSON and YAML formats. +It also supports a new URI scheme `docker://` +to make it even easier to work with Unit running in a container. + +## Full Changelog + +```none +Changes with Unit 1.31.1 19 Oct 2023 + + *) Feature: allow to set the HTTP response status in Wasm module. + + *) Feature: allow uploads larger than 4GiB in Wasm module. + + *) Bugfix: application process could crash while rewriting URLs with + query strings. + + *) Bugfix: requests larger than about 64MiB could cause error in Wasm + module. + + *) Bugfix: when using many headers in Java module some of them could be + corrupted due to memory realocation issue. + + *) Bugfix: ServerRequest.destroy() implemented in Node.js module to make + it compatible with some frameworks that might use it. + + *) Bugfix: chunk argument of ServerResponse.write() can now be a + Uint8Array to improve compatibility with Node.js 15.0.0 and above. + + *) Bugfix: Node.JS unit-http NPM module now has appropriate default + paths for macOS/arm64 systems. + + *) Bugfix: build on musl libc with clang. +``` + +For a full list of changes and bugfixes, +please see the [changelog]({{< relref "/unit/CHANGES.md" >}}). diff --git a/content/unit/news/2024/_index.md b/content/unit/news/2024/_index.md new file mode 100644 index 000000000..8aefa487d --- /dev/null +++ b/content/unit/news/2024/_index.md @@ -0,0 +1,4 @@ +--- +title: 2024 +weight: 200 +--- diff --git a/content/unit/news/2024/fermyon-spin-rust-sdk.md b/content/unit/news/2024/fermyon-spin-rust-sdk.md new file mode 100644 index 000000000..5051b3cda --- /dev/null +++ b/content/unit/news/2024/fermyon-spin-rust-sdk.md @@ -0,0 +1,85 @@ +--- +title: "Wasm Components: Working with the Spin SDK for Rust" +weight: 500 +--- + +In our blog series [Part 1]({{< relref "/unit/news/2024/wasm-component-model-part-1.md" >}}) and [Part 2]({{< relref "/unit/news/2024/wasm-component-model-part-2.md" >}}) , we have covered the core mechanism of the WebAssembly Component Model and showcased how to create a Wasm Component using WASI 0.2 APIs and the **wasi/http:proxy** world. + +In this blog post, we will have a look at the [Fermyon's Spin](https://www.fermyon.com/spin) SDK for [Rust](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/index.html) and create a Wasm Component that can be hosted on NGINX Unit. + +The Spin SDKs provide a great developer experience, as they wrap a lot of the manual work in easy to consume APIs. In this blog post we will focus on Rust, but if you would like to learn more about the other language SDKs, please see the official [documentation](https://developer.fermyon.com/spin/v2/language-support-overview). + +Let's start by creating a new Rust library using **cargo new**. This will create a new library project in a sub-directory **test-spin-component** of our current work directory. + +```bash +$ cargo new --lib test-spin-component +$ cd test-spin-component +``` + +Add the latest version of the "spin-sdk" and "anyhow" (Flexible Error Types and a dependency of the Spin SDK) crates to the project by running the following command: + +```bash +$ cargo add spin-sdk anyhow +``` + +Before we implement the actual functionality, we must modify our **Cargo.toml** file. Open the **Cargo.toml** with an editor of your choice and append the following to the bottom of your existing **Cargo.toml** file. + +```toml +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "component:test-component" +proxy = true + +[package.metadata.component.dependencies] +``` + +Next, replace the content of **src/lib.rs** file with the following code: + +```rust +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_component; + +#[http_component] +fn handle_hello_world(_req: Request) -> anyhow::Result { + let body_string = String::from("Hello, this is a Wasm Component using Spin SDK"); + + Ok(Response::builder() + .status(200) + .header("Content-Type", "text/plain") + .header("Content-Lenght", body_string.len().to_string()) + .body(body_string) + .build()) +} +``` + +Compile the Rust Library into a Wasm Component using **cargo component**: + +```bash +$ cargo component build --release +``` + +To run the Wasm Component on NGINX Unit, start up Unit and use this initial configuration. + +{{< note >}} +Make sure you point to the Wasm component by using an absolute path. +{{< /note >}} + +```json +{ + "listeners": { + "127.0.0.1:8085": { + "pass": "applications/my-spin-component" + } + }, + "applications": { + "my-spin-component": { + "type": "wasm-wasi-component", + "component": "target/wasm32-wasi/release/test_spin_component.wasm" + } + } +} +``` + +As the Wasm Component we have just created uses the request and response interfaces defined by the **wasi:http/proxy**, it can easily be deployed on NGINX Unit. diff --git a/content/unit/news/2024/unit-1.32.0-released.md b/content/unit/news/2024/unit-1.32.0-released.md new file mode 100644 index 000000000..88e4fc752 --- /dev/null +++ b/content/unit/news/2024/unit-1.32.0-released.md @@ -0,0 +1,259 @@ +--- +title: Unit 1.32.0 Released +weight: 700 +--- + +Unit 1.32.0 comes with a new language module for WebAssembly that supports +the WASI 0.2 HTTP world so that WebAssembly Components implementing this +interface can be hosted on Unit. + +This new language module improves upon the existing WebAssembly support. We +recommend all users to use this new implementation for WebAssembly. We consider +the old **unit-wasm** module deprecated now. + +Additionally, we have added the following features: + +- Enhanced the {doc}`njs (NGINX JavaScript) experience <../../scripting>` by making all Unit variables + accessible from JavaScript +- Added support for + {ref}`conditional access logging `, to help + you filter the requests that are logged +- Added support for changing Unit's control socket permissions +- Added a [new variable]({{}}) +, **\$request_id** + +...and much more! Keep reading to learn more about what changed since 1.31.1. + +## WebAssembly: Support for Wasm components and the WASI 0.2 API + +Since the release of Unit 1.31.0 in August 2023 and the announcement of our +technology preview for WebAssembly, a lot has changed and happened in the +WebAssembly ecosystem. + +So we evolved and with 1.32.0, we are happy to announce the support for the +WebAssembly Component Model using the WASI 0.2 APIs. This opens the +possibilities to run Wasm Components compatible with the WASI 0.2 APIs on Unit +without having a need to rebuild them. This is also the first Language Module +for Unit that was driven by the Community. Special thanks to Alex Crichton +for the contribution! + +We are preparing a blog post where we will dive deeper into the details of the +new WebAssembly language module. Stay tuned! + +## Enhanced scripting support - Use Unit-variables in NGINX JavaScript + +Using JavaScript in Unit's configuration unlocks almost endless opportunities. +A simple Unit configuration can be used to decide where a request should be +routed or rewritten to by creating the values for **pass** and **rewrite** +dynamically inside a JavaScript function. + +Previously JavaScript modules had access to a +{doc}`limited set of objects and scalars <../../scripting>`. Now JavaScript has +access to all of {ref}`Unit's variables ` through +the vars object. + +In the following sample configuration, we set the **Cache-Control** header +based on the HTTP method. We do this by accessing the method variable as +**vars.method**. When the method starts with a "P" (POST, PUT, PATCH), +we do not want to cache the response. For all other methods we set a **max-age** +of 3600 seconds. + +```json +{ + "action": { + "pass": "applications/my_app", + "response_headers": { + "Cache-Control": "`${vars.method.startsWith('P') ? 'no-cache' : 'max-age=3600'}`" + } + } +} +``` + +## Conditional access logging {#conditional-access-logging-news} + +Access logs are a great way to monitor the traffic sent to Unit. +However, you might find that certain requests, such as regular +health checks and automated UI tests, aren't ones you want +cluttering up your logs. While these checks are crucial for monitoring +the health of your services or web applications, they can significantly +increase the volume of data in your access logs, leading to unnecessary noise. + +With conditional access logging, you can define rules to decide if a request +should be logged or not. + +```json +{ + "access_log": { + "if": "`${uri == '/health' ? false : true}`", + "path": "/var/log/unit/access.log", + "format": "`${host + ': ' + uri}`" + } +} +``` + +In this example we don't want to log any health checks sent to Unit. +As shown in our example, to get the maximum out of the newly added **if** +option, you can combine it with our JavaScript scripting feature, but this +is not a must. + +The **if** option also supports simple string validation to check if a value +is present in a request or not. + +```json +{ + "access_log": { + "if": "$cookie_session", + "path": "…" + } +} +``` + +In this example Unit will check the existence of a Cookie named session +and only log requests when it exists. + +## CLI enhancements + +The **unitc** command line tool is a convenient way of applying and editing Unit +configuration without constructing lengthy **curl(1)** commands or knowing where +the control socket is located. Unit 1.32.0 includes two useful enhancements to +**unitc** that are included in the official packages. + +A Docker container ID can now be specified as the configuration target. +To access the configuration of a local Unit container, use the **docker://** +scheme to specify the container ID or name. + +It is now also possible to convert Unit's configuration to and from YAML. +This can be convenient when a more compact format is desirable, such as when +storing it in a source control system. YAML format also provides an elegant way +of displaying Unit's usage statistics without the noise" of JSON. + +Let's combine these two enhancements to display a compact form of Unit's usage +statistics from a Docker container: + +```bash +$ unitc docker://f4f3d9e918e6 /status --format YAML +connections: + accepted: 1067 + active: 13 + idle: 4 + closed: 1050 +requests: + total: 1307 +applications: + my_app: + processes: + running: 14 + starting: 0 + idle: 4 + requests: + active: 10 +``` + +Note that the [yq(1)](https://github.com/mikefarah/yq#install) tool is required +for YAML format conversion. + +## Unit is now on GitHub! + +This release is special! Special for us and the Community! As you may have +noticed we have moved more and more of our development and planning workloads +from our old systems to [GitHub](https://github.com/nginx/unit/). + +GitHub is no longer just a read-only mirror. It now serves as the primary +source for our source code and tests. We invite you to create +[issues](https://github.com/nginx/unit/issues), contribute through +[pull requests](https://github.com/nginx/unit/pulls), or join our +[discussions](https://github.com/nginx/unit/discussions). There are many +ways to get involved with us. + +We've also fully transitioned the development and maintenance of unit.nginx.org +to the [Github unit-docs](https://github.com/nginx/unit-docs/) repository. +We look forward to pull requests and issues that will improve our documentation. + +## Changes in behavior and other updates + +### Docker image uses **stderr**, so now you can send **access_log** to stdout + +With 1.32.0 the **unit.log** file is symlinked to the container's +**/dev/stderr** instead of **/dev/stdout**. This leaves room for the +*access_log* to be redirected to **/dev/stdout** and will not populate +the Unit log messages to **stdout** which might be scraped by log collectors. + +### unit.nginx.org/download/ is now sources.nginx.org/unit/ + +We have moved the location of the Unit tarballs from "unit.nginx.org/download/" +to a new, central source archive for NGINX: +[sources.nginx.org/unit/](https://sources.nginx.org/unit/). + +The old link is currently proxying to the new location, but officially +deprecated now! Please update to the new location "sources.nginx.org/unit/". + +## Wall of fame + +Special Thanks to all external contributors helping us +making Unit better! With 1.32.0 we would like to send a shout out to: + +- Alejandro Colomar +- Alex Crichton +- Andrei Vasiliu +- Chris Adams +- David Carlier +- Dean Coakley +- rustedsword +- Hippolyte Pello +- Javier Evans + +## Full Changelog + +```none +Changes with Unit 1.32.0 27 Feb 2024 + + *) Feature: WebAssembly Components using WASI interfaces defined in + wasi:http/proxy@0.2.0. + + *) Feature: conditional access logging. + + *) Feature: NJS variables access. + + *) Feature: $request_id variable contains a string that is formed using + random data and can be used as a unique request identifier. + + *) Feature: options to set control socket permissions. + + *) Feature: Ruby arrays in response headers, improving compatibility + with Rack v3.0. + + *) Feature: Python bytearray response bodies for ASGI applications. + + *) Bugfix: router could crash while sending large files. Thanks to + rustedsword. + + *) Bugfix: serving static files from a network filesystem could lead to + error. + + *) Bugfix: "uidmap" and "gidmap" isolation options validation. + + *) Bugfix: abstract UNIX socket name could be corrupted during + configuration validation. Thanks to Alejandro Colomar. + + *) Bugfix: HTTP header field value encoding could be misinterpreted in + Python module. + + *) Bugfix: Node.js http.createServer() accepts and ignores the "options" + argument, improving compatibility with strapi applications, among + others. + + *) Bugfix: ServerRequest.flushHeaders() implemented in Node.js module to + make it compatible with Next.js. + + *) Bugfix: ServerRequest.httpVersion variable format in Node.js module. + + *) Bugfix: Node.js module handles standard library imports prefixed with + "node:", making it possible to run newer Nuxt applications, among + others. + + *) Bugfix: Node.js tarball location changed to avoid build/install + errors. + + *) Bugfix: Go module sets environment variables necessary for building + on macOS/arm64 systems. +``` diff --git a/content/unit/news/2024/unit-1.32.1-released.md b/content/unit/news/2024/unit-1.32.1-released.md new file mode 100644 index 000000000..69dcfc261 --- /dev/null +++ b/content/unit/news/2024/unit-1.32.1-released.md @@ -0,0 +1,39 @@ +--- +title: Unit 1.32.1 Released +weight: 300 +--- + +NGINX Unit 1.32.1 is a maintenance release that fixes bugs in the new WebAssembly Language Module and in our njs implementation. + +## Resolved issues + +This release fixes the following issues: + +### Applications of type `wasm-wasi-component` can't be restarted + +Applications deployed as `wasm-wasi-components` can't be restarted using the `restart` endpoint. + +After deploying a new Wasm Component binary to disk, the restart will trigger a reload of the component in Unit without restarting the server. + +As restarts will work independently of the application type, the behavior shipped with **1.32.0** was not right. It has been fixed in this release. + +### Unit-variables in NGINX JavaScript are constantly cached + +In **1.32.0** we added the possibility to access all Unit variables form inside njs. + +As reported in GitHub issue [#1169](https://github.com/nginx/unit/issues/1169) the variables were cached and would hold the wrong value, which is not how this feature should work. With version 1.32.1, we have fixed this issue. + +## Full Changelog + +```none +Changes with Unit 1.32.1 26 Mar 2024 + + *) Bugfix: NJS variables in templates may have incorrect values due to + improper caching. + + *) Bugfix: Wasm application process hangs after receiving restart signal + from the control. +``` + +For a full list of changes and bugfixes, +please see the [changelog]({{< relref "/unit/changes.md" >}}. diff --git a/content/unit/news/2024/unit-1.33.0-released.md b/content/unit/news/2024/unit-1.33.0-released.md new file mode 100644 index 000000000..db840f0ba --- /dev/null +++ b/content/unit/news/2024/unit-1.33.0-released.md @@ -0,0 +1,192 @@ +--- +title: Unit 1.33.0 Released +weight: 200 +--- + +We are pleased to announce the release of NGINX Unit 1.33.0. This release includes +a number of new features and changes: + +## New configuration options + +This release introduces three new configuration options: + +1. **listen_threads** + + This option can be set under **/settings/listen_threads** and controls the + number of threads the router process creates to handle client + connections. By default, Unit creates the same number of threads as there + are CPUs available. + +2. **backlog** + + This option can be set under **/listeners/backlog**. This is a per-listener + option that sets the the backlog parameter as passed to the **listen(2)** + system-call, which defines the maximum length for the queue of pending + connections for the socket. + + This is analogous to the **backlog** parameter of the **listen** directive in + NGINX. + +3. **factory** + + This option can be set under '/applications/\/factory' and is specific to + Python applications. This allows you to enable the `Application factories` + feature of Python. + + This option is a boolean value. If set to 'true', Unit treats 'callable' as + a factory. + + The default value is 'false'. + + NOTE: Unit does **not** support passing arguments to factories. + +## unitctl CLI tool + +[unitctl]({{}}) +is a Rust-based CLI tool that allows you to deploy, manage, and configure Unit +in your environment. + +{{< note >}} +This is a "Technical Preview" release of **unitctl**. We welcome feedback and +suggestions for this early access version. It is provided to test its features +and should not be used in production environments. +{{< /note >}} + +## Chunked request body support + +Unit can now accept chunked requests rather than returning **411 +Length Required**. This feature is experimental (not documented and subject to change), and can +be enabled setting the **/settings/chunked_transform** configuration option +to **true**. + +## Changes in behavior and other updates + +- On Linux, we now default to a **listen(2)** backlog of **-1**, which means we + use the OS's default: **4096** for Linux 5.4 and later; **128** for older versions. + + The previous default for Unit was 511. + +- Under systemd, Unit once again runs in **forking** mode. This allows the + per-application logging feature to work out the box. + +- The Python language module now supports **Application Factories** + (thanks to Gourav). + +## Changes for developers + +We have made some changes to the build system: + +### Prettified make output by default with GNU make + +Instead of getting the normal compiler command for each target being built +you now get a simplified line like: + +```console +CC build/src/nxt_cgroup.o +``` + +You can use the **V=1** option to get the old verbose output, for example: + +```console +make V=1 +``` + +### Make variables + +You can now control some aspects of the build process by passing variables to +**make** (like the above). The currently supported variables are: + +{{}} + +| Option | Description | Default | +|----------------|--------------------------------|---------| +| **D=1** | Enables debug builds (-O0) | 0 | +| **E=0** | Disables -Werror | 1 | +| **V=1** | Enables verbose output | 0 | +| **EXTRA_CFLAGS=** | Add extra compiler options | | + +{{}} + + +### GCC & Clang + +We removed support for several lesser-known compilers. GCC and Clang are now the +only supported compilers for building Unit. + +### -std=gnu11 + +We now build with **-std=gnu11** (C11 with GNU extensions). While previously we +didn't explicitly set the -std= option, as we were supporting CentOS 7 (which is now +EOL), we were effectively limited to **-std=gnu89/90**. + +## Wall of fame + +Special Thanks to all external contributors helping us +making Unit better! With 1.33.0 we would like to send a shout out to: + +- Alejandro Colomar +- Costas Drongos +- Gourav +- Remi Collet +- Robbie McKinstry + +Special thanks to Arjun for his fuzzing work. + +## Full Changelog + +```none +Changes with Unit 1.33.0 18 Sep 2024 + + *) Feature: show list of loaded language modules in the /status + endpoint. + + *) Feature: make the number of router threads configurable. + + *) Feature: make the listen(2) backlog configurable. + + *) Feature: add fuzzing via oss-fuzz. + + *) Feature: add Python application factory support. + + *) Feature: add chunked request body support. + + *) Feature: add "if" option to the "match" object. + + *) Feature: Unit ships with a new Rust based CLI application "unitctl". + + *) Feature: the wasm-wasi-component language module now inherits the + processes environment. + + *) Change: under systemd unit runs in forking mode (once again). + + *) Change: if building with njs, version 0.8.3 or later is now required. + + *) Change: Unit now builds with -std=gnu11 (C11 with GNU extensions). + + *) Change: Unit now creates the full directory path for the PID file and + control socket. + + *) Change: build system improvements, including pretty printing the make + output and enabling various make variables to influence the build + process (see: make help). + + *) Change: better detection of available runnable CPUs on Linux. + + *) Change: default listen(2) backlog on Linux now defaults to Kernel + default. + + *) Bugfix: fix a crash when interrupting a download via a proxy. + + *) Bugfix: don't create the $runstatedir directory which triggered an + Alpine packaging error. + + *) Bugfix: wasm-wasi-component application process hangs after receiving + restart signal from the control endpoint. + + *) Bugfix: njs variables accessed with a JS template literal should not + be cacheable. + + *) Bugfix: don't modify REQUEST_URI. + + *) Bugfix: properly handle deleting arrays of certificates. +``` diff --git a/content/unit/news/2024/unit-1.34.0-released.md b/content/unit/news/2024/unit-1.34.0-released.md new file mode 100644 index 000000000..3a3e89f0c --- /dev/null +++ b/content/unit/news/2024/unit-1.34.0-released.md @@ -0,0 +1,148 @@ +--- +title: Unit 1.34.0 Released +weight: 100 +--- + +We are pleased to announce the release of NGINX Unit 1.34.0. This release +includes a number of new features and changes: + +## JSON formatted access logs + +This release introduces the ability to specify a JSON format for access logs. + +When defining an access log you can specify 'format' as an object defining +JSON field name/value pairs, e.g. + +```json +{ + "access_log": { + "path": "/tmp/access.log", + "format": { + "remote_addr": "$remote_addr", + "time_local": "$time_local", + "request_line": "$request_line", + "status": "$status", + "body_bytes_sent": "$body_bytes_sent", + "header_referer": "$header_referer", + "header_user_agent": "$header_user_agent" + } + } +} +``` + +The JSON *values* support being strings, variables and JavaScript. + +## OpenTelemetry (OTEL) + +This release includes initial support for OpenTelemtry (OTEL) +\<> + +This support has been added via the OpenTelemetry Rust SDK and as such +requires cargo/rust to build. + +An example configuration looks like + +```json +{ + "listeners": { + "[::1]:8080": { + "pass": "routes" + } + }, + + "settings": { + "telemetry": { + "batch_size": 20, + "endpoint": "http://example.com/v1/traces", + "protocol": "http", + "sampling_ratio": 1 + } + }, + + "routes": [ + { + "match": { + "headers": { + "accept": "*text/html*" + } + }, + "action": { + "share": "/usr/share/unit/welcome/welcome.html" + } + }, { + "action": { + "share": "/usr/share/unit/welcome/welcome.md" + } + } + ] +} +``` + +- **endpoint** + + The endpoint for the OpenTelemetry (OTEL) Collector. This is required. + + The value must be a URL to either a gRPC or HTTP endpoint. + +- **protocol** + + Determines the protocol used to communicate with the endpoint. This is + required. + + Can be either "http" or "grpc". + +- **batch_size** + + Number of spans to cache before triggering a transaction with the + configured endpoint. This is optional. + + This allows the user to cache up to N spans before the OpenTelemetry + (OTEL) background thread sends spans over the network to the collector. + + Must be a positive integer. + +- **sampling_ratio** + + Percentage of requests to trace. This is optional. + + This allows the user to only trace anywhere from 0% to 100% of requests + that hit Unit. In high throughput environments this percentage should be + lower. This allows the user to save space in storing span data, and to + collect request metrics like time to decode headers and whatnot without + storing massive amounts of duplicate superfluous data. + + Must be a positive floating point number. + + This support is disabled by default but can be enabled by passing --otel + to ./configure. + +## Changes to language modules + +- The Perl language module no longer adds a 'new' constructor to parsed + scripts. It's not required and could interfere with scripts that were + trying to use 'new' themselves. + +## Changes for developers + +- -funsigned-char + + We now compile Unit with -funsigned-char, this ensures we are using the + same char type on all platforms (what you get by default varies by + platform). + + This is also a first step in getting rid of (mostly at least) our usage of + u_char and using char instead, which better aligns with libc interfaces and + so on. + +## Full Changelog + +```none +Changes with Unit 1.34.0 19 Dec 2024 + + *) Feature: initial OpenTelemetry (OTEL) support. (Disabled by default). + + *) Feature: support for JSON formatted access logs. + + *) Bugfix: tweak the Perl language module to avoid breaking scripts in + some circumstances. +``` diff --git a/content/unit/news/2024/wasm-component-model-part-1.md b/content/unit/news/2024/wasm-component-model-part-1.md new file mode 100644 index 000000000..fde637994 --- /dev/null +++ b/content/unit/news/2024/wasm-component-model-part-1.md @@ -0,0 +1,69 @@ +--- +title: The WebAssembly Component Model - Part 1 +weight: 500 +--- + +Are you interested in getting started with the WebAssembly Component Model but feel overwhelmed by the ecosystem and unsure where to begin? If so, this blog post is for you! + +In this post, we'll share some of the lessons we learned and the "aha" moments we experienced while adding support for the WebAssembly Component Model to NGINX Unit, thanks to the assistance of the robust and active community. + +If you're already familiar with the Wasm ecosystem or just would like to get started with code, feel free to jump directly to [Part 2](/news/2024/wasm-component-model-part-2) of this blog series. + +## The WebAssembly Component Model and NGINX Unit + +A lot has happened since we shipped the first version of our Wasm Language Module for Unit. +Back in September 2023 we said: + +> We introduce WebAssembly support as a Technology Preview - we expect to replace it with WASI-HTTP support as soon as that is possible. + +We have done just that with Unit 1.32.0. This release supports Wasm Components using the WASI 0.2 APIs and the wasi:http/proxy world as its main interface. + +Let's pause here for a moment. If the previous sentence contained unfamiliar vocabulary, don't worry. This blog post will explain the concept of the Component Model for WebAssembly (Wasm) and the role that the WebAssembly System Interface (WASI) plays in this context. We will also discuss the significance of the "WebAssembly Interface Types." + +As you can read in our first [Wasm blog post](https://www.nginx.com/blog/server-side-webassembly-nginx-unit/), the Wasm runtime shares data with the Wasm Module as raw bytes over shared memory. To make sense of this byte stream, the Host as well as the Wasm Module must speak about the same things or technically speaking, implementing the same interfaces. It is a core concept of NGINX Unit to create an application-specific context of an incoming HTTP request and share this set of bytes in memory with the runtime. + +This is exactly what we did with **unit-wasm** and it was an interesting and necessary learning to add Wasm support to Unit. However, this is far away from implementing or using a standard. This is where the Wasm Component Model comes into play. + +The WebAssembly (Wasm) Component Model outlines how different Wasm modules, or components, can communicate with each other and the runtime environment. It establishes specific contracts that must be met to ensure that code compiled into a Wasm component can be hosted on a compatible runtime and seamlessly exchange data with other Wasm components during runtime. + +To provide a practical illustration of this theoretical framework, we can examine the implementation found in NGINX Unit, which serves as a textbook example of the Wasm Component Model in action. + +The two essential parts of the Wasm Component Model are the WebAssembly System Interface (WASI) and WebAssembly Interface Types (WIT). +Let's have a closer look on the two standards. + +## WASI and WIT + +WASI is short for "WebAssembly System Interface" and was introduced by the Wasmtime project and was designed from the ground up for Wasm. WASI is a portable system interface for Wasm that provides access to several operating system-like features, including files and the file system, sockets, clocks, random numbers, and more. But why is that necessary? + +As we are creating Wasm components for server-side runtimes, we cannot target browser-based Wasm runtimes anymore where Web APIs or JavaScript is used to provide this functionality. Code that is outside the browser needs a way to talk to the underlaying system. To better understand where WASI comes into play, we develop a very simple program, written in Rust that will print out "Hello World". + +The code we write can be compiled into an executable binary file. After launching it, we will see "Hello World" printed on the command line. The magic behind this is a standard called POSIX, which defines system calls. System calls work differently on different operating systems. + +WASI provides an abstraction layer for those syscalls, that can be targeted from the code that will be compiled to Wasm. +A WASI compatible runtime will be able to handle the execution of that code. We see this in action in our [Rust tutorial](/news/2024/wasm-component-model-part-2) further in part 2 of this blog series. Since Preview2 of the WASI proposal - [the WASI-APIs are defined in WIT-files](https://bytecodealliance.org/articles/webassembly-the-updated-roadmap-for-developers#webassembly-system-interface-wasi). + +WIT (Wasm Interface Types) is a descriptive [interface description language (IDL)]() used to define interfaces. It isn't a general-purpose coding language. The written WIT files don't contain any business logic; they are pure definitions of contracts. Multiple interfaces can be further combined into worlds. While it is not required to deeply understand the way you can create your own WIT-files, it will help to track down issues or trouble-shoot them while building components. To learn more about the WIT programming language, see the official [documentation.](https://component-model.bytecodealliance.org/design/wit.html#structure-of-a-wit-file) + +The WIT files used by the Wasm Component Model and the **wasi:http/proxy** world in specific, are created and maintained by the Bytecode Alliance. At the time of writing this blog post, the best way to make use of them are the Wasmtime project's GitHub repository and a manual pull. + +One of the interesting facts about WIT-files is the versioning system. As the host implementing the Wasm runtime as well as the component we are about to build are creating bindings for the contracts defined by the WIT files, it is important to target the same version of those contracts or choose a runtime that supports multiple versions of those WIT files. But this is worth another blog post. For now, we should focus on the latest stable release, which it was published in February 2024 and is labeled as WASI 0.2. +This release included **wasi:cli** and the **wasi:http** worlds. + +In the WebAssembly ecosystem, these contracts are referred to as "worlds." Therefore, we will use that term from now on. For the use cases of NGINX Unit, it was pretty clear we will be targeting the **wasi:http/proxy** world. You can think of the **wasi:http/proxy** world as the set of interfaces describing how a HTTP request and response will look like, including all its data (HTTP Method, Headers, Body, and more). If you are an old-school web developer, this might remind you of CGI. + +## NGINX Unit, Wasmtime and Rust - A runtime implementation + +After a brief investigation, we discovered that the WASI/WIT pair we'd already heard of plays a vital role in supporting the Component Model. As the host, Unit must implement the WASI HTTP proxy interfaces defined by WIT files to fulfill the contract. This isn't new information; we were already aware of this fact. Since we're using Wasmtime as the Wasm runtime, we could delegate this task to the runtime, right? Indeed, we could! However, there was a small but significant detail: our implementation at the time was written entirely in C using the Wasmtime C-APIs. Unfortunately, these APIs lack the necessary functions to support the Component Model. + +As mentioned at the beginning of this article, any challenge, regardless of its complexity, can be solved when the right people and community share the same mindset. +Fermyon has been, and continues to be, a valuable and significant partner for us. After a late-night Slack and Zoom Session, we found it too complex to add native support for the Component Model to the Wasmtime C-API. Additionally, implementing the interfaces manually with WIT files without help from automation tools like **bindgen** would result in a significant amount of ongoing upkeep work. + +While explaining how the internals of NGINX Unit and the current C-based Language Module work to Fermyon, they shared a prototype of a Rust-based Unit Language Module targeting the Rust API of Wasmtime. Not the C-API anymore. + +Now we are equipped with the necessary knowledge to write some code. + +## What's next? + +In the next part we will covering the process of creating a Wasm Component using Rust and the WASI 0.2 APIs. + +[Part 2]({{< relref "/unit/news/2024/wasm-component-model-part-2.md" >}}) diff --git a/content/unit/news/2024/wasm-component-model-part-2.md b/content/unit/news/2024/wasm-component-model-part-2.md new file mode 100644 index 000000000..dda11ab81 --- /dev/null +++ b/content/unit/news/2024/wasm-component-model-part-2.md @@ -0,0 +1,233 @@ +--- +title: The WebAssembly Component Model - Part 2 +weight: 600 +--- + +This is Part 2 of our blog series about the Wasm Component Model, it's ecosytem and how to use Wasm Components with NGINX Unit. +In [Part 1](/news/2024/wasm-component-model-part-1) we have covered all the conceptional parts. In this part, we will focus on the process of creating a Wasm Component. + +## Tutorial - A Rust based Wasm Component + +Rust is the premier language for WebAssembly development and the most mature in terms of support. In the example, we will use Rust and its ecosystem to create a Wasm Component that can be hosted directly on NGINX Unit. + +This tutorial targets Linux-based operating systems and MacOS. If you are on Windows, we recommend using WSL2 (Windows Subsystem for Linux) to follow along. If you haven't already installed NGINX Unit alongside the WebAssembly language module, please refer to the [docs](https://docs.nginx.com/nginx-unit/installation/#official-packages) on how to do it or use the official [Docker Image](https://docs.nginx.com/nginx-unit/installation/#docker-images) **unit:wasm**. + +### Rust Development Setup + +Let's start by installing the Rust ecosystem, if not already done. At the time of writing, Rust 1.76 is the latest stable version. +To install Rust, see the instructions on their [website](https://www.rust-lang.org/tools/install). + +After the installation completes, you can confirm the current version of Rust by running: + +```bash +$ rustc -V +rustc 1.76.0 (07dca489a 2024-02-04) +``` + +To work with Wasm Components, we need some additional tooling. This is a one-time setup for you to be able to write Rust source code and compile it to a Wasm Component. + +### Add the wasm32-wasi compiler target + +The wasm32-wasi compiler target will provide general Wasm support to your rustc installation. Add the target by running: + +```bash +$ rustup target add wasm32-wasi +``` + +### Install cargo-component + +**cargo-component** will add a cargo subcommand to build Wasm Components without any intermediate steps from our Rust project. +To install the latest version, run the following command: + +```bash +$ cargo install cargo-component +``` + +### Install wasmtime runtime and CLI for testing + +The wasmtime-cli will be used to test and play around with the Wasm component. At the time of writing, we are using Wasmtime 18. +To install the latest version of Wasmtime, run: + +```bash +$ curl https://wasmtime.dev/install.sh -sSf | bash +``` + +For more information about Wasmtime and installing it, see their [GitHub repository](https://github.com/bytecodealliance/wasmtime/) + +Once we have all the tools in place, we can create the Rust projects. + + + +### Using the **wasi** Rust library + +Our experience with the official WASI Rust library was very interesting and exciting. The component build time was fascinating, and the library has a low dependency footprint. However, there are some costs in terms of developer experience. See for yourselves: + +Start by creating a new Wasm Component using **cargo component**: + +```bash +$ cargo component new --lib test-wasi-component +``` + +Navigate into the **test-wasi-component** directory. + +Add the **wasi** crate: + +```bash +$ cargo add wasi +``` + +Next, modify the **Cargo.toml** file with the text editor of your choice. Add the **proxy = true** configuration to the **[package.metadata.component]** section. After saving the changes, your **Cargo.toml** file should look like this: + +```toml +[package] +name = "test-wasi-component" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "2.4.2" +wit-bindgen-rt = "0.21.0" +wasi = "0.13.0" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "component:test-wasi-component" +proxy = true + +[package.metadata.component.dependencies] +``` + +The actual code from **src/lib.rs** should look like this: + +```rust +use wasi::http::types::{ + Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam, +}; + +wasi::http::proxy::export!(Component); + +struct Component; + +impl wasi::exports::http::incoming_handler::Guest for Component { + fn handle(_request: IncomingRequest, response_out: ResponseOutparam) { + + let hdrs = Fields::new(); + let mesg = String::from("Hello, This is a Wasm Component using wasi/http:proxy!"); + let _try = hdrs.set(&"Content-Type".to_string(), &[b"plain/text".to_vec()]); + let _try = hdrs.set(&"Content-Length".to_string(), &[mesg.len().to_string().as_bytes().to_vec()]); + + let resp = OutgoingResponse::new(hdrs); + + // Add the HTTP Response Status Code + resp.set_status_code(200).unwrap(); + + let body = resp.body().unwrap(); + ResponseOutparam::set(response_out, Ok(resp)); + + let out = body.write().unwrap(); + out.blocking_write_and_flush(mesg.as_bytes()).unwrap(); + drop(out); + + OutgoingBody::finish(body, None).unwrap(); + } +} +``` + +Targeting the wasi crate requires some low-level Rust work by us. Not bad at all, but something to consider when choosing this option. For the **wasi:http/proxy** world there is an interface description available on [GitHub](https://github.com/WebAssembly/wasi-http/blob/main/proxy.md) which will help to write your code. + +Let's build the component. Run the following command from the **test-wasi-component** directory: + +```bash +$ cargo component build --release +``` + +The build shows a very small dependency footprint, so is a major benefit from the wasi crate. + +To test the Component, we can use wasmtime serve. + +```bash +$ wasmtime serve target/wasm32-wasi/release/test_wasi_component.wasm +``` + +The output should look like the following: + +```bash +$ wasmtime serve target/wasm32-wasi/release/test_wasi_component.wasm + Serving HTTP on http://0.0.0.0:8080/ +``` + +Sending a request to the exposed endpoint will output something like this: + +```bash +$ curl -v localhost:8080 +… +> GET / HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/8.4.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< content-type: plain/text +< content-length: 54 +< date: Tue, 12 Mar 2024 12:28:56 GMT +< +* Connection #0 to host localhost left intact +Hello, This is a Wasm Component using wasi/http:proxy! +``` + +## NGINX Unit for production grade Wasm workloads + +While the **wasmtime-cli** interface is good for testing Wasm components locally, there are more requirements for production workloads. + +With NGINX Units Wasm runtime, you will be able to run your Wasm workloads next to other host applications on a single host and make use of all the other powerful Unit features. Given Units design and as we have decoupled the listeners from the application runtime, you can make full use of the Unit Router to make routing decisions before sharing a request with your Wasm Component or add HTTPS to your stack. + +To run the component on NGINX Unit, start Unit, and send the initial configuration, make sure you point to the Wasm component by using an absolute path. + +Create a **config.json** file: + +```json +{ + "listeners": { + "127.0.0.1:8085": { + "pass": "applications/my-wasm-component" + } + }, + "applications": { + "my-wasm-component": { + "type": "wasm-wasi-component", + "component": "path/target/wasm32-wasi/release/test_wasi_component.wasm" + } + } +} +``` + +Apply the configuration using **unitc**: + +```bash +$ unitc config.json /config +``` + +Sending a request to the exposed endpoint will create the same output from a different runtime implementation: + +```bash +$ curl -v localhost:8085 +… +< HTTP/1.1 200 OK +< content-type: plain/text +< content-length: 54 +< Server: Unit/1.32.0 +< Date: Tue, 12 Mar 2024 15:16:13 GMT +< +* Connection #0 to host localhost left intact +Hello, This is a Wasm Component using wasi/http:proxy! +``` + +This is the full power of Wasm Components. Build once - run on every runtime. + +## What's next? + +The Wasm ecosystem and all its associated projects are undergoing rapid and positive changes. Every week brings new features and opportunities for innovation. NGINX Unit remains dedicated to Wasm and will continue implementing new features in our Wasmtime integration and publishing technical blog posts about Wasm. + +Feel free to share your feedback about this blog post on our [GitHub discussions](https://github.com/nginx/unit/discussions) page. We'd love to know what you think is missing regarding the work with Wasm Components. diff --git a/content/unit/news/2025/_index.md b/content/unit/news/2025/_index.md new file mode 100644 index 000000000..b44aade6e --- /dev/null +++ b/content/unit/news/2025/_index.md @@ -0,0 +1,4 @@ +--- +title: 2025 +weight: 100 +--- \ No newline at end of file diff --git a/content/unit/news/2025/unit-1.34.1-released.md b/content/unit/news/2025/unit-1.34.1-released.md new file mode 100644 index 000000000..5772af0c4 --- /dev/null +++ b/content/unit/news/2025/unit-1.34.1-released.md @@ -0,0 +1,24 @@ +--- +title: Unit 1.34.1 Released +weight: 100 +--- + +We are pleased to announce the release of NGINX Unit 1.34.1. This is a +maintenance release that fixes stability issues caused by the +OpenTelemetry (OTEL) support that was added in the previous release. + +- It addresses instability issues resulting in Unit segmentation + faulting when OTEL was not actually configured. +- It addresses issues of not being able to build Unit with OTEL support + on various platforms, including macOS. + +## Full Changelog + +```none +Changes with Unit 1.34.1 10 Jan 2025 + + *) Bugfix: fix instability issues due to OpenTelemetry (OTEL) support. + + *) Bugfix: fix issues with building OpenTelemetry (OTEL) support on + various platforms, including macOS. +``` diff --git a/content/unit/news/_index.md b/content/unit/news/_index.md new file mode 100644 index 000000000..073b6ad27 --- /dev/null +++ b/content/unit/news/_index.md @@ -0,0 +1,4 @@ +--- +title: Releases and announcements +weight: 20000 +--- \ No newline at end of file diff --git a/content/unit/news/archived-news/2018.md b/content/unit/news/archived-news/2018.md new file mode 100644 index 000000000..947b16e63 --- /dev/null +++ b/content/unit/news/archived-news/2018.md @@ -0,0 +1,633 @@ +--- +title: 2018 +weight: 500 +toc: true +--- + +## Unit 1.7 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This is a bugfix release with a primary focus on the stabilization of +the Node.js module. We have made great progress with it, and now Node.js +support is in much better shape than before. + +```none +Changes with Unit 1.7 20 Dec 2018 + + *) Change: now rpath is set in Ruby module only if the library was not + found in default search paths; this allows to meet packaging + restrictions on some systems. + + *) Bugfix: "disable_functions" and "disable_classes" PHP options set via + Control API did not work. + + *) Bugfix: Promises on request data in Node.js were not triggered. + + *) Bugfix: various compatibility issues with Node.js applications. + + *) Bugfix: a segmentation fault occurred in Node.js module if + application tried to read request body after request.end() was + called. + + *) Bugfix: a segmentation fault occurred in Node.js module if + application attempted to send header twice. + + *) Bugfix: names of response header fields in Node.js module were + erroneously treated as case-sensitive. + + *) Bugfix: uncatched exceptions in Node.js were not logged. + + *) Bugfix: global install of Node.js module from sources was broken on + some systems; the bug had appeared in 1.6. + + *) Bugfix: traceback for exceptions during initialization of Python + applications might not be logged. + + *) Bugfix: PHP module build failed if PHP interpreter was built with + thread safety enabled. +``` + +Highly likely, this is the last release of Unit in 2018, so I would like to +wish you a Happy New Year on the behalf of the entire Unit team. + +2018 was an exciting year in Unit development. Many important features have +been introduced, including: + +- Advanced Process Management, which allows scaling application processes + dynamically depending on the amount of load. Thanks go to Maxim Romanov + who primarily worked on this feature. + + Documentation: https://docs.nginx.com/nginx-unit/configuration/#process-management + +- Perl, Ruby, and Node.js application support. Thanks to Alexander Borisov + who implemented these language modules. + +- TLS support and Certificates Storage API that allows to dynamically + configure TLS certificates. Thanks to Igor Sysoev who collaborated with + me on this feature. + + Documentation: https://docs.nginx.com/nginx-unit/certificates/ + +- C API language modules were moved into a separate library; this helped a lot + with Node.js integration and aids the upcoming Java support. Thanks again + to Maxim Romanov for this work. + +- Essential access logging support. + Documentation: https://docs.nginx.com/nginx-unit/configuration/#access-log + +- Advanced settings for applications including environment variables, runtime + arguments, PHP options, and php.ini path customization. + +I can’t imagine releasing any of these features without the effort of our QA +engineer, Andrey Zelenkov, who relentlessly improves test coverage of Unit +codebase, runs various fuzzing tests, and reports any suspicious behaviour +to the developers. + +In addition, one of the most important achievements of the year was a tangible +improvement of documentation quality. The unit.nginx.org website is up-to-date +now and covers all the features introduced in the new and previous Unit +releases. This duty was successfully carried out by our technical writer, +Artem Konev. + +Besides, he continues refactoring the documentation and plans to introduce +HowTos for various use cases and applications. If you have any particular +suggestions concerning applications you’d like to configure with Unit, +please create a feature request in our documentation issue tracker on GitHub: +https://github.com/nginx/unit-docs/issues + +Thanks to our system engineers, Andrei Belov and Konstantin Pavlov, who are +toiling over packages in our own repositories and images in Docker hub. + +Thanks to our product manager Nick Shadrin who helps us to envision our +strategy and gives excellent talks on conferences around the world. +You can see him in the latest Unit demo session at NGINX Conf 2018: +https://www.youtube.com/watch?v=JQZKbIG3uro + +Of course, everything I’ve just mentioned wouldn’t be possible without our +vibrant community; our users who are eager to move their projects to Unit; +everyone who reports bugs and suggests features, guiding us to the right path. +We urge everybody to participate via our mailing list at unit/at/nginx.org or +on GitHub: https://github.com/nginx/unit + +I gladly mention 洪志道 (Hong Zhi Dao) as one of the most active community +members who not only reports bugs but also reads our code, asks pointed +questions, and regularly sends patches with improvements. Thank you very much +for your contribution. + +Special thanks go to the maintainers of Unit packages in various community +repositories: Sergey A. Osokin (FreeBSD), Ralph Seichter (Gentoo), André +Klitzing (Alpine Linux), and Julian Brost (Arch Linux). Sorry if I didn't +mention anyone else who maintains Unit packages for other distributions; you +can open an issue for your repository to be included in the Installation +section at unit.nginx.org: https://github.com/nginx/unit-docs/issues + +Unfortunately, we weren’t able to achieve each and every of our audacious +goals this year. The development of some features is postponed until +the upcoming year. + +Currently, there is ongoing work on WebSocket support, the Java module, +request routing, and static files serving. + +We have already made good progress on the Java module. This work is underway +in a separate GitHub public `repository `__, so +everybody willing to run their Java applications on Unit can participate. + +Many other good things and announcements about Unit will surely happen in 2019. +Thank you for staying with us, and all the best. + +--- + +## Unit 1.6 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. + +This release primarily focuses on improvements in Node.js module compatibility; thanks to our vibrant community, we made great progress here. + +Please don't hesitate to report any problems to: + +- Github: https://github.com/nginx/unit/issues +- Mailing list: https://mailman.nginx.org/mailman/listinfo/unit + +If you have installed the `unit-http` module from [npm](https://www.npmjs.com), then don't forget to update it besides Unit itself. + +Detailed instructions for Node.js installation can be found here: +http://unit.nginx.org/installation/#node-js + +```none +Changes with Unit 1.6 15 Nov 2018 + + *) Change: "make install" now installs Node.js module as well if it was + configured. + + *) Feature: "--local" ./configure option to install Node.js module + locally. + + *) Bugfix: Node.js module might have crashed due to broken reference + counting. + + *) Bugfix: asynchronous operations in Node.js might not have worked. + + *) Bugfix: various compatibility issues with Node.js applications. + + *) Bugfix: "freed pointer is out of pool" alerts might have appeared in + log. + + *) Bugfix: module discovery didn't work on 64-bit big-endian systems + like IBM/S390x. +``` + +--- + +## Unit 1.5 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. + +This release introduces preliminary Node.js support. + +Currently it lacks WebSockets, and there's a known problem with "promises". However, our admirable users have already started testing it even before the release: https://medium.com/house-organ/what-an-absolute-unit-a36851e72554 + +Now it even easier, since Node.js package is published in [npm](https://www.npmjs.com): https://www.npmjs.com/package/unit-http + +So feel free to try it and give us feedback on: + + - Github: https://github.com/nginx/unit/issues + - Mailing list: https://mailman.nginx.org/mailman/listinfo/unit + +We will continue improving Node.js support in future releases. + +Among other features we are working on right now: WebSockets, Java module, flexible request routing, and serving of static media assets. + +```none +Changes with Unit 1.5 25 Oct 2018 + + *) Change: the "type" of application object for Go was changed to + "external". + + *) Feature: initial version of Node.js package with basic HTTP + request-response support. + + *) Feature: compatibility with LibreSSL. + + *) Feature: --libdir and --incdir ./configure options to install libunit + headers and static library. + + *) Bugfix: connection might be closed prematurely while sending + response; the bug had appeared in 1.3. + + *) Bugfix: application processes might have stopped handling requests, + producing "last message send failed: Resource temporarily + unavailable" alerts in log; the bug had appeared in 1.4. + + *) Bugfix: Go applications didn't work when Unit was built with musl C + library. +``` + +--- + +## Unit 1.4 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. + +The key feature of the new version is dynamically configurable TLS support +with certificate storage API that provides detailed information about your +certificate chains, including common and alternative names as well as +expiration dates. + +See the documentation for details: +https://docs.nginx.com/nginx-unit/certificates/ + +This is just our first step in TLS support. More configuration options +and various TLS-related features will be added in the future. + +Full-featured HTTP/2 support is also in our sights. + +```none + Changes with Unit 1.4 20 Sep 2018 + + *) Change: the control API maps the configuration object only at + "/config/". + + *) Feature: TLS support for client connections. + + *) Feature: TLS certificates storage control API. + + *) Feature: Unit library (libunit) to streamline language module + integration. + + *) Feature: "408 Request Timeout" responses while closing HTTP + keep-alive connections. + + *) Feature: improvements in OpenBSD support. Thanks to David Carlier. + + *) Bugfix: a segmentation fault might have occurred after + reconfiguration. + + *) Bugfix: building on systems with non-default locale might be broken. + + *) Bugfix: "header_read_timeout" might not work properly. + + *) Bugfix: header fields values with non-ASCII bytes might be handled + incorrectly in Python 3 module. +``` + +In a few weeks, we are going to add preliminary Node.js support. It's almost +ready; our QA engineers are already testing it. + +Now we are also working on Java module, WebSockets support, flexible request +routing, and serving of static media assets. + +Please also welcome Artem Konev, who joined our team as a technical writer. He +has already started improving documentation on the website and updated it with +the configuration options currently available: +https://github.com/nginx/unit-docs/ + +Of course, the website still leaves much to be desired, so Artem will strive to +provide industry-grade documentation for Unit. You are welcome to join this +effort with your ideas, suggestions, and edits: just send a pull request or +open an issue in our documentation repository on GitHub: +https://github.com/nginx/unit-docs/ + +Stay tuned! + +--- + +## Unit 1.3 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. + +```none + Changes with Unit 1.3 13 Jul 2018 + + *) Change: UTF-8 characters are now allowed in request header field + values. + + *) Feature: configuration of the request body size limit. + + *) Feature: configuration of various HTTP connection timeouts. + + *) Feature: Ruby module now automatically uses Bundler where possible. + + *) Feature: http.Flusher interface in Go module. + + *) Bugfix: various issues in HTTP connection errors handling. + + *) Bugfix: requests with body data might be handled incorrectly in PHP + module. + + *) Bugfix: individual PHP configuration options specified via control + API were reset to previous values after the first request in + application process. +``` + +Here's an example configuration with new parameters: + +```json + { + "settings": { + "http": { + "header_read_timeout": 30, + "body_read_timeout": 30, + "send_timeout": 30, + "idle_timeout": 180, + "max_body_size": 8388608 + } + }, + + "listeners": { + "127.0.0.1:8034": { + "application": "mercurial" + } + }, + + "applications": { + "mercurial": { + "type": "python 2", + "module": "hgweb", + "path": "/data/hg" + } + } + } +``` + +All timeouts values are specified in seconds. The `max_body_size` value is specified in bytes. + +Please note that the parameters of the `http` object in this example are set to their default values. So, there's no need to set them explicitly if you are happy with the values above. + +Binary Linux packages and Docker images are available here: + +- Packages: https://docs.nginx.com/nginx-unit/installation/#official-packages +- Docker: https://hub.docker.com/r/nginx/unit/tags/ + +Also, please follow our blog posts to learn more about new features in the recent versions of Unit: https://www.nginx.com/blog/tag/nginx-unit/ + +--- + +## Unit 1.2 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. + +```none + Changes with Unit 1.2 07 Jun 2018 + + *) Feature: configuration of environment variables for application + processes. + + *) Feature: customization of php.ini path. + + *) Feature: setting of individual PHP configuration options. + + *) Feature: configuration of execution arguments for Go applications. + + *) Bugfix: keep-alive connections might hang after reconfiguration. +``` + +Here's an example of new configuration parameters of application objects: + +```json + { + "args-example": { + "type": "go", + "executable": "/path/to/compiled/go/binary", + "arguments": ["arg1", "arg2", "arg3"] + }, + + "opts-example": { + "type": "php", + "root": "/www/site", + "script": "phpinfo.php", + + "options": { + "file": "/path/to/php.ini", + "admin": { + "memory_limit": "256M", + "variables_order": "EGPCS", + "short_open_tag": "1" + }, + "user": { + "display_errors": "0" + } + } + }, + + "env-example": { + "type": "python", + "path": "/www/django", + "module": "wsgi", + + "environment": { + "DB_ENGINE": "django.db.backends.postgresql_psycopg2", + "DB_NAME": "mydb", + "DB_HOST": "127.0.0.1" + } + } + } +``` + +Please note that `environment` can be configured for any type of application. + +Binary Linux packages and Docker images are available here: + +- Packages: https://docs.nginx.com/nginx-unit/installation/#official-packages +- Docker: https://hub.docker.com/r/nginx/unit/tags/ + +--- + +## Unit 1.1 Released + +Hello, + +I'm glad to announce a new release of NGINX Unit. This is mostly a bugfix release with stability and compatibility improvements. + +```none + Changes with Unit 1.1 26 Apr 2018 + + *) Bugfix: Python applications that use the write() callable did not + work. + + *) Bugfix: virtual environments created with Python 3.3 or above might + not have worked. + + *) Bugfix: the request.Read() function in Go applications did not + produce EOF when the whole body was read. + + *) Bugfix: a segmentation fault might have occurred while access log + reopening. + + *) Bugfix: in parsing of IPv6 control socket addresses. + + *) Bugfix: loading of application modules was broken on OpenBSD. + + *) Bugfix: a segmentation fault might have occurred when there were two + modules with the same type and version; the bug had appeared in 1.0. + + *) Bugfix: alerts "freed pointer points to non-freeble page" might have + appeared in log on 32-bit platforms. +``` + +A half of these issues were reported on GitHub by our users. Thank you all for helping us make Unit better. + +If you have encountered a problem with Unit or have any ideas for improvements, please feel free to share here: + +- Mailing list: https://mailman.nginx.org/mailman3/lists/unit.nginx.org/ +- GitHub: https://github.com/nginx/unit/issues + +--- + +## Unit 1.0 Released + +Hello, + +I'm happy to congratulate you on the [International Day of Human Space Flight](https://www.un.org/en/observances/human-spaceflight-day) and glad to announce the release of NGINX Unit 1.0. + +```none + Changes with Unit 1.0 12 Apr 2018 + + *) Change: configuration object moved into "/config/" path. + + *) Feature: basic access logging. + + *) Bugfix: 503 error occurred if Go application did not write response + header or body. + + *) Bugfix: Ruby applications that use encoding conversions might not + work. + + *) Bugfix: various stability issues. +``` + +With this release Unit ends its beta period. If you wish to know more about +the project and our plans, please read the announcement blog post: +https://www.nginx.com/blog/nginx-unit-1-0-released/ + +--- + +## Unit Beta 0.7 Released + +Hello, + +I'm glad to announce a new beta of NGINX Unit with a number of bugfixes and Ruby/Rack support. Now you can easily run applications like Redmine with Unit. + +The full list of supported languages today is PHP, Python, Go, Perl, and Ruby. More languages are coming. + +```none +Changes with Unit 0.7 22 Mar 2018 + + *) Feature: Ruby application module. + + *) Bugfix: in discovering modules. + + *) Bugfix: various race conditions on reconfiguration and during + shutting down. + + *) Bugfix: tabs and trailing spaces were not allowed in header fields + values. + + *) Bugfix: a segmentation fault occurred in Python module if + start_response() was called outside of WSGI callable. + + *) Bugfix: a segmentation fault might occur in PHP module if there was + an error while initialization. +``` + +Binary Linux packages and Docker images are available here: + +- Packages: https://docs.nginx.com/nginx-unit/installation/#precompiled-packages +- Docker: https://hub.docker.com/r/nginx/unit/tags/ + +Packages and images for the new Ruby module will be built next week. + +--- + +## Unit Beta 0.6 Released + +Hello, + +I'm glad to announce a new beta of NGINX Unit with advanced process management and Perl/PSGI support. One of the Perl applications that has been tested is Bugzilla, and it runs with Unit flawlessly. + +Here is a changes log of 0.5 and 0.6 versions: + +```none +Changes with Unit 0.5 08 Feb 2018 + + *) Change: the "workers" application option was removed, the "processes" + application option should be used instead. + + *) Feature: the "processes" application option with prefork and dynamic + process management support. + + *) Feature: Perl application module. + + *) Bugfix: in reading client request body; the bug had appeared in 0.3. + + *) Bugfix: some Python applications might not work due to missing + "wsgi.errors" environ variable. + + *) Bugfix: HTTP chunked responses might be encoded incorrectly on 32-bit + platforms. + + *) Bugfix: infinite looping in HTTP parser. + + *) Bugfix: segmentation fault in router. + +Changes with Unit 0.6 09 Feb 2018 + + *) Bugfix: the main process died when the "type" application option + contained version; the bug had appeared in 0.5. +``` + +The announcement of 0.5 was skipped as a serious regression was found right after the packages were built and published. + +Besides the precompiled packages for CentOS, RHEL, Debian, Ubuntu, and Amazon Linux, now you can try Unit using official Docker containers. See the links below for details: + +- Packages: https://docs.nginx.com/nginx-unit/installation/#precompiled-packages +- Docker: https://hub.docker.com/r/nginx/unit/tags/ + +--- + +## Unit Beta 0.4 Released + +Hello, + +I'm glad to announce a new beta of NGINX Unit. This is mostly a bugfix release in order to eliminate significant regressions in the previous version. + +```none +Changes with Unit 0.4 15 Jan 2018 + + *) Feature: compatibility with DragonFly BSD. + + *) Feature: "configure php --lib-static" option. + + *) Bugfix: HTTP request body was not passed to application; the bug had + appeared in 0.3. + + *) Bugfix: HTTP large header buffers allocation and deallocation fixed; + the bug had appeared in 0.3. + + *) Bugfix: some PHP applications might not work with relative "root" + path. +``` + +You can find links to the source code and precompiled Linux packages here: +https://docs.nginx.com/nginx-unit/installation/ + +Internally, we use Buildbot to build each commit and run tests on a large number of systems. We also use various static analysis tools to improve code quality and check test coverage. + +There is ongoing work on a functional tests framework that will allow us to avoid such regressions in the future. And there are plans to add fuzz testing. + +You can learn more about recent Unit changes in this detailed blog post: +https://www.nginx.com/blog/unit-0-3-beta-release-available-now/ + +Besides that, please welcome Alexander Borisov, who's joined our Unit dev team today. His first task is going to be adding Perl/PSGI support. diff --git a/content/unit/news/archived-news/2019.md b/content/unit/news/archived-news/2019.md new file mode 100644 index 000000000..731f529c9 --- /dev/null +++ b/content/unit/news/archived-news/2019.md @@ -0,0 +1,592 @@ +--- +title: "2019" +weight: 400 +toc: true +--- + +## Unit 1.14.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +Besides improving the request routing abilities, this release simplifies +operations concerning the Go module. Now it can also be installed with the +`go get` command: + +```console +$ go get unit.nginx.org/go +``` + +Mind, however, that it requires the `unit-dev/unit-devel` [package](https://docs.nginx.com/nginx-unit/installation/#official-packages). + +Great effort went into improving the efficiency and avoiding memory bloat in +cases where an application generates gigabytes of response body. Now Unit can +deal with that without much hassle. We will continue improving the performance +and increasing efficiency, as this is one of our primary priorities. + +```none +Changes with Unit 1.14.0 26 Dec 2019 + + *) Change: the Go package import name changed to "unit.nginx.org/go". + + *) Change: Go package now links to libunit instead of including library + sources. + + *) Feature: ability to change user and group for isolated applications + when Unit daemon runs as an unprivileged user. + + *) Feature: request routing by source and destination addresses and + ports. + + *) Bugfix: memory bloat on large responses. +``` + +We also updated our Docker images and switched them from Debian 9 to 10 as the +base, so the language module versions have been updated respectively: +[https://docs.nginx.com/nginx-unit/installation/#docker-images](https://docs.nginx.com/nginx-unit/installation/#docker-images) + +Python 3.6 module packages were added to CentOS and RHEL 7 repositories, +and Python 3.7 package was added to Amazon Linux 2 LTS. Please note that +the name of Python 2.7 package in these repositories was changed from +`unit-python` to `unit-python27`. + +The Go package now has the same name `unit-go` across all our +repositories and depends on `unit-dev`. + +This is the last release of 2019, so I'll use this opportunity to wish +a Happy New Year to our strong community. Thank you for your requests, +bug reports, ideas, and suggestions. Everything that we do, we primarily +do for you, our users. + +This year, we made 8 releases, with 427 commits to the repository, where 65242 +lines were added and 8219 removed. The biggest features of the year are: + +- Support for Java Servlet Containers, which means that now Unit supports + 7 languages + +- Advanced internal request routing that allows to filter requests by various + parameters, including: URI, header fields, arguments, cookies, addresses, + and ports + +- Built-in WebSocket server offloading for Node.js and Java + +- Isolation of application processes + +- Serving of static files + +- Reverse proxying + +These features establish a firm basis for further development of Unit as a +general-purpose web server that is able to perform absolutely any task related +to handling and processing web protocols in the most efficient way. This is +our ultimate goal, and we are eager to achieve it over the coming years. + +I'd like to thank everyone who worked hard with me on Unit through the year: + +- Andrei Belov - system engineer, who maintained repositories and prepared + packages + +- Andrei Zeliankou - QA engineer, who wrote functional tests and ran fuzzing + +- Artem Konev - technical writer, who wrote documentation and blog posts, + improved the website, and sometimes helped us to arrange + words in sentences the right way + +- Axel Duch - junior developer, who improved request routing + +- Igor Sysoev - senior developer and architect, who worked on request routing, + proxying, and many internal aspects + +- Konstantin Pavlov - system engineer, who prepared Docker images and packages + +- Maxim Romanov - senior developer, who worked on Java, WebSockets, + and internal IPC + +- Tiago Natel de Moura - senior developer, who worked on isolation features + +Thank you guys, I'm happy to work with you. + +--- + +## Unit 1.13.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This release expands Unit's functionality as a generic web server by +introducing basic HTTP reverse proxying. + +See the details in our documentation: +[https://docs.nginx.com/nginx-unit/configuration/#proxying](https://docs.nginx.com/nginx-unit/configuration/#proxying) + +Compared to mature proxy servers and load balancers, Unit's proxy features +are limited now, but we'll continue the advance. + +Also, this release improves the user experience for Python and Ruby modules and +remediates compatibility issues with existing applications in these languages. + +Our long-term goal is to turn Unit into the ultimate high-performance building +block that will be helpful and easy to use with web services of any kind. To +accomplish this, Unit's future releases will focus on the following aspects: + +- security, isolation, and DoS protection +- ability to run various types of dynamic applications +- connectivity with load balancing and fault tolerance +- efficient serving of static media assets +- statistics and monitoring + +```none +Changes with Unit 1.13.0 14 Nov 2019 + + *) Feature: basic support for HTTP reverse proxying. + + *) Feature: compatibility with Python 3.8. + + *) Bugfix: memory leak in Python application processes when the close + handler was used. + + *) Bugfix: threads in Python applications might not work correctly. + + *) Bugfix: Ruby on Rails applications might not work on Ruby 2.6. + + *) Bugfix: backtraces for uncaught exceptions in Python 3 might be + logged with significant delays. + + *) Bugfix: explicit setting a namespaces isolation option to false might + have enabled it. +``` + +Please feel free to share your experiences and ideas on GitHub: +[https://github.com/nginx/unit/issues](https://github.com/nginx/unit/issues) + +Or via Unit mailing list: [https://mailman.nginx.org/mailman/listinfo/unit](https://mailman.nginx.org/mailman/listinfo/unit) + +--- + +## Unit 1.12.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This is an ad-hoc release that focuses on fixing several annoying bugs and adds +compatibility with the upcoming PHP 7.4, scheduled for release on November 28, +2019. + +```none +Changes with Unit 1.12.0 03 Oct 2019 + + *) Feature: compatibility with PHP 7.4. + + *) Bugfix: descriptors leak on process creation; the bug had appeared in + 1.11.0. + + *) Bugfix: TLS connection might be closed prematurely while sending + response. + + *) Bugfix: segmentation fault might have occurred if an irregular file + was requested. +``` + +Regarding our plans for the near future, see our earlier [announcement](#unit-1.11.0-released). + +To know more about some features introduced recently, you can follow posts +about Unit in the official blog: [https://www.nginx.com/blog/tag/nginx-unit/](https://www.nginx.com/blog/tag/nginx-unit/) + +We also updated our Docker images with an initialization script that +significantly simplifies the initial configuration of Unit daemon inside a +container. Please check the documentation for instructions: +[https://docs.nginx.com/nginx-unit/installation/#initial-configuration](https://docs.nginx.com/nginx-unit/installation/#initial-configuration) + +--- + +## Unit 1.11.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This release improves the stability of Go applications and introduces +three major features: + +### 1. Ability to Serve Static Media Assets + +With this feature, we're only at the beginning of a long road to transform +Unit into a full-fledged web server, capable of acting as a building block +for web services of any kind. + +In this release, the support for static files is very simple; you can only +specify the document root directory for Unit to handle: + +```json +{ + "share": "/data/www/example.com" +} +``` + +Also, you can fine-tune MIME types: + +```json +{ + "mime_types": { + "text/plain": [ + "readme", + ".c", + ".h" + ], + + "application/msword": ".doc" + } +} +``` + +Use encoding to access object members with names that contain "/" characters +directly by their URI: `GET +/config/settings/http/static/mime_types/text%2Fplain/` + +See the documentation for details: +[https://docs.nginx.com/nginx-unit/configuration/#static-files](https://docs.nginx.com/nginx-unit/configuration/#static-files) + +In the upcoming releases, we'll extend this area of functionality to handle +more use cases in the most performant manner. + +Unfortunately, basic proxying support did not make it to this release, as +tests have revealed that it needs more work. There are excellent chances +that the feature will be included in the next release in a month or so. + +### 2. Application Isolation + +This capability increases the security of running applications, allowing to +run them in isolated environments based on Linux namespaces. This is very +similar to how Docker containers work. + +The configuration is pretty straightforward: you can customize the isolation +level and configure UID/GID mapping between the host and the container: + +```json +{ + "namespaces": { + "credential": true, + "pid": true, + "network": true, + "mount": false, + "uname": true, + "cgroup": false + }, + + "uidmap": [ + { + "container": 1000, + "host": 812, + "size": 1 + } + ], + + "gidmap": [ + { + "container": 1000, + "host": 812, + "size": 1 + } + ] +} +``` + +See the documentation for details: +[https://docs.nginx.com/nginx-unit/configuration/#process-isolation](https://docs.nginx.com/nginx-unit/configuration/#process-isolation) + +This feature was implemented by Tiago de Bem Natel de Moura, who has joined +our team recently; he will continue working on security features hardening +and container support of Unit. + +### 3. WebSockets in Java Servlet Containers + +WebSocket connection offloading was first introduced in the previous release +for Node.js only; now it's extended to JSC as well. We will continue advancing +application language support further to provide equally broad opportunities, +whichever language you may prefer. + +```none +Changes with Unit 1.11.0 19 Sep 2019 + + *) Feature: basic support for serving static files. + + *) Feature: isolation of application processes with Linux namespaces. + + *) Feature: built-in WebSocket server implementation for Java Servlet + Containers. + + *) Feature: direct addressing of API configuration options containing + slashes "/" using URI encoding (%2F). + + *) Bugfix: segmentation fault might have occurred in Go applications + under high load. + + *) Bugfix: WebSocket support was broken if Unit was built with some + linkers other than GNU ld (e.g. gold or LLD). +``` + +That's all for this release. Try, test, leave feedback, and stay tuned! + +--- + +## Unit 1.10.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This release includes a number of improvements in various language modules and, finally, basic handling of incoming WebSocket connections, currently only for Node.js. Next in line to obtain WebSocket support is the Java module; it's almost ready but requires some polishing. + +To handle WebSocket connections in your Node.js app via Unit, use the server object from the `unit-http` module instead of the default one: + +```javascript +var webSocketServer = require('unit-http/websocket').server; +``` + +Another interesting and long-awaited feature in this release is the splitting of `PATH_INFO` in the PHP module. Now, Unit can properly handle requests like `/app.php/some/path?some=args`, which are often used to implement "user-friendly" URLs in PHP applications. + +```none +Changes with Unit 1.10.0 22 Aug 2019 + + *) Change: matching of cookies in routes made case sensitive. + + *) Change: decreased log level of common errors when clients close + connections. + + *) Change: removed the Perl module's "--include=" ./configure option. + + *) Feature: built-in WebSocket server implementation for Node.js module. + + *) Feature: splitting PATH_INFO from request URI in PHP module. + + *) Feature: request routing by scheme (HTTP or HTTPS). + + *) Feature: support for multipart requests body in Java module. + + *) Feature: improved API compatibility with Node.js 11.10 or later. + + *) Bugfix: reconfiguration failed if "listeners" or "applications" + objects were missing. + + *) Bugfix: applying a large configuration might have failed. +``` + +Please welcome our new junior developer, Axel Duch. For this release, he implemented scheme matching in request routing; now, he works to further extend the request routing capabilities with source and destination address matching. + +In parallel, Tiago Natel de Moura, who also joined the development recently, has achieved significant progress in the effort to add various process isolation features to Unit. You can follow his recent work on Linux namespaces support in the following pull request: https://github.com/nginx/unit/pull/289 + +See also his email about the feature: https://mailman.nginx.org/pipermail/nginx/2019-August/058321.html + +In the meantime, we are about to finish the first round of adding basic support for serving static media assets and proxying in Unit. + +--- + +## Unit 1.9.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +In this release, we continue improving routing capabilities for more advanced +and precise request matching. Besides that, the control API was extended with +POST operations to simplify array manipulation in configuration. + +Please check the documentation about new features: + +- Matching rules: https://docs.nginx.com/nginx-unit/configuration/#matching-conditions +- API operations: https://docs.nginx.com/nginx-unit/controlapi/ + +If you prefer to perceive information visually, here's a recording of NGINX +Meetup that gives a good overview of dynamic application routing, although +doesn't discuss new features from this release: +https://www.youtube.com/watch?v=5O4TjbbxTxw + +Also, a number of annoying bugs were fixed; thanks to your feedback, +the Node.js module now works fine with more applications. + +```none +Changes with Unit 1.9.0 30 May 2019 + + *) Feature: request routing by arguments, headers, and cookies. + + *) Feature: route matching patterns allow a wildcard in the middle. + + *) Feature: POST operation for appending elements to arrays in + configuration. + + *) Feature: support for changing credentials using CAP_SETUID and + CAP_SETGID capabilities on Linux without running main process as + privileged user. + + *) Bugfix: memory leak in the router process might have happened when a + client prematurely closed the connection. + + *) Bugfix: applying a large configuration might have failed. + + *) Bugfix: PUT and DELETE operations on array elements in configuration + did not work. + + *) Bugfix: request schema in applications did not reflect TLS + connections. + + *) Bugfix: restored compatibility with Node.js applications that use + ServerResponse._implicitHeader() function; the bug had appeared in + 1.7. + + *) Bugfix: various compatibility issues with Node.js applications. +``` + +With this release, packages for Ubuntu 19.04 "disco" are also available. See +the website for a full list of available repositories: +https://docs.nginx.com/nginx-unit/installation/ + +Meanwhile, we continue working on WebSocket support. It's almost ready and +has great chances to be included in the next release for Node.js and Java +modules. + +Work on proxying and static files serving is also in progress; this will +take a bit more time. + +--- + +## Unit 1.8.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. This release contains two +big features that we have been working on diligently during the last months. + +Some of you wonder why listener sockets are separated from applications in +Unit configuration API. That was done intentionally to introduce advanced +routing between sockets and applications in the future, and this future is +finally happening. + +Now you will be able to specify quite handy rules that will direct your +requests to a particular application depending on various parameters. + +Please take a glance at the routing documentation: +https://docs.nginx.com/nginx-unit/configuration/#routes + +Currently, it only supports internal routing by Host, URI, and method request +parameters. In the following releases, available options are going to be +expanded to allow matching arbitrary headers, arguments, cookies, source +and destination addresses. We will also add regular expression patterns. + +In future releases, these routing abilities will be handy for issuing redirects +and changing configuration on a per route basis. + +As usual with Unit, all routing changes are fully dynamic and gracefully done +through its control API. + +The second feature is even bigger. We've merged the code that Maxim Romanov +developed in a separate branch last year to support running applications +leveraging certain technology described in the Java(tm) Servlet 3.1 (JSR-340) +specification. This module is a **BETA** release as the module is untested and +presumed incompatible with the JSR-340 specification. + +Now everybody can easily install it from our packages, try it with their Java +applications, and leave us feedback. If you're a Jira user, please use this +HowTo: https://docs.nginx.com/nginx-unit/howto/jira/ + +More documentation is available in +[Installation](https://docs.nginx.com/nginx-unit/installation) and +[Configuration](https://docs.nginx.com/nginx-unit/configuration/) sections. + +We intend to use our open-development process to refine and improve the +software and to eventually test and certify the software's compatibility +with the JSR-340 specification. Unless and until the software has been +tested and certified, you should not deploy the software in support of +deploying or providing Java Servlet 3.1 applications. You should instead +deploy production applications on pre-built binaries that have been tested +and certified to meet the JSR-340 compatibility requirements such as +certified binaries published for the JSR-340 reference implementation +available at https://javaee.github.io/glassfish/. + + +{{< note>}} +Java is a registered trademark of Oracle and/or its affiliates. +{{< /note>}} + +```none +Changes with Unit 1.8.0 01 Mar 2019 + + *) Change: now three numbers are always used for versioning: major, + minor, and patch versions. + + *) Change: now QUERY_STRING is always defined even if the request does + not include the query component. + + *) Feature: basic internal request routing by Host, URI, and method. + + *) Feature: experimental support for Java Servlet Containers. + + *) Bugfix: segmentation fault might have occurred in the router process. + + *) Bugfix: various potential memory leaks. + + *) Bugfix: TLS connections might have stalled. + + *) Bugfix: some Perl applications might have failed to send the response + body. + + *) Bugfix: some compilers with specific flags might have produced + non-functioning builds; the bug had appeared in 1.5. + + *) Bugfix: Node.js package had wrong version number when installed from + sources. +``` + +Our versioning scheme is actually always supposed to have the third version +number, but the ".0" patch version was hidden. In order to avoid any possible +confusion, it was decided to always show ".0" in version numbers. + +For those who are interested in running Unit on CentOS, Fedora, and RHEL +with latest versions of PHP, the corresponding packages are now available +in Remi's RPM repository: +https://docs.nginx.com/nginx-unit/installation/#community-remisrpm + +Many kudos to Remi Collet for collaboration. + +Note also that our technical writer Artem Konev has recently added more HowTos +to the site about configuring various applications, including WordPress, Flask, +and Django-based ones: https://docs.nginx.com/nginx-unit/howto/ + +He will continue discovering and writing instructions for other applications. +If you're interested in some specific use cases and applications, please don't +hesitate to leave a feature request on the documentation GitHub: +https://github.com/nginx/unit-docs/issues + +In the following releases, we will continue improving routing capabilities +and support for Java applications. Among other big features we're working +on are WebSockets support and serving static media assets. + +Stay tuned, give feedback, and help us to create the best software ever. + +--- + +## Unit 1.7.1 Released + +Hi, + +This is a bugfix release of NGINX Unit that eliminates a security flaw. +All versions of Unit from 0.3 to 1.7 are affected. + +Everybody is strongly advised to update to a new version. + +```none +Changes with Unit 1.7.1 07 Feb 2019 + + *) Security: a heap memory buffer overflow might have been caused in the + router process by a specially crafted request, potentially resulting + in a segmentation fault or other unspecified behavior + (CVE-2019-7401). + + *) Bugfix: install of Go module failed without prior building of Unit + daemon; the bug had appeared in 1.7. +``` + +Release of Unit 1.8 with support for internal request routing and an +experimental Java module is planned for end of February. diff --git a/content/unit/news/archived-news/2020.md b/content/unit/news/archived-news/2020.md new file mode 100644 index 000000000..fc64c94a4 --- /dev/null +++ b/content/unit/news/archived-news/2020.md @@ -0,0 +1,717 @@ +--- +title: 2020 +weight: 300 +toc: true +---- + +## Unit 1.21.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +Our two previous releases were thoroughly packed with new features and +capabilities, but Unit 1.21.0 isn't an exception either. This is our +third big release in a row, with only six weeks since the previous one! + +Perhaps, the most notable feature of this release is the support for +multithreaded request handling in application processes. Now, you can +fine-tune the number of threads used for request handling in each +application process; this improves scaling and optimizes memory usage. + +As a result, your apps can use a combination of multiple processes and +multiple threads per each process for truly dynamic scaling; the feature +is available for any Java, Python, Perl, or Ruby apps out of the box +without any need to update their code. + +Moreover, if you make use of ASGI support in Unit (introduced in the +previous release), each thread of each process of +your application can run asynchronously. + +To configure the number of threads per process, use the `threads` option +of the application object: [https://docs.nginx.com/nginx-unit/configuration/#applications](https://docs.nginx.com/nginx-unit/configuration/#applications) + +Yet another cool feature is the long-awaited support for regular expressions. +In Unit, they enable granular request filtering and routing via our compound +matching rules; now, with PCRE syntax available, your request matching +capabilities are limited only by your imagination. For details and examples, +see our documentation: [https://docs.nginx.com/nginx-unit/configuration/#routes](https://docs.nginx.com/nginx-unit/configuration/#routes) + +### Full Changelog + +```none +Changes with Unit 1.21.0 19 Nov 2020 + + *) Change: procfs is mounted by default for all languages when "rootfs" + isolation is used. + + *) Change: any characters valid according to RFC 7230 are now allowed in + HTTP header field names. + + *) Change: HTTP header fields with underscores ("_") are now discarded + from requests by default. + + *) Feature: optional multithreaded request processing for Java, Python, + Perl, and Ruby apps. + + *) Feature: regular expressions in route matching patterns. + + *) Feature: compatibility with Python 3.9. + + *) Feature: the Python module now supports ASGI 2.0 legacy applications. + + *) Feature: the "protocol" option in Python applications aids choice + between ASGI and WSGI. + + *) Feature: the fastcgi_finish_request() PHP function that finalizes + request processing and continues code execution without holding onto + the client connection. + + *) Feature: the "discard_unsafe_fields" HTTP option that enables + discarding request header fields with irregular (but still valid) + characters in the field name. + + *) Feature: the "procfs" and "tmpfs" automount isolation options to + disable automatic mounting of eponymous filesystems. + + *) Bugfix: the router process could crash when running Go applications + under high load; the bug had appeared in 1.19.0. + + *) Bugfix: some language dependencies could remain mounted after using + "rootfs" isolation. + + *) Bugfix: various compatibility issues in Java applications. + + *) Bugfix: the Java module built with the musl C library couldn't run + applications that use "rootfs" isolation. +``` + +Also, packages for Ubuntu 20.10 "Groovy" are available in our repositories: +[https://docs.nginx.com/nginx-unit/installation/#ubuntu-2010](https://docs.nginx.com/nginx-unit/installation/#ubuntu-2010) + +Thanks to Sergey Osokin, the FreeBSD port of Unit now provides an almost +exhaustive set of language modules: [https://www.freshports.org/www/unit/](https://www.freshports.org/www/unit/) + +We encourage you to follow our roadmap on GitHub, where your ideas and requests +are always more than welcome: [https://github.com/orgs/nginx/projects/1](https://github.com/orgs/nginx/projects/1) + +--- + +## Unit 1.20.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +It is yet another big release, featuring ASGI support for Python and a long list +of other improvements and bug fixes. + +[ASGI 3.0](https://asgi.readthedocs.io/en/latest/) is a modern standardized +interface that enables writing natively asynchronous web applications making +use of the async/await feature available in latest versions of Python. Now, +Unit fully supports it along with WSGI. Even more, Unit automatically detects +the interface your Python app is using (ASGI or WSGI); the configuration +experience remains the same, though. Also, our take on ASGI relies on Unit's +native high-performance capabilities to implement WebSockets. + +To learn more about the new feature, check out the documentation: +[https://docs.nginx.com/nginx-unit/configuration/#python](https://docs.nginx.com/nginx-unit/configuration/#python) + +In addition, we've prepared for you a couple of howtos on configuring popular +ASGI-based frameworks with Unit: + +- [Quart](https://docs.nginx.com/nginx-unit/howto/quart/) (note a simple WebSocket app) +- [Starlette](https://docs.nginx.com/nginx-unit/howto/starlette/) + +Finally, we've updated the Django howto to include the [ASGI alternative](https://docs.nginx.com/nginx-unit/howto/django/#asgi-interface). + +### Full Changelog + +```none +Changes with Unit 1.20.0 08 Oct 2020 + + *) Change: the PHP module is now initialized before chrooting; this + enables loading all extensions from the host system. + + *) Change: AVIF and APNG image formats added to the default MIME type + list. + + *) Change: functional tests migrated to the pytest framework. + + *) Feature: the Python module now fully supports applications that use + the ASGI 3.0 server interface. + + *) Feature: the Python module now has a built-in WebSocket server + implementation for applications, compatible with the HTTP & WebSocket + ASGI Message Format 2.1 specification. + + *) Feature: automatic mounting of an isolated "/tmp" file system into + chrooted application environments. + + *) Feature: the $host variable contains a normalized "Host" request + value. + + *) Feature: the "callable" option sets Python application callable + names. + + *) Feature: compatibility with PHP 8 RC 1. Thanks to Remi Collet. + + *) Feature: the "automount" option in the "isolation" object allows to + turn off the automatic mounting of language module dependencies. + + *) Bugfix: "pass"-ing requests to upstreams from a route was broken; the + bug had appeared in 1.19.0. Thanks to 洪志道 (Hong Zhi Dao) for + discovering and fixing it. + + *) Bugfix: the router process could crash during reconfiguration. + + *) Bugfix: a memory leak occurring in the router process; the bug had + appeared in 1.18.0. + + *) Bugfix: the "!" (non-empty) pattern was matched incorrectly; the bug + had appeared in 1.19.0. + + *) Bugfix: fixed building on platforms without sendfile() support, + notably NetBSD; the bug had appeared in 1.16.0. +``` + +I would very much like to highlight one of these changes. Perhaps the least +noticeable, it is still important for the entire project: our functional tests +moved to a more feature-rich pytest framework from the native Python unittest +module that we've used previously. This change should enable us to write more +sophisticated tests, boosting the overall quality of our future releases. + +All in all, this is a genuinely solid release, but I'm still more excited +about the things yet to come. Yes, even more great features are coming our +way very shortly! Right now, we are tinkering with route matching patterns +to support regular expressions; working on keepalive connection caching; +adding multithreading to application modules; and finally, fabricating the +metrics API! + +We encourage you to follow our roadmap on GitHub, where your ideas and requests +are always more than welcome: [https://github.com/orgs/nginx/projects/1](https://github.com/orgs/nginx/projects/1) + +--- + +## Unit 1.19.0 Released + +Hi, + +I'm always happy to announce a new release of NGINX Unit, but this one's BIG. +Besides the varied features and bugfixes, some breakthrough improvements were +made under the hood. + +As you may know, Unit uses an advanced architecture that relies on dedicated +processes to serve different roles in request processing. The process that +handles client connections is the router. It uses asynchronous threads (one +per CPU core) to accept new connections and send or receive data over already +established connections in a non-blocking manner. For security and scalability, +all applications run as separate processes over which you have a degree of +control: [https://docs.nginx.com/nginx-unit/configuration/#process-management](https://docs.nginx.com/nginx-unit/configuration/#process-management) + +To talk to application processes, relay requests for actual processing, +and obtain their responses, the router process uses an elaborate mechanism +of inter-process communication (IPC) based on shared memory segments. +The general idea is to avoid copying data between processes and minimize +overhead, potentially achieving almost zero-latency application interaction. + +Our first implementation of this protocol used a complex algorithm to +distribute requests between processes, heavily utilizing Unix socket pairs +to pass synchronization control messages. In practice, this turned out +rather sub-optimal due to lots of extra syscalls and overt complexity. +Also, the push semantics became a serious limitation that prevented us +from efficiently handling asynchronous applications. + +Thus, we stepped back a bit at the end of the last year to meticulously +reconsider our approach to IPC, and now this tremendous work finally sees +the light of day with the release of Unit version 1.19.0. Maintaining the +progress achieved while working with shared memory segments, the protocol now +is enhanced to bring the number of syscalls almost to zero under heavy load. +We have also changed the request distribution semantics. Now, instead of +pushing requests to application processes using a complex router process +algorithm, we make application processes pull requests out of a shared +queue anytime they're ready. This enables implementing async interfaces +in applications in the most effective manner. + +Relying on this new approach to IPC, we shall be able to improve the +performance of Go and Node.js modules in the upcoming releases, also +introducing multithreading and new interfaces, such as ASGI in Python. + +We are obsessed over performance and will continue optimizing Unit to +make it the best and brightest in every aspect. + +As for the other features of the release, there's an improvement in proxying: +now it speaks HTTP/1.1 and accepts chunked responses from backends. + +Moreover, request matching rules were also upgraded to enable more complex +wildcard patterns like `*/some/*/path/*.php*`. + +Finally, we have introduced our first configuration variables. They are +a small bunch at the moment, but that's to change. In a while, variables +shall be sufficiently diversified and will be available in more and more +options. + +### Full Changelog + +```none +Changes with Unit 1.19.0 13 Aug 2020 + + *) Feature: reworked IPC between the router process and the applications + to lower latencies, increase performance, and improve scalability. + + *) Feature: support for an arbitrary number of wildcards in route + matching patterns. + + *) Feature: chunked transfer encoding in proxy responses. + + *) Feature: basic variables support in the "pass" option. + + *) Feature: compatibility with PHP 8 Beta 1. Thanks to Remi Collet. + + *) Bugfix: the router process could crash while passing requests to an + application under high load. + + *) Bugfix: a number of language modules failed to build on some systems; + the bug had appeared in 1.18.0. + + *) Bugfix: time in error log messages from PHP applications could lag. + + *) Bugfix: reconfiguration requests could hang if an application had + failed to start; the bug had appeared in 1.18.0. + + *) Bugfix: memory leak during reconfiguration. + + *) Bugfix: the daemon didn't start without language modules; the bug had + appeared in 1.18.0. + + *) Bugfix: the router process could crash at exit. + + *) Bugfix: Node.js applications could crash at exit. + + *) Bugfix: the Ruby module could be linked against a wrong library + version. +``` + +Also, official packages for Fedora 32 are available now: +[https://docs.nginx.com/nginx-unit/installation/#fedora](https://docs.nginx.com/nginx-unit/installation/#fedora) + +And if you'd like to know more about the features introduced recently in +the previous release, see the blog posts: + +- [NGINX Unit 1.18.0 Adds Filesystem Isolation and Other Enhancements](https://www.nginx.com/blog/nginx-unit-1-18-0-now-available/) +- [Filesystem Isolation in NGINX Unit](https://www.nginx.com/blog/filesystem-isolation-nginx-unit/) + +--- + +:orphan: + +## Unit 1.18.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This release includes a few internal routing improvements that simplify some +configurations and a new isolation option for chrooting application processes +called `rootfs`. + +```none +Changes with Unit 1.18.0 28 May 2020 + + *) Feature: the "rootfs" isolation option for changing root filesystem + for an application. + + *) Feature: multiple "targets" in PHP applications. + + *) Feature: support for percent encoding in the "uri" and "arguments" + matching options and in the "pass" option. +``` + +Also, our official packages for the recently released Ubuntu 20.04 (Focal Fossa) +are available now: [https://docs.nginx.com/nginx-unit/installation/#ubuntu](https://docs.nginx.com/nginx-unit/installation/#ubuntu) + +At least two of the features in this release deserve special attention. + +### Changing The Root Filesystem + +Security is our top priority, so let's look closer at the `rootfs` +option first. + +The coolest thing about it is that it's not just a simple `chroot()` +system call as some may expect. It's not a secret that `chroot()` is +not intended for security purposes, and there's plenty of ways for an attacker +to get out of the chrooted directory (just check "man 2 chroot"). That's why +on modern systems Unit can use `pivot_root()` with the `mount` +namespace isolation enabled, which is way more secure and pretty similar to +putting your application in an individual container. + +Also, our goal is to make any security option as easy to use as possible. +In this case, Unit automatically tries to mount all the necessary +language-specific dependencies inside a new root, so you won't need +to care about them. Currently, this capability works for selected languages +only, but the support will be extended in the next releases. + +For more information and examples of `rootfs` usage, check the +documentation: [https://docs.nginx.com/nginx-unit/configuration/#process-isolation](https://docs.nginx.com/nginx-unit/configuration/#process-isolation) + +Now to the second feature... + +### Multiple PHP Application "Targets" + +The other major update in this release is called `targets`, aiming to +simplify configuration for many PHP applications. Perhaps, it is best +illustrated by an example: WordPress. This is one of many applications that +use two different addressing schemes: + +1. Most user requests are handled by `index.php` regardless of the actual + request URI. + +2. Administration interface and some components rely on direct requests + to specific `.php` scripts named in the URI. + +Earlier, users had to configure two Unit applications to handle this disparity: + +```json +{ + "wp_index": { + "type": "php", + "user": "wp_user", + "group": "wp_user", + "root": "/path/to/wordpress/", + "script": "index.php" + }, + + "wp_direct": { + "type": "php", + "user": "wp_user", + "group": "wp_user", + "root": "/path/to/wordpress/" + } +} +``` + +The first app directly executes the `.php` scripts named by the URI, +whereas the second one passes all requests to `index.php`. + +Now, you can use `targets` instead: + +```json +{ + "wp": { + "type": "php", + "user": "wp_user", + "group": "wp_user", + + "targets": { + "index": { + "root": "/path/to/wordpress/", + "script": "index.php" + }, + + "direct": { + "root": "/path/to/wordpress/" + } + } + } +} +``` + +The complete example is available in our WordPress howto: +[https://docs.nginx.com/nginx-unit/howto/wordpress/](https://docs.nginx.com/nginx-unit/howto/wordpress/) + +You can configure as many `targets` in one PHP application as you want, +routing requests between them using various sophisticated request matching +rules. + +Check our website to know more about the new option: +[https://docs.nginx.com/nginx-unit/configuration#targets](https://docs.nginx.com/nginx-unit/configuration/#targets) + +To learn more about request matching rules: +[https://docs.nginx.com/nginx-unit/configuration/#matching-conditions](https://docs.nginx.com/nginx-unit/configuration/#matching-conditions) + +Finally, see here for more howtos: +[https://docs.nginx.com/nginx-unit/howto/](https://docs.nginx.com/nginx-unit/howto/) + +We have plenty of them, covering many popular web applications and frameworks, +but if your favorite one is still missing, let us know by opening a ticket here: +[https://github.com/nginx/unit-docs/issues](https://github.com/nginx/unit-docs/issues) + +To keep the finger on the pulse, refer to our further plans in the roadmap here: +[https://github.com/orgs/nginx/projects/1](https://github.com/orgs/nginx/projects/1) + +--- + +:orphan: + +## Unit 1.17.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +In addition to improved stability, this release introduces two handy features. + +The first one is configured using the `return` and `location` +options of the action object. It can be used to immediately generate a simple +HTTP response with an arbitrary status - for example, to deny access to some +resources: + +```json +{ + "match": { + "uri": "*/.git/*" + }, + + "action": { + "return": 403 + } +} +``` + +Or, you can redirect a client to another resource: + +```json +{ + "match": { + "host": "example.org" + }, + + "action": { + "return": 301, + "location": "http://www.example.org" + } +} +``` + +See the documentation for a detailed description of routing: +[https://docs.nginx.com/nginx-unit/configuration/#routes](https://docs.nginx.com/nginx-unit/configuration/#routes) + +The second new feature of the release is mostly syntax sugar rather than new +functionality. Now, you can specify servers' weights in an upstream group +using fractional numbers. + +Say, you have a bunch of servers and want one of them to receive half as many +requests as the others for some reason. Previously, the only way to achieve +that was to double the weights of all the other servers: + +```json +{ + "192.168.0.101:8080": { + "weight": 2 + }, + "192.168.0.102:8080": { + "weight": 2 + }, + "192.168.0.103:8080": {}, + "192.168.0.104:8080": { + "weight": 2 + } +} +``` + +Using fractional weights, you can perform the update much easier by altering +the weight of the server in question: + +```json +{ + "192.168.0.101:8080": {}, + "192.168.0.102:8080": {}, + "192.168.0.103:8080": { + "weight": 0.5 + }, + "192.168.0.104:8080": {} +} +``` + +For details of server groups, see here: +[https://docs.nginx.com/nginx-unit/configuration/#proxying](https://docs.nginx.com/nginx-unit/configuration/#proxying) + +```none +Changes with Unit 1.17.0 16 Apr 2020 + + *) Feature: a "return" action with optional "location" for immediate + responses and external redirection. + + *) Feature: fractional weights support for upstream servers. + + *) Bugfix: accidental 502 "Bad Gateway" errors might have occurred in + applications under high load. + + *) Bugfix: memory leak in the router; the bug had appeared in 1.13.0. + + *) Bugfix: segmentation fault might have occurred in the router process + when reaching open connections limit. + + *) Bugfix: "close() failed (9: Bad file descriptor)" alerts might have + appeared in the log while processing large request bodies; the bug + had appeared in 1.16.0. + + *) Bugfix: existing application processes didn't reopen the log file. + + *) Bugfix: incompatibility with some Node.js applications. + + *) Bugfix: broken build on DragonFly BSD; the bug had appeared in + 1.16.0. +``` + +Please also see a blog post about the new features of our two previous releases: +[https://www.nginx.com/blog/nginx-unit-1-16-0-now-available/](https://www.nginx.com/blog/nginx-unit-1-16-0-now-available/) + +To keep the finger on the pulse, refer to our further plans in the roadmap here: +[https://github.com/orgs/nginx/projects/1](https://github.com/orgs/nginx/projects/1) + +--- + +## Unit 1.16.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +{{< note >}} +To all Unit package maintainers: please don't miss the new `--tmp` +configure option. It specifies the directory where the Unit daemon +stores temporary files (i.e. large request bodies) at runtime. +{{< /note >}} + +In this release, we continue improving the functionality related to proxying +and static media asset handling. + +Now, the new `upstreams` object enables creating server groups for +weighted round-robin load balancing: + +```json +{ + "listeners": { + "*:80": { + "pass": "upstreams/rr-lb" + } + }, + + "upstreams": { + "rr-lb": { + "servers": { + "192.168.0.100:8080": {}, + "192.168.0.101:8080": { + "weight": 2 + } + } + } + } +} + +``` + +See the docs for details: +[https://docs.nginx.com/nginx-unit/configuration/#configuration-upstreams](https://docs.nginx.com/nginx-unit/configuration/#configuration-upstreams) + +So far, it's rather basic, but many more proxying and load-balancing +features are planned for future releases. + +By its design, the new `fallback` option is somewhat similar to the +`try_files` directive in `nginx`. It allows proceeding to +another action if a file isn't available: + +```json +{ + "share": "/data/www/", + + "fallback": { + "pass": "applications/php" + } +} + +``` + +In the example above, an attempt is made first to serve a request +with a file from the `/data/www/` directory. If there's no such +file, the request is passed to the `php` application. + +Also, you can chain such fallback actions: + +```json +{ + "share": "/data/www/", + + "fallback": { + "share": "/data/cache/", + + "fallback": { + "proxy": "http://127.0.0.1:9000" + } + } +} + +``` + +More info: [https://docs.nginx.com/nginx-unit/configuration/#configuration-fallback](https://docs.nginx.com/nginx-unit/configuration/#configuration-fallback) + +Finally, configurations you upload can use line (`//`) and block +(`/* */`) comments. Now, Unit doesn't complain; instead, it strips them +from the JSON payload. This comes in handy if you store your configuration in +a file and edit it manually. + +```none +Changes with Unit 1.16.0 12 Mar 2020 + + *) Feature: basic load-balancing support with round-robin. + + *) Feature: a "fallback" option that performs an alternative action if a + request can't be served from the "share" directory. + + *) Feature: reduced memory consumption by dumping large request bodies + to disk. + + *) Feature: stripping UTF-8 BOM and JavaScript-style comments from + uploaded JSON. + + *) Bugfix: negative address matching in router might work improperly in + combination with non-negative patterns. + + *) Bugfix: Java Spring applications failed to run; the bug had appeared + in 1.10.0. + + *) Bugfix: PHP 7.4 was broken if it was built with thread safety + enabled. + + *) Bugfix: compatibility issues with some Python applications. +``` + +To keep the finger on the pulse, see our further plans in the roadmap here: +[https://github.com/orgs/nginx/projects/1](https://github.com/orgs/nginx/projects/1) + +Also, good news for macOS users! Now, there's a Homebrew tap for Unit: +[https://docs.nginx.com/nginx-unit/installation/#homebrew](https://docs.nginx.com/nginx-unit/installation/#homebrew) + +--- + +## Unit 1.15.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This is mostly a bugfix release that eliminates a few nasty issues. +Also, it addresses incompatibilities caused by a minor API change in +the recently released major version of Ruby. + +```none +Changes with Unit 1.15.0 06 Feb 2020 + + *) Change: extensions of dynamically requested PHP scripts were + restricted to ".php". + + *) Feature: compatibility with Ruby 2.7. + + *) Bugfix: segmentation fault might have occurred in the router process + with multiple application processes under load; the bug had appeared + in 1.14.0. + + *) Bugfix: receiving request body over TLS connection might have + stalled. +``` + +More features are planned for the next release that is expected in the +beginning of March. Among them are basic load balancing in the proxy module +and `try_files`-like functionality for more sophisticated request +routing. diff --git a/content/unit/news/archived-news/2021.md b/content/unit/news/archived-news/2021.md new file mode 100644 index 000000000..46600063d --- /dev/null +++ b/content/unit/news/archived-news/2021.md @@ -0,0 +1,696 @@ +--- +title: 2021 +weight: 200 +toc: true +--- + +## Unit 1.26.1 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This is a minor bugfix release that aims to eliminate some annoying regressions +revealed after the release of Unit 1.26.0 two weeks ago. + +Notably, the shared [OPcache](https://www.php.net/manual/en/book.opcache.php) implementation in that +release required introducing some major architectural changes, but our +functional tests didn't catch some regressions caused by these changes. Still, +thanks to our active community, the issues were reported shortly after the +release, and now we can provide an updated version. We will also improve our +functional testing to avoid such regressions in the future. + +The most painful and visible one was that sometimes Unit daemon couldn't +completely exit, leaving some zombie processes. However, the second attempt to +stop it always succeeded. Also, some [DragonFly BSD](https://www.dragonflybsd.org) kernel interfaces +are seemingly broken, preventing the Unit daemon from functioning, so we +disabled their use when compiling for DragonFly BSD. + +### Full Changelog + +```none +Changes with Unit 1.26.1 02 Dec 2021 + +- Bugfix: occasionally, the Unit daemon was unable to fully terminate; + the bug had appeared in 1.26.0. + +- Bugfix: a prototype process could crash on an application process + exit; the bug had appeared in 1.26.0. + +- Bugfix: the router process crashed on reconfiguration if "access_log" + was configured without listeners. + +- Bugfix: a segmentation fault occurred in the PHP module if chdir() or + fastcgi_finish_request() was called in the OPcache preloading script. + +- Bugfix: fatal errors on DragonFly BSD; the bug had appeared in + 1.26.0. +``` + +To know more about the bunch of changes introduced in Unit 1.26 and the roadmap +for 1.27, please see the previous announcement: +[https://mailman.nginx.org/pipermail/unit/2021-November/000288.html](https://mailman.nginx.org/pipermail/unit/2021-November/000288.html) + +Thank you again for keeping your finger on the pulse, reporting issues, and +submitting feature requests via our [GitHub issue tracker](https://github.com/nginx/unit/issues). + +--- + +## Unit 1.26.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +Please read this letter to the end, as it explains some significant changes in +the latest version. But first, I have great news for the PHP users: now the +interpreter's [OPcache](https://www.php.net/manual/en/book.opcache.php) is +shared between the processes of an app. + +In previous versions, due to an architecture limitation (which imposed strong +isolation, much stronger than was sometimes needed), each PHP process had +a separate OPcache memory. As a result, with some workloads (especially +involving many dynamic processes), performance could degrade because each +new process had to warm up the cache after starting. Also, it required more +memory because the bytecode of the same PHP scripts was duplicated in each process. +Now, all these flaws are finally gone. + +Next, we noticed that more and more users use Unit to serve static files, +if only because it's efficient and easy to configure. Modern apps are all +dynamic, yes, but at the same time, almost all apps and websites have static +resources like images, scripts, styles, fonts, and media files. It's very +important to supply these resources as fast as possible without any delays +to smoothen the overall user experience. We take this aspect seriously and +continue improving Unit capabilities as a generic static media web server. + +This time, all changes are about configuration flexibility. You may know that +NGINX has a number of different directives that control static file serving: + +- `root` +- `alias` +- `try_files` + +Some of these are mutually exclusive, some can be combined, some work +differently depending on the location type. That gives the configuration +a lot of flexibility but may look a bit complicated. Users kept asking us +to provide the same functionality in Unit, but instead of just repeating +these, we thought about how we can improve this experience to make it easier +to understand without losing flexibility. + +Finally, we came up with a solution. In previous versions, we introduced the +`share` directive, very similar to the `root` directive in +NGINX: + +```json +{ + "share": "/path/to/dir/" +} +``` + +Basically, it specified the so-called document root directory. To determine a +file to serve, Unit appended the URI from the request to this `share` +path. For this request: + +```none +GET /some/file.html +``` + +The above configuration served `/path/to/dir/some/file.html`. + +In simple times, that's exactly what you want. Still, there are plenty of +cases when a different file needs to be served and the requested URI doesn't +match a path. More, you may want to serve a single file for any requests; +the so-called one-page apps often utilize such a scheme. + +Starting with Unit 1.26.0, the `share` option specifies the *entire* path +to a shared file rather than just the document root. And yes, the option +supports variables, so you can write: + +```json +{ + "share": "/www/data/$uri" +} +``` + +This occurs only once, after the version update. If you manage your +configurations using some scripts and store them somewhere else, +make sure to adjust the "share" values there accordingly. + +To read more about the new share behavior, check the documentation: +[https://docs.nginx.com/nginx-unit/configuration/#static-files](https://docs.nginx.com/nginx-unit/configuration/#static-files) + +### Full Changelog + +```none +Changes with Unit 1.26.0 18 Nov 2021 + + *) Change: the "share" option now specifies the entire path to the files + it serves, rather than a document root directory to be prepended to + the request URI. + + *) Feature: automatic adjustment of existing configurations to the new + "share" behavior when updating from previous versions. + + *) Feature: variables support in the "share" option. + + *) Feature: multiple paths in the "share" option. + + *) Feature: variables support in the "chroot" option. + + *) Feature: PHP opcache is shared between application processes. + + *) Feature: request routing by the query string. + + *) Bugfix: the router and app processes could crash when the requests + limit was reached by asynchronous or multithreaded apps. + + *) Bugfix: established WebSocket connections could stop reading frames + from the client after the corresponding listener had been + reconfigured. + + *) Bugfix: fixed building with glibc 2.34, notably Fedora 35. +``` + +Other major features that we are preparing for the next release include: + +- Basic statistics API for monitoring Unit instances +- Various variables for different aspects of request and connection data +- Customization of access log format with variables +- Custom variables out of regexp captures on various request parameters +- Simple request rewrite using variables +- Command-line tool to simplify the use of Unit's control socket API + +To participate, share your ideas, or discuss new features, you're welcome +to visit Unit's issue tracker on GitHub: [https://github.com/nginx/unit/issues](https://github.com/nginx/unit/issues) + +--- + +## Unit 1.25.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This one is much awaited not only because the last one occurred quite some +time ago, but also because it contains some sought-after features that were +requested quite often. + +### Obtaining The Originating Client IP Address + +When Unit operates behind a reverse proxy, it receives all incoming connections +from a proxy machine address. As a result, the originating IP address of +a client cannot be determined from the IP protocol. To overcome this, a +special HTTP request header field can be used to carry the client IP address +information over one to several proxies. Such header fields are usually called +"X-Forwarded-For", but variations exist as well ("X-Real-IP", "X-Remote-Addr", +etc..). + +Before, Unit could not use information from such header fields otherwise than +just pass them on "as is." With this release, functionality similar to the +real-ip nginx module became available. Now, in any listener object, you can +specify a `client_ip` option, configuring trusted proxy addresses and the +header field name, to obtain the client IP address: + +```json +{ + "listeners": { + "*:80": { + "client_ip": { + "header": "X-Forwarded-For", + "recursive": true, + "source": [ + "10.0.0.0/8", + "150.172.238.0/24" + ] + } + } + } +} +``` + +Unit will use the address obtained from this header to the same effect as if a +direct connection was made from the client. For instance, it will be reflected +in any logs, used for source address matching in routing, and provided to the +application via a relevant request environment (e.g. `$_SERVER['REMOTE_ADDR']` in PHP). + +See more details in the documentation: +[https://docs.nginx.com/nginx-unit/configuration/#originating-ip-identification](https://docs.nginx.com/nginx-unit/configuration/#originating-ip-identification) + +### Control API to Restart Application Processes + +Unit dynamic configuration is pretty smart and granular. If it detects +no changes to an application during reconfiguration, it won't touch the +application's processes. However, sometimes our users need to restart a +specific application, and the only good way to do that was to intentionally +introduce a change to the application's configuration. Usually, a dummy +`environment` option was used for this: + +```console +curl -X PUT -d '"$RANDOM"' --unix-socket /var/run/control.unit.sock \ + /config/applications//environment/gen +``` + +While it worked well, the solution can't be called elegant; it was more like a +workaround. But now, Unit has a special section in the control API that allows +restarting any configured application with a basic GET request: + +```console +curl --unix-socket /var/run/control.unit.sock \ + /control/applications//restart +``` + +See here for the details of app process management in Unit: +[https://docs.nginx.com/nginx-unit/configuration/#process-management](https://docs.nginx.com/nginx-unit/configuration/#process-management) + +### Full Changelog + +```none +Changes with Unit 1.25.0 19 Aug 2021 + + *) Feature: client IP address replacement from a specified HTTP header + field. + + *) Feature: TLS sessions cache. + + *) Feature: TLS session tickets. + + *) Feature: application restart control. + + *) Feature: process and thread lifecycle hooks in Ruby. + + *) Bugfix: the router process could crash on TLS connection open when + multiple listeners with TLS certificates were configured; the bug had + appeared in 1.23.0. + + *) Bugfix: TLS connections were rejected for configurations with + multiple certificate bundles in a listener if the client did not use + SNI. + + *) Bugfix: the router process could crash with frequent multithreaded + application reconfiguration. + + *) Bugfix: compatibility issues with some Python ASGI apps, notably + based on the Starlette framework. + + *) Bugfix: a descriptor and memory leak occurred in the router process + when an app process stopped or crashed. + + *) Bugfix: the controller or router process could crash if the + configuration contained a full-form IPv6 in a listener address. + + *) Bugfix: the router process crashed when a request was passed to an + empty "routes" or "upstreams" using a variable "pass" option. + + *) Bugfix: the router process crashed while matching a request to an + empty array of source or destination address patterns. +``` + +In the meantime, there are several other features currently at different stages +of development and implementation: + +- Variable support in the static file serving options +- Custom variables from regexp captures in the "match" object +- Simple request rewrites using variables +- More variables to access request and connection information +- A statistics API +- Unit CLI utility tool +- App prototype processes to reduce memory usage, share the PHP opcache, + and improve the handling of apps isolation +- [`njs`](https://nginx.org/en/docs/njs/index.html) integration +- .NET Core language module prototype + +--- + +## Unit 1.24.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This one is full of shiny new features. But before I dive into the details, +let me introduce our new developers without whom this release wouldn't be so +feature-rich. Please, welcome Zhidao Hong (洪志道) and Oisín Canty. + +Zhidao has already been contributing to various nginx open-source projects for +years as a community member, and I'm very excited to finally have him on board. + +Oisín is a university student who's very interested in Unit; he joined our dev +team as an intern and already shown solid coding skills, curiosity, and +attention to details, which is so important to our project. Good job! + +Now, back to the features. I'd like to highlight the first of our improvements +in serving static media assets. + +### MIME Type Filtering + +Now, you can restrict file serving by MIME type: + +```json +{ + "share": "/www/data", + "types": [ "image/*", "video/*" ] +} +``` + +The configuration above allows only files with various video and image +extensions, but all other requests will return status code 403. + +In particular, this goes well with the `fallback` option that performs +another action if the `share` returns a 40x error: + +```json +{ + "share": "/www/data", + "types": [ "!application/x-httpd-php" ], + + "fallback": { + "pass": "applications/php" + } +} +``` + +Here, all requests to existing files other than `.php` will be served as +static content while the rest will be passed to a PHP application. + +More examples and documentation snippets are available here: +[https://docs.nginx.com/nginx-unit/configuration/#mime-filtering](https://docs.nginx.com/nginx-unit/configuration/#mime-filtering) + +### Full Changelog + +```none +Changes with Unit 1.24.0 27 May 2021 + + *) Change: PHP added to the default MIME type list. + + *) Feature: arbitrary configuration of TLS connections via OpenSSL + commands. + + *) Feature: the ability to limit static file serving by MIME types. + + *) Feature: support for chrooting, rejecting symlinks, and rejecting + mount point traversal on a per-request basis when serving static + files. + + *) Feature: a loader for automatically overriding the "http" and + "websocket" modules in Node.js. + + *) Feature: multiple "targets" in Python applications. + + *) Feature: compatibility with Ruby 3.0. + + *) Bugfix: the router process could crash while closing a TLS + connection. + + *) Bugfix: a segmentation fault might have occurred in the PHP module if + fastcgi_finish_request() was used with the "auto_globals_jit" option + enabled. +``` + +--- + +## Unit 1.24.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This one is full of shiny new features. But before I dive into the details, +let me introduce our new developers without whom this release wouldn't be so +feature-rich. Please, welcome Zhidao Hong (洪志道) and Oisín Canty. + +Zhidao has already been contributing to various nginx open-source projects for +years as a community member, and I'm very excited to finally have him on board. + +Oisín is a university student who's very interested in Unit; he joined our dev +team as an intern and already shown solid coding skills, curiosity, and +attention to details, which is so important to our project. Good job! + +Now, back to the features. I'd like to highlight the first of our improvements +in serving static media assets. + +### MIME Type Filtering + +Now, you can restrict file serving by MIME type: + +```json +{ + "share": "/www/data", + "types": [ "image/*", "video/*" ] +} +``` + +The configuration above allows only files with various video and image +extensions, but all other requests will return status code 403. + +In particular, this goes well with the `fallback` option that performs +another action if the `share` returns a 40x error: + +```json +{ + "share": "/www/data", + "types": [ "!application/x-httpd-php" ], + + "fallback": { + "pass": "applications/php" + } +} +``` + +Here, all requests to existing files other than `.php` will be served as +static content while the rest will be passed to a PHP application. + +More examples and documentation snippets are available here: +[https://docs.nginx.com/nginx-unit/configuration/#mime-filtering](https://docs.nginx.com/nginx-unit/configuration/#mime-filtering) + +### Full Changelog + +```none +Changes with Unit 1.24.0 27 May 2021 + + *) Change: PHP added to the default MIME type list. + + *) Feature: arbitrary configuration of TLS connections via OpenSSL + commands. + + *) Feature: the ability to limit static file serving by MIME types. + + *) Feature: support for chrooting, rejecting symlinks, and rejecting + mount point traversal on a per-request basis when serving static + files. + + *) Feature: a loader for automatically overriding the "http" and + "websocket" modules in Node.js. + + *) Feature: multiple "targets" in Python applications. + + *) Feature: compatibility with Ruby 3.0. + + *) Bugfix: the router process could crash while closing a TLS + connection. + + *) Bugfix: a segmentation fault might have occurred in the PHP module if + fastcgi_finish_request() was used with the "auto_globals_jit" option + enabled. +``` + +--- + +## Unit 1.23.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +Nowadays, TLS is everywhere, while plain HTTP is almost nonexistent in the +global network. We are fully aware of this trend and strive to simplify TLS +configuration in Unit as much as possible. Frankly, there's still much to do, +but the introduction of smart SNI certificate selection marks yet another step +in this direction. + +Perhaps, you already know about Unit's certificate storage API that uploads +certificate bundles to a running instance. Otherwise, if you're not yet fully +informed but still curious, here's a decent overview: +[https://docs.nginx.com/nginx-unit/certificates/](https://docs.nginx.com/nginx-unit/certificates/) + +Basically, you just upload a certificate chain and a key under some name; after +that, you can specify the name (`mycert` in the example below) with any +listening socket to configure it for HTTPS: + +```json +{ + "listeners": { + "*:443": { + "tls": { + "certificate": "mycert" + }, + "pass": "routes" + } + } +} +``` + +Unit's API also enables informative introspection of uploaded certificate +bundles so you can monitor their validity and benefit from service discovery. + +You can also upload any number of certificate bundles to switch between them on +the fly without restarting the server. Still, with this release, there are even +more options, as you can supply any number of certificate bundle names with a +listener socket: + +```json +{ + "certificate": [ "mycertA", "mycertB", ... ] +} +``` + +For each client, Unit automatically selects a suitable certificate from the +list depending on the domain name the client connects to (and therefore +supplies via the "Server Name Indication" TLS extension). Thus, you don't even +need to care about matching certificates to server names; Unit handles that for +you. As a result, there's almost no room for a mistake, which spares more time +for stuff that matters. + +As one can reasonably expect, you can always add more certs, delete them, or +edit the cert list on the fly without compromising performance. That's the +Unit way! + +Also, plenty of solid bug-fixing work was done by the whole team. See the full +change log below: + +### Full Changelog + +```none +Changes with Unit 1.23.0 25 Mar 2021 + + *) Feature: support for multiple certificate bundles on a listener via + the Server Name Indication (SNI) TLS extension. + + *) Feature: "--mandir" ./configure option to specify the directory for + man page installation. + + *) Bugfix: the router process could crash on premature TLS connection + close; the bug had appeared in 1.17.0. + + *) Bugfix: a connection leak occurred on premature TLS connection close; + the bug had appeared in 1.6. + + *) Bugfix: a descriptor and memory leak occurred in the router process + when processing small WebSocket frames from a client; the bug had + appeared in 1.19.0. + + *) Bugfix: a descriptor leak occurred in the router process when + removing or reconfiguring an application; the bug had appeared in + 1.19.0. + + *) Bugfix: persistent storage of certificates might've not worked with + some filesystems in Linux, and all uploaded certificate bundles were + forgotten after restart. + + *) Bugfix: the controller process could crash while requesting + information about a certificate with a non-DNS SAN entry. + + *) Bugfix: the controller process could crash on manipulations with a + certificate containing a SAN and no standard name attributes in + subject or issuer. + + *) Bugfix: the Ruby module didn't respect the user locale for defaults + in the Encoding class. + + *) Bugfix: the PHP 5 module failed to build with thread safety enabled; + the bug had appeared in 1.22.0. +``` + +--- + +## Unit 1.22.0 Released + +Hi, + +I'm glad to announce a new release of NGINX Unit. + +This is our first release of 2021, and it focuses on improving stability. +There's an extensive list of bugfixes, although many occur in rare conditions +that have so far been observed only in our test environments. These bugs +were caught due to improvements in our continuous functional testing; our QA, +Andrei Zeliankou, is always looking to increase the testing coverage and use +new techniques to spot various race conditions and leaks, thus improving +the quality of each release. This very important work never ends. + +### IMPORTANT: Changes to Official Linux Packages + +Starting with this release, the user and group accounts that run non-privileged +Unit processes are changed in our Linux packages: + +- in previous packages: `nobody:nobody` +- in 1.22.0 and later: `unit:unit` + +These settings are used to serve static files and run applications if +`user` or `group` options are not explicitly specified in the app +configuration. + +Please take a note of the change and update your configuration appropriately +before upgrading an existing Unit installation with our official packages: +[https://docs.nginx.com/nginx-unit/installation/#official-packages](https://docs.nginx.com/nginx-unit/installation/#official-packages) + +The rationale for this change in our packages was that using `nobody` by +default was very inconvenient while serving static files. You can always +override these settings with the `--user` and `--group` +daemon options in your startup scripts. See here for more details: +[https://docs.nginx.com/nginx-unit/howto/source/#startup-and-shutdown](https://docs.nginx.com/nginx-unit/howto/source/#startup-and-shutdown) + +### IMPORTANT 2: Changes to official Docker images + +Another notable change is also related to our official distributions; in +this case, it affects our Docker images. Many asked us to provide the most +up-to-date versions of language modules in our Docker images, but there was +no maintainable way of doing this while still relying on the Debian base +image we used before. + +Starting with 1.22.0, we stop maintaining images with language modules that use +the old Debian base; instead, now we rely on official Docker images for latest +language versions: [https://docs.nginx.com/nginx-unit/installation/#docker-images](https://docs.nginx.com/nginx-unit/installation/#docker-images) + +Our images are available at both Docker Hub and Amazon ECR Public Gallery; +you can also download them at our website. + +### Full Changelog + +```none +Changes with Unit 1.22.0 04 Feb 2021 + + *) Feature: the ServerRequest and ServerResponse objects of Node.js + module are now compliant with Stream API. + + *) Feature: support for specifying multiple directories in the "path" + option of Python apps. + + *) Bugfix: a memory leak occurred in the router process when serving + files larger than 128K; the bug had appeared in 1.13.0. + + *) Bugfix: apps could stop processing new requests under high load; the + bug had appeared in 1.19.0. + + *) Bugfix: app processes could terminate unexpectedly under high load; + the bug had appeared in 1.19.0. + + *) Bugfix: invalid HTTP responses were generated for some unusual status + codes. + + *) Bugfix: the PHP_AUTH_USER, PHP_AUTH_PW, and PHP_AUTH_DIGEST server + variables were missing in the PHP module. + + *) Bugfix: the router process could crash with multithreaded apps under + high load. + + *) Bugfix: Ruby apps with multithreading configured could crash on start + under load. + + *) Bugfix: mount points weren't unmounted when the "mount" namespace + isolation was used; the bug had appeared in 1.21.0. + + *) Bugfix: the router process could crash while removing or + reconfiguring an app that used WebSocket. + + *) Bugfix: a memory leak occurring in the router process when removing + or reconfiguring an application; the bug had appeared in 1.19.0. +``` diff --git a/content/unit/news/archived-news/2022.md b/content/unit/news/archived-news/2022.md new file mode 100644 index 000000000..f294c5891 --- /dev/null +++ b/content/unit/news/archived-news/2022.md @@ -0,0 +1,510 @@ +--- +title: 2022 +weight: 100 +toc: true +--- + +## Unit 1.29.0 Released + +We are happy to announce Unit 1.29.0! This release enhances the configuration +experience when managing Unit and provides programmability within the +configuration. + +- NGINX JavaScript (njs) is now built with official Unit packages, enabling + JavaScript expressions within configuration values. +- First-time users benefit from a setup script that configures Unit with a + helpful welcome page. +- A simple command-line curl(1) wrapper simplifies configuring a running + instance in real time. + +In addition, Unit's isolation capabilities have been extended so that each +application can run in a new or a pre-existing [Linux cgroup](https://en.wikipedia.org/wiki/Cgroups), +but this is only a sampler of even richer per-application observability. Read on for full details of these +enhancements, smaller features, and bug fixes. + +### NGINX JavaScript Integration + +NGINX JavaScript (njs) is a server-side JavaScript runtime, optimized for +ultra-fast initialization, with a virtual machine that lives and dies with each +request. Originally designed for extending NGINX, the njs architecture lends +itself to integration, and now it also extends Unit! + +This release brings the initial integration of the NGINX JavaScript engine to +Unit. Future releases will extend these capabilities to enable more elaborate +uses. With Unit 1.29.0, JavaScript template literals may be used in +configuration strings to execute JavaScript expressions. A simple example is +to use the ternary operator to make a routing decision. + +```console +# curl --unix-socket /var/run/control.unit.sock http://localhost/config/routes +``` + +```json +[ + { + "action": { + "pass": "`applications/${new Date().getHours() < 12 ? 'am' : 'pm'}`" + } + } +] +``` + +Here, requests are passed between different applications depending on the time +of day. Note that a template literal is enclosed in backticks (`\``), +and `${}` encloses the JavaScript expression. Template literals may be +used wherever Unit supports variables, and multiple expressions can appear in a +single template literal. + +Also, this embedded JavaScript code can access various HTTP request properties: + +- Scalars: `host`, `uri`, `remoteAddr` +- Objects: `args`, `cookies`, `headers` + +Let's use these properties to redirect clients to the HTTPS login page if there +is no `session` cookie: + +```console +# curl --unix-socket /var/run/control.unit.sock http://localhost/config/routes/0 +``` + +```json +{ + "match": { + "scheme": "http" + }, + + "action": { + "return": 302, + "location": "`https://${host}${cookies['session'] === undefined ? '/login' : uri}`" + } +} +``` + +More complex logic can be implemented using the [immediately invoked function expressions (IIFE)](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) in +the template literal: an entire JavaScript function can be defined, +comprising multiple statements and local variables. + +This defines a simple key-value log format that parses a JSON Web Token (JWT) to extract the sub claim: + +```console +# curl --unix-socket /var/run/control.unit.sock http://localhost/config/access_log +``` + +```json +{ + "path": "/var/log/unit/access_kv.log", + "format": "`timestamp=${new Date().toISOString()} ip=${remoteAddr} uri=${uri} sub=${(() => { var authz = headers['Authorization']; if (authz === undefined) { return '-'; } else { var parts = authz.slice(7).split('.').slice(0,2).map(v=>Buffer.from(v, 'base64url').toString()).map(JSON.parse); return parts[1].sub; } } )()} +`" +} +``` + +Embedding IIFE code in the configuration is extremely powerful, but is typically long, difficult to read, and challenging to debug. The [`njs` command line utility](http://nginx.org/en/docs/njs/cli.html) can be used to help develop JavaScript expressions. + +Future releases will support loading JavaScript modules into a separate storage and later using module-based functions in the configuration. + +### Configuration Tools + +This release introduces two new command-line tools to simplify Unit's installation and configuration. + +#### setup-unit + +The `setup-unit` tool automates configuring the software repository +prior to installing Unit. It also verifies a fresh installation by configuring +and serving a "welcome" web page. This takes some of the guesswork out of the +installation process for first-time users and guides them to their next steps. +Installing and running Unit on a typical Linux system is now as simple as this: + +```console +$ wget https://unit.nginx.org/_downloads/setup-unit && chmod +x setup-unit +``` + +```console +# ./setup-unit repo-config +``` + +```console +# apt install unit || yum install unit +``` + +```console +# ./setup-unit welcome +``` + +The `setup-unit` tool has other useful functions you can explore by +running `setup-unit --help`. + +#### unitc + +The `unitc` tool provides a command-line interface as a wrapper for +`curl(1)` for daily configuration and management of Unit instances. + +```console +$ unitc /config +``` + +```console +$ cat conf.json | unitc /config +``` + +### Per-Application Cgroups + +With Unit 1.29.0, we support the cgroup V2 API to provide the ability to place +each application into its own cgroup or have multiple applications in a single +cgroup. The following configuration illustrates the newly added configuration +syntax: + +```json +"applications": { + "cgroup-demo": { + "type": "python", + "path": "/path/to/app/dir", + "module": "app", + "isolation": { + "cgroup": { + "path": "unit/cgroup-demo" + } + } + } +} +``` + +--- + +## Unit Not Impacted By CVE-2022-35256, CVE-2022-40674 + +This is a brief announcement to notify our users that NGINX Unit is not +impacted by [CVE-2022-40674](https://nvd.nist.gov/vuln/detail/CVE-2022-40674#vulnCurrentDescriptionTitle) +and [CVE-2022-35256](https://feed.prelude.org/p/cve-2022-35256); we don't +use `libexpat` and implement our own HTTP stack when integrating with +Node.js. + +--- + +## Unit 1.28.0 Released + +We are happy to announce Unit 1.28! This release sets the first milestone for +observability: + +- It is now possible to get basic information about connections, requests, and + other per-application metrics + +- All this is now available via our powerful RESTful API + +In addition, we introduce new variables and the ability to use them to +customize the access log format. Besides the long-awaited statistics and +logging use cases, we also present: + +- Enhanced forward header handling with new configuration syntax and + X-Forwarded-Proto support + +- Support for abstract UNIX domain sockets in listeners on Linux-like + systems + +- Fixes for several community-reported bugs + +### Metrics and Statistics + +With 1.28, the Unit API has a new endpoint available; the `/status` +endpoint is exposed at the root level, as with the `/config` and +`/certificates` endpoints: + +```console +curl --unix-socket /var/run/control.unit.sock http://localhost +``` + +```json +{ + "config": { + "listeners": { + }, + + "applications": { + } + }, + + "status": { + "connections": { + "accepted": 0, + "active": 0, + "idle": 0, + "closed": 0 + }, + + "requests": { + "total": 0 + }, + + "applications": {} + } +} +``` + +The `status` object contains three nested objects: + +- The `connections` object provides detailed information about the client + connections to the Unit instance or, specifically, to its listeners. Here, + `accepted` and `closed` are total values accumulated over the + instance's lifetime; restarting Unit resets the total values. + +- In contrast, `active` and `idle` are spot values representing the + number of active or idle requests at one of the listeners that Unit exposes. + +The `requests` object holds the total number of requests to all exposed +listeners since the last restart. + +{{< note >}} + +Both `connections` and `requests` count requests to Unit's +listeners, NOT the config API itself. + +{{< /note >}} + +- The `applications` section follows the `/config/applications` + tree in the API; again, there's no special setup required because Unit + automatically maintains per-app metrics for all applications in + `/config/applications`, and the apps' names identify them respectively. + +Consider the following applications configuration as an example: + +```json +{ + "my-app":{ + "type": "external", + "working_directory": "/www/chat", + "executable": "bin/chat_app", + "processes":{ + "max": 10, + "spare": 5, + "idle_timeout": 20 + } + } +} +``` + +The interesting part is the `processes` configuration. We defined a +maximum of 10 and a spare number of 5 processes; the `idle_timeout` is 20 +seconds. After a couple of requests, let's look at the app statistics: + +```json +{ + "my-app":{ + "processes":{ + "running": 9, + "starting": 0, + "idle": 2 + }, + + "requests":{ + "active": 9 + } + } +} +``` + +Knowing the process configuration of `my-app`, this is quite easy to +understand. Currently, there are 9 out of 10 total processes running, while 0 +are currently starting. The two idles are inactive app processes that have not +reached the `idle_timeout` yet; these will be removed when the configured +timeout of 20 seconds elapses, so the number of running processes will drop to +7. + +So, with Unit 1.28, you now can see your basic workload and process statistics +for the Unit instance itself as well as individual applications. This is but a +first, very important step to increased visibility for us. + +### Full Changelog + +```none +Changes with Unit 1.28.0 13 Sep 2022 + + *) Change: increased the applications' startup timeout. + + *) Feature: basic statistics API. + + *) Feature: customizable access log format. + + *) Feature: more HTTP variables support. + + *) Feature: forwarded header to replace client address and protocol. + + *) Feature: ability to get dynamic variables. + + *) Feature: support for abstract Unix sockets. + + *) Bugfix: the Ruby application process could crash on SIGINT. + + *) Bugfix: mutex leak in the C API. +``` + +### Platform Updates + +#### Docker Images + +- The Unit JSC11 image is now based on `eclipse-temurin` instead of + `openjdk` + +- Go version bump: 1.18 → 1.19 + +- Perl version bump: 5.34 → 5.36 + +--- + +## Unit 1.27.0 Released + +We are pleased to announce NGINX Unit 1.27. This release brings a new level of +maturity to Unit for serving static assets. We love using Unit as a +cloud-native web server, and this release brings some missing features to this +use case. + +- Redirecting HTTP requests to HTTPS +- Configurable filename for path-only URIs + +### Redirecting HTTP Requests to HTTPS + +Since we added TLS support and certificate management to Unit, we’ve been asked +to simplify redirecting plaintext HTTP requests to the TLS-enabled listener. +This is now possible by configuring the `location` value of a route +action to contain variables. Indeed, a new variable, `$request_uri`, is +now available that contains the path-and-query parts of the original URI, +preserving any encoding needed by the browser. + +A full example is provided below. + +```json +{ + "listeners": { + "*:443": { + "tls": { + "certificate": "example.com" + }, + "pass": "routes" + }, + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "scheme": "http" + }, + "action": { + "return": 301, + "location": "https://${host}${request_uri}" + } + } + ] +} +``` + +This configuration enables Unit to listen on plaintext and TLS-enabled ports, +ensuring that any requests received on the plaintext port notify the browser to +resubmit on the TLS-enabled port. See more details in the documentation. + +### Configurable Filename for Path-Only URIs + +While it is conventional for `index.html` to represent the resource to be +served when a path-only URI is requested, i.e., one without a filename suffix, +this convention is rooted in history. It comes from a time in the early 1990s +when HTTP was used exclusively to index and navigate HTML pages. + +You can now use a different default filename by specifying the index for a +route action. A full example is provided below. + +```json +"routes": [ + { + "match": { + "uri": "/cms/*" + }, + "action": { + "share": "/var/cms$uri", + "index": "default.html" + } + }, + { + "action": { + "share": "/var/www$uri" + } + } +] +``` + +This configuration enables Unit to serve `default.html` for path-only +URIs made to `/cms/*` and the default `index.html` filename for all +other path-only URIs. See more details in the documentation. + +### Full Changelog + +```none +Changes with Unit 1.27.0 02 Jun 2022 + + *) Feature: ability to specify a custom index file name when serving + static files. + + *) Feature: variables support in the "location" option of the "return" + action. + + *) Feature: support empty strings in the "location" option of the + "return" action. + + *) Feature: added a new variable, $request_uri, that includes both the + path and the query parts as per RFC 3986, sections 3-4. + + *) Feature: Ruby Rack environment parameter "SCRIPT_NAME" support. + + *) Feature: compatibility with GCC 12. + + *) Bugfix: Ruby Sinatra applications don't work without custom logging. + + *) Bugfix: the controller process could crash when a chain of more than + four certificates was uploaded. + + *) Bugfix: some Perl applications failed to process the request body, + notably with Plack. + + *) Bugfix: some Spring Boot applications failed to start, notably with + Grails. + + *) Bugfix: incorrect Python protocol auto-detection (ASGI or WSGI) for + native callable object, notably with Falcon. + + *) Bugfix: ECMAScript modules did not work with the recent Node.js + versions. +``` + +### Platform Updates + +Official packages are now available for the following Linux distributions: + +- Fedora 36 +- RHEL 9 +- Ubuntu 22.04 + +Docker images have been updated to use the latest +language versions: + +- Go 1.18 +- PHP 8.1 +- Ruby 3.1 + +### Community, Roadmap, and Open Issues + +We continue to receive valuable bug reports and feature requests to our [GitHub +issues page](https://github.com/nginx/unit/issues). Although we are a small +team, every issue is reviewed, and we aim to respond within 2-3 days. Since the +last release, we refreshed our [GitHub README](https://github.com/nginx/unit#readme) +with a super-quick-start guide and added [contribution guidelines](https://github.com/nginx/unit/blob/master/CONTRIBUTING.md) +to help you get involved. For other discussions, please join us at the `#unit-users` +channel on the [NGINX Community Slack](https://nginxcommunity.slack.com/join/shared_invite/zt-1aaa22w80-~_~wSMNyPxLPLp5xunOC7w) +or the [mailing list](https://mailman.nginx.org/mailman3/lists/unit.nginx.org/). + +Although this release focuses on bug fixes and web server features, we have +advanced in several other projects that you can expect to see in forthcoming +releases this year: + +- Custom logging (which brings lots of new variables and places you can use them) +- JavaScript-in-the-configuration (already available as an [experimental patch](https://github.com/nginx/unit/issues/652)) +- Statistics API to provide true observability for Unit +- CLI tool for easier command-line management of Unit diff --git a/content/unit/news/archived-news/_index.md b/content/unit/news/archived-news/_index.md new file mode 100644 index 000000000..e6716b2fa --- /dev/null +++ b/content/unit/news/archived-news/_index.md @@ -0,0 +1,4 @@ +--- +title: "Archived news and releases" +weight: 400 +--- diff --git a/content/unit/scripting.md b/content/unit/scripting.md new file mode 100644 index 000000000..ee058810c --- /dev/null +++ b/content/unit/scripting.md @@ -0,0 +1,212 @@ +--- +title: Scripting +weight: 700 +toc: true +--- + +NGINX Unit's [control API]({{< relref "/unit/controlapi.md" >}}) supports +JavaScript expressions, including function calls, in the form of +[template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) +written in +[NGINX JavaScript](https://nginx.org/en/docs/njs/) ( `njs` ). +They can be used with these [configuration]({{< relref "/unit/configuration.md" >}}) options: + +- **pass** in + [listeners]({{< relref "/unit/configuration.md#configuration-listeners">}}) + and + [actions]({{< relref "/unit/configuration.md#configuration-routes-action">}}) + to choose between routes, applications, app targets, or upstreams. +- **response_headers** values in + [actions]({{< relref "/unit/configuration.md#configuration-routes-action">}}) + to manipulate response header fields. +- **rewrite** in + [actions]({{< relref "/unit/configuration.md#configuration-routes-action">}}) + to enable [URI rewriting]({{< relref "/unit/configuration.md#configuration-rewrite">}}). +- **share** and **chroot** in + [actions]({{< relref "/unit/configuration.md#configuration-routes-action">}}) + to control [static content serving]({{< relref "/unit/configuration.md#configuration-static">}}). +- **location** in **return** + [actions]({{< relref "/unit/configuration.md#configuration-return">}}) + to enable HTTP redirects. +- **format** in the + [access log]({{< relref "/unit/configuration.md#custom-log-format">}}) + to customize Unit's log output. +- **if** in the + [access log]({{< relref "/unit/configuration.md#conditional-access-log-1">}}) + to dynamically turn Unit's logging on and off. + +As its JavaScript engine, Unit uses the `njs` library, +shipped with the [official packages]({{< relref "/unit/installation.md#installation-precomp-pkgs" >}}) +or +[built from source]({{< relref "/unit/installation.md#installation-source" >}}). + +{{< warning >}} +Unit 1.32.0 and later require [njs 0.8.2](https://nginx.org/en/docs/njs/changes.html). +{{< /warning >}} + +Some request properties are exposed as `njs` objects or scalars: + +{{}} + +| Name | Type | Description | +|-------------|--------|------------------------------------------------------------------------------------------------------------------| +| **args** | Object | Query string arguments; **Color=Blue** is **args.Color**; can be used with **Object.keys()**. | +| **cookies** | Object | Request cookies; an **authID** cookie is **cookies.authID**; can be used with **Object.keys()**. | +| **headers** | Object | Request header fields; **Accept** is **headers.Accept**, **Content-Encoding** is **headers['Content-Encoding']** (hyphen requires an array property accessor); can be used with **Object.keys()**. | +| **host** | Scalar | **Host** [header field](https://datatracker.ietf.org/doc/html/rfc7230#section-5.4), converted to lower case and normalized by removing the port number and the trailing period (if any). | +| **remoteAddr** | Scalar | Remote IP address of the request. | +| **uri** | Scalar | [Request target](https://datatracker.ietf.org/doc/html/rfc7230#section-5.3), [percent decoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) and normalized by removing the [query string](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4) and resolving [relative references](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2) ("." and "..", "//"). | +| **vars** | Object | Unit [variables]({{< relref "/unit/configuration.md#configuration-variables-native" >}}); vars.method is **$method**. | + +{{}} + + +Template literals are wrapped in backticks. To use a literal backtick in a string, +escape it: **\\\\\`** (escaping backslashes is a +[JSON requirement](https://www.json.org/json-en.html)). +The `njs` snippets should be enclosed in curly brackets: +**\${...}**. + +Next, you can upload and use custom JavaScript modules +with your configuration. Consider this **http.js** script that distinguishes requests +by their **Authorization** header field values: + +```javascript +var http = {} + +http.route = function(headers) { + var authorization = headers['Authorization']; + if (authorization) { + var user = atob(authorization.split(' ')[1]); + if (String(user) == 'user:password') { + return 'accept'; + } + + return 'forbidden'; + } + + return 'unauthorized'; +} + +export default http +``` + +To upload it to Unit's JavaScript module storage as **http**, run the following +command as root: + +```console +# curl -X PUT --data-binary @http.js --unix-socket /path/to/control.unit.sock \ # + http://localhost/js_modules/http # +``` + +Unit doesn't enable the uploaded modules by default, so add the module's name to **settings/js_module** running the following command as root: + +```console +curl -X PUT -d '"http"' # Module name to be enabled + /path/to/control.unit.sock \ # Path to the remote control socket + http://localhost/config/settings/js_module +``` + +{{< note >}} +Please note that the **js_module** option +can be a string or an array; choose the appropriate HTTP method. +{{< /note >}} + +Now, the **http.route()** function can be used with Unit-supplied header field values: + +```json +{ + "routes": { + "entry": [ + { + "action": { + "pass": "`routes/${http.route(headers)}`" + } + } + ], + + "unauthorized": [ + { + "action": { + "return": 401 + } + } + ], + + "forbidden": [ + { + "action": { + "return": 403 + } + } + ], + + "accept": [ + { + "action": { + "return": 204 + } + } + ] + } +} +``` + +## Examples {#njs-examples} + +This example adds simple routing logic that extracts the agent name from the +**User-Agent** header field to reject requests issued by `curl`: + +```json +"routes": { + "parse": [ + { + "action": { + "pass": "`routes/${ headers['User-Agent'].split('/')[0] == 'curl' ? 'reject' : 'default' }`" + } + } + ], + + "reject": [ + { + "action": { + "return": 400 + } + } + ], + + "default": [ + { + "action": { + "return": 204 + } + } + ] +} +``` + +This example uses a series of transformations to log the request's date, IP, URI, +and all its headers: + +```json +{ + "path": "/var/log/unit/access_kv.log", + "format": "`@timestamp=${new Date().toISOString()} ip=${remoteAddr} uri=${uri} ${Object.keys(headers).map(k => 'req.' + k + '=\"' + headers[k] + '\"').join(' ')}\n`" +} +``` + +The next example will add the **Cache-Control** Header based on the HTTP Request method: + +```json +{ + "action": { + "pass": "applications/my_app", + "response_headers": { + "Cache-Control": "`${vars.method.startsWith('P') ? 'no-cache' : 'max-age=3600'}`" + } + } +} +``` + +For further reference, +see the [njs documentation](https://nginx.org/en/docs/njs/). diff --git a/content/unit/statusapi.md b/content/unit/statusapi.md new file mode 100644 index 000000000..ec4877474 --- /dev/null +++ b/content/unit/statusapi.md @@ -0,0 +1,213 @@ +--- +title: Status API +weight: 900 +toc: true +--- + + + +Unit collects information about the loaded language models, as well as +instance- and app-wide metrics, and makes them available via the **GET**-only +**/status** section of the [control API]({{< relref "/unit/controlapi.md" >}}): + +{{}} + +| Option | Description | +|--------------|-----------------------------------------------------| +| **modules** | Object; lists currently loaded language modules. | +| **connections** | Object; lists per-instance connection statistics. | +| **requests** | Object; lists per-instance request statistics. | +| **applications** | Object; each option item lists per-app process and request statistics. | + +{{}} + +Example: + +```json +{ + "modules": { + "python": [ + { + "version": "3.12.3", + "lib": "/opt/unit/modules/python.unit.so" + }, + { + "version": "3.8", + "lib": "/opt/unit/modules/python-3.8.unit.so" + } + ], + + "php": { + "version": "8.3.4", + "lib": "/opt/unit/modules/php.unit.so" + } + }, + + "connections": { + "accepted": 1067, + "active": 13, + "idle": 4, + "closed": 1050 + }, + + "requests": { + "total": 1307 + }, + + "applications": { + "wp": { + "processes": { + "running": 14, + "starting": 0, + "idle": 4 + }, + + "requests": { + "active": 10 + } + } + } +} +``` + +--- + +## Modules + +Each item in the **modules** object lists one of the currently loaded language +modules, the installed version (or versions) of the module, and the path to the +module file: + +{{}} + +| Option | Description | +|-----------|-------------| +| **name** | String; language module name. | +| **version** | String; language module version. If multiple versions are loaded, the list contains multiple items. | +| **lib** | String; path to the language module file. | + +{{}} + +--- + +## Connections + +The **connections** object offers the following Unit instance metrics: + +{{}} + +| Option | Description | +|----------|-------------| +| **accepted** | Integer; total accepted connections during the instance's lifetime. | +| **active** | Integer; current active connections for the instance. | +| **idle** | Integer; current idle connections for the instance. | +| **closed** | Integer; total closed connections during the instance's lifetime. | + +{{}} + +Example: + +```json +"connections": { + "accepted": 1067, + "active": 13, + "idle": 4, + "closed": 1050 +} +``` + +{{< note >}} +For details of instance connection management, +refer to +[Configuration Settings]({{< relref "/unit/configuration.md#configuration-stngs" >}}). +{{< /note >}} + +--- + +## Requests + +The **requests** object currently exposes a single instance-wide metric: + +{{}} + +| Option | Description | +|----------|-------------| +| **total** | Integer; total non-API requests during the instance's lifetime. | + +{{}} + +Example: + +```json +"requests": { + "total": 1307 +} +``` + +--- + +## Applications + +Each item in **applications** describes an app currently listed in the +**/config/applications** +[section]({{< relref "/unit/configuration.md#configuration-applications" >}}). + +{{}} + +| Option | Description | +|------------|-------------| +| **processes** | Object; lists per-app process statistics. | +| **requests** | Object; similar to **/status/requests**, but includes only the data for a specific app. | + +{{}} + +Example: + +```json +"applications": { + "wp": { + "processes": { + "running": 14, + "starting": 0, + "idle": 4 + }, + + "requests": { + "active": 10 + } + } +} +``` + +--- + +## Processes + +The **processes** object exposes the following per-app metrics: + +{{}} + +| Option | Description | +|------------|-------------| +| **running** | Integer; current running app processes. | +| **starting** | Integer; current starting app processes. | +| **idle** | Integer; current idle app processes. | + +{{}} + + +Example: + +```json +"processes": { + "running": 14, + "starting": 0, + "idle": 4 +} +``` + +{{< note >}} +For details of per-app process management, +refer to +[Process management]({{< relref "/unit/configuration.md#configuration-proc-mgmt" >}}). +{{< /note >}} diff --git a/content/unit/troubleshooting.md b/content/unit/troubleshooting.md new file mode 100644 index 000000000..3f185a864 --- /dev/null +++ b/content/unit/troubleshooting.md @@ -0,0 +1,324 @@ +--- +title: Troubleshooting +weight: 1100 +toc: true +--- + +{{< note >}} +The commands in this document starting with a hash (#) must be run as root or +with superuser privileges. +{{< /note >}} + +## Logging {#troubleshooting-log} + +Unit maintains a single general-purpose log, a system-wide log for runtime messaging, + usually found at `/var/log/unit.log`, for diagnostics and troubleshooting +(not to be confused with the [access log]({{< relref "/unit/configuration#configuration-access-log" >}})). +To find out its default location in your installation: + +```console +$ unitd -h + + unit options: + ... + --log FILE set log filename + default: "/path/to/unit.log" +``` + +The **--log** option overrides the default value; if Unit is already running, +check whether this option is set: + +```console +$ ps ax | grep unitd + ... + unit: main v1.34.1 [/path/to/unitd ... --log /path/to/unit.log ...] +``` + +If Unit isn't running, see its system startup scripts or configuration files +to check if **--log** is set, and how. + +Available log levels: + +- **[alert]**: Non-fatal errors such as app exceptions or misconfigurations. +- **[error]**: Serious errors such as invalid ports or addresses. +- **[warn]**: Recoverable issues such as **umount2(2)** failures. +- **[notice]**: Self-diagnostic and router events. +- **[info]**: General-purpose reporting. +- **[debug]**: Debug events. + +{{< note >}} +Mind that our Docker images forward their log output to the +[Docker log collector](https://docs.docker.com/config/containers/logging/) +instead of a file. +{{< /note >}} + +### Router events {#troubleshooting-router-log} + +The **log_route** option in Unit's +[settings]({{< relref "/unit/configuration#configuration-stngs" >}}) +allows recording +[routing choices]({{< relref "/unit/configuration#configuration-routes-matching" >}}) +in the general-purpose log: + +{{}} + +| Event | Log Level | Description | +|----------------------|----------------|---------------------------------------------------------------------------------------------------| +| HTTP request line | **[notice]** | Incoming [request line](https://datatracker.ietf.org/doc/html/rfc9112#section-3). | +| URI rewritten | **[notice]** | The request URI is updated. | +| Route step selected | **[notice]** | The route step is selected to serve the request. | +| Fallback taken | **[notice]** | A **fallback** action is taken after the step is selected. | + +{{}} + + +Sample router logging output may look like this: + +```none +[notice] 8308#8339 *16 http request line "GET / HTTP/1.1" +[info] 8308#8339 *16 "routes/0" discarded +[info] 8308#8339 *16 "routes/1" discarded +[notice] 8308#8339 *16 "routes/2" selected +[notice] 8308#8339 *16 URI rewritten to "/backend/" +[notice] 8308#8339 *16 "fallback" taken +``` + +It lists specific steps and actions (such as **routes/2**) that can be queried via +the [control API]({{< relref "/unit/controlapi.md" >}}) for details: + +```console +curl --unix-socket /path/to/control.unit.sock http://localhost/config/routes/2 # The step listed in the log +``` + +### Debug events {#troubleshooting-dbg-log} + +Unit's log can be set to record **[debug]**-level events; the steps to enable this +mode vary by install method. + +{{< warning >}} +Debug log is meant for developers; it grows rapidly, so enable it only for +detailed reports and inspection. +{{< /warning >}} + +{{< tabs name="debug-log" >}} +{{% tab name="Installing From Our Repos" %}} + +Our [repositories]({{< relref "/unit/installation.md#installation-precomp-packages" >}}) +provide a debug version of `unitd` called `unitd-debug` within the `unit` package: + +```console +# unitd-debug +``` + +{{% /tab %}} +{{% tab name="Running From Docker Images" %}} + +To enable debug-level logging when using our +[Docker iamges]({{< relref "/unit/installation.md#installation-docker" >}}): + +```console +$ docker run -d unit:{{< param "unitversion" >}}-minimal unitd-debug --no-daemon \ + --control unix:/var/run/control.unit.sock +``` + +Another option is adding a new layer in a Dockerfile: + +```dockerfile + +FROM unit:{{< param "unitversion" >}}-minimal + +CMD ["unitd-debug","--no-daemon","--control","unix:/var/run/control.unit.sock"] +``` + +The **CMD** instruction above replaces the default `unitd` executable +with its debug version. + +{{% /tab %}} +{{% tab name="Building From Source" %}} + +To enable debug-level logging when +[installing from source]({{< relref "/unit/installation.md#source" >}}), +use the **--debug** option: + +```console +$ ./configure --debug +``` + +Then recompile and reinstall Unit and your +[language modules]({{< relref "/unit/howto/source.md#source-modules" >}}). +{{% /tab %}} +{{< /tabs >}} + + +## Core dumps {#troubleshooting-core-dumps} + +Core dumps help us investigate crashes; attach them when +[reporting an issue]({{< relref "/unit/troubleshooting.md#troubleshooting-support" >}}). +For builds from +[our repositories]({{< relref "/unit/installation.md#installation-precomp-packages" >}}), +we maintain debug symbols in special packages; they have the original packages' +names with the **-dbg** suffix appended, such as **unit-dbg**. + +{{< note >}} +This section assumes you're running Unit as **root** (recommended). +{{< /note >}} + +{{< tabs name="core-dumps" >}} +{{% tab name="Linux: systemd" %}} + +To enable saving core dumps while running Unit as a `systemd` service +(for example, with +[packaged installations]({{< relref "/unit/installation.md#installation-precomp-packages" >}})), +adjust the [service settings](https://www.freedesktop.org/software/systemd/man/systemd.exec.html) +in **/lib/systemd/system/unit.service**: + +```ini +[Service] +... +LimitCORE=infinity +LimitNOFILE=65535 +``` + +Alternatively, update the +[global settings](https://www.freedesktop.org/software/systemd/man/systemd.directives.html) +in **/etc/systemd/system.conf**: + +```ini +[Manager] +... +DefaultLimitCORE=infinity +DefaultLimitNOFILE=65535 +``` + +Next, reload the service configuration and restart Unit to reproduce the crash +condition: + +```console +# systemctl daemon-reload +``` + +```console +# systemczl restart unit.service +``` + +After a crash, locate the core dump file: + +```console +# coredumpctl -1 # optional + + TIME PID UID GID SIG COREFILE EXE + Mon 2020-07-27 11:05:40 GMT 1157 0 0 11 present /usr/sbin/unitd +``` + +```console +# ls -al /var/lib/systemd/coredump/ # default, see also /etc/systemd/coredump.conf and /etc/systemd/coredump.conf.d/*.conf + + ... + -rw-r----- 1 root root 177662 Jul 27 11:05 core.unitd.0.6135489c850b4fb4a74795ebbc1e382a.1157.1590577472000000.lz4 +``` + +{{% /tab %}} +{{% tab name="Linux: Manual Setup" %}} + +Check the +[core dump settings](https://www.man7.org/linux/man-pages/man5/limits.conf.5.html) +in **/etc/security/limits.conf**, adjusting them if necessary: + +```none +root soft core 0 # disables core dumps by default +root hard core unlimited # enables raising the size limit +``` + +Next, raise the core dump size limit with +[ulimit](https://www.man7.org/linux/man-pages/man1/bash.1.html#SHELL_BUILTIN_COMMANDS), +then restart Unit to reproduce the crash condition: + +```console +# ulimit -c unlimited +``` + +```console +# cd /path/to/unit/ # Unit's installation directory +``` + +```console +# sbin/unitd # or sbin/unitd-debug +``` + +After a crash, locate the core dump file: + +```console +# ls -al /path/to/unit/working/directory/ # default location, see /proc/sys/kernel/core_pattern + + ... + -rw-r----- 1 root root 177662 Jul 27 11:05 core.1157 +``` + +{{% /tab %}} +{{% tab name="FreeBSD" %}} + +Check the +[core dump settings](https://www.freebsd.org/cgi/man.cgi?sysctl.conf(5)) +in **/etc/sysctl.conf**, adjusting them if necessary: + +```ini +kern.coredump=1 +# must be set to 1 +kern.corefile=/path/to/core/files/%N.core +# must provide a valid pathname +``` + +Alternatively, update the settings in runtime: + +```console +# sysctl kern.coredump=1 +``` + +```console +# sysctl kern.corefile=/path/to/core/files/%N.core +``` + +Next, restart Unit to reproduce the crash condition. +If Unit is installed as a service: + +```console +# service unitd restart +``` + + +If it's installed manually: + +```console +# cd /path/to/unit/ # Unit's installation directory +``` + +```console +# sbin/unitd +``` + +After a crash, locate the core dump file: + +```console +# ls -al /path/to/core/files/ # core dump directory + + ... + -rw------- 1 root root 9912320 Jul 27 11:05 unitd.core +``` + +{{% /tab %}} +{{< /tabs >}} + +## Getting support {#troubleshooting-support} + +{{}} + +| Support \| Channel | Details | +|----------------------|---------| +| **GitHub** | Visit our [repo](https://github.com/nginx/unit) to submit issues, suggest features, ask questions, or see the roadmap. | +| **Mailing lists** | To post questions to [unit@nginx.org](mailto:unit@nginx.org) and get notifications, including release news, email [unit-subscribe@nginx.org](mailto:unit-subscribe@nginx.org) or sign up [here](https://mailman.nginx.org/mailman/listinfo/unit).

To receive all OSS release announcements from NGINX, join the general mailing list [here](https://mailman.nginx.org/mailman/listinfo/nginx-announce). | +| **Security alerts** | Please report security issues to [security-alert@nginx.org](mailto:security-alert@nginx.org), specifically mentioning NGINX Unit in the subject and following the [CVSS v3.1](https://www.first.org/cvss/v3.1/specification-document) specification. | + +{{
}} + + +In addition, we offer [commercial support](https://my.f5.com/manage/s/article/K000140156/). diff --git a/content/unit/unitctl.md b/content/unit/unitctl.md new file mode 100644 index 000000000..ea4a0e73e --- /dev/null +++ b/content/unit/unitctl.md @@ -0,0 +1,345 @@ +--- +title: CLI (unitctl) +weight: 1000 +toc: true +--- + +{{< note >}} +**unitctl** is currently being provided as a "Technical Preview". We welcome +feedback and suggestions for this early access version. It is provided to test +its features and should not be used in production environments. +{{< /note >}} + +Unit provides a [Rust SDK](https://github.com/nginx/unit/tree/master/tools/unitctl) +to interact with its [control API]({{< relref "/unit/controlapi.md" >}}), and a command line +interface (unitctl) that exposes the functionality provided by the SDK. + +This CLI is a multi-purpose tool that allows you to deploy, manage, and configure +Unit in your environment. + +## Download binaries + +Unitctl binaries are available for Linux (ARM64 and X64) and macOS systems. + +Download the latest binaries from the [Unit GitHub releases page](https://github.com/nginx/unit/releases). + +## Build from source + +To build unitctl from source, follow the instructions in the [unitctl repository](https://github.com/nginx/unit/tree/master/tools/unitctl). + +## Using unitctl + +The unitctl CLI offers several commands to interact with Unit. Here are the available commands: + +{{}} + +| Command | Description | +|---------------|-----------------------------------------------------------------------------| +| **instances** | List all running Unit processes | +| **apps** | List and restart active applications | +| **edit** | Open the current Unit configuration in the default system editor | +| **export** | Export the current Unit configuration (excluding certificates) to a tarball | +| **import** | Import Unit configuration from a directory | +| **execute** | Send a raw JSON payload to Unit | +| **status** | Get the current status of Unit | +| **listeners** | List all active listeners | +| **help** | Display help information for commands and options | + +{{}} + + +There are also a number of options that you can use with the unitctl CLI: + +{{}} + +| Option | Description | +|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| **-s, --control-socket-address ** | Specify a path (e.g., unix:/var/run/unit/control.sock), TCP address with port (e.g., 127.0.0.1:80), or URL for Unit's control socket | +| **-w, --wait-timeout-seconds ** | Specify the timeout in seconds for the control socket to become available | +| **-t, --wait-max-tries ** | Specify the maximum number of tries to connect to the control socket when waiting (default: 3) | +| **-h, --help** | Display help information for commands and options | +| **-v, --version** | Display the version of the unitctl CLI | + +{{}} + + +### List and create instances of Unit + +The **instances** command lets you list all running Unit processes and +deploy new instances of Unit. + +The **instances** command has the following option: + +{{}} + +| Option | Description | +|----------|--------------------------------------| +| **new** | Deploy a new instance of Unit | + +{{}} + + +Running unitcl with the **instances** command shows output similar to this: + +```console +$ unitctl instances +No socket path provided - attempting to detect from running instance +unitd instance [pid: 79489, version: 1.32.0]: + Executable: /opt/unit/sbin/unitd + API control unix socket: unix:/opt/unit/control.unit.sock + Child processes ids: 79489, 79489 + Runtime flags: --no-daemon + Configure options: --prefix=/opt/unit --user=myUser --group=myGroup --openssl +``` + +You can use the **new** option with three arguments to deploy a new instance of Unit: + +1. **Control API path**: A file path for a Unix socket or a TCP address with port. + + - If you specify a directory, the Unit container will mount it to **/var/run** internally. + The control socket and pid file are accessible from the host. Example: **/tmp/2**. + - If you specify a TCP address, the Unit container will listen on this + address and port. Example: **127.0.0.1:7171**. + +2. **Application path**. The Unit container will mount this path in read-only mode + to **/www** internally. This setup allows you to configure the Unit + container to expose an application stored on the host. Example: **\$(pwd)**. + +3. **Image tag**: Unitctl will deploy this image, enabling you use custom + images. For example: **unit:wasm**. + +```console +$ unitctl instances new /tmp/2 $(pwd) 'unit:wasm' +Pulling and starting a container from unit:wasm +Will mount /tmp/2 to /var/run for socket access +Will READ ONLY mount /home/user/unitctl to /www for application access +Note: Container will be on host network +``` + +After the deployment is complete, you will have one Unit container running on the +host network. + +### List and restart running apps + +The **apps** command lets you list and restart active applications. + +#### Options + +The **apps** command has the following options: + +{{}} + +| Option | Description | +|------------------------|-------------------------------------| +| **list** | List all active applications | +| **restart ** | Restart the specified application | + +{{}} + + +To list active applications, run: + +```console +$ unitctl apps list +{ + "wasm": { + "type": "wasm-wasi-component", + "component": "/www/wasmapp-proxy-component.wasm" + } +} +``` + +To restart an application, run: + +```console +$ unitctl apps restart wasm +{ + "success": "Ok" +} +``` + +{{< note >}} +This command supports operating on multiple instances of Unit at once. To do +this, use the **-s** option multiple times with different values: + +```console +$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock app list +``` +{{< /note >}} + +### Fetch active listeners + +Unitctl can query a given control API to fetch all configured listeners. + +To list all active listeners, run: + +```console +$ unitctl listeners +No socket path provided - attempting to detect from running instance +{ + "127.0.0.1:8080": { + "pass": "routes" + } +} +``` + +{{< note >}} +This command supports operating on multiple instances of Unit at once. To do +this, use the **-s** option multiple times with different values: + +```console +$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock listeners +``` +{{< /note >}} + +### Check the status of Unit + +Unitctl can query the control API to provide the **status** of the running Unit +daemon. + +To get the current status of the Unit, run: + +```console +$ unitctl status -t yaml +No socket path provided - attempting to detect from running instance +connections: + accepted: 0 + active: 0 + idle: 0 + closed: 0 +requests: + total: 0 +applications: {} +``` + +{{< note >}} +This command supports operating on multiple instances of Unit at once. To do +this, use the **-s** option multiple times with different values: + +```console +$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock status +``` +{{< /note >}} + +### Send configuration payloads to Unit + +With the **execute** command, Unitctl can accept custom request payloads and +query specified API endpoints with them. Use the **-f** flag to pass the request +payload as a filename or **-** to denote stdin, as shown in the example below. + +```console +$ echo '{ + "listeners": { + "127.0.0.1:8080": { + "pass": "routes" + } + }, + + "routes": [ + { + "action": { + "share": "/www/data$uri" + } + } + ] +}' | unitctl execute --http-method PUT --path /config -f - +{ +"success": "Reconfiguration done." +} +``` + +{{< note >}} +This command supports operating on multiple instances of Unit at once. To do +this, use the **-s** option multiple times with different values: + +```console +$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock execute ... +``` +{{< /note >}} + +### Edit current configuration + +Unitctl can fetch the configuration from a running instance of Unit and load it +in a preconfigured editor on your command line using the **edit** command. + +Unitctl tries to use the editor configured with the **EDITOR** environment +variable, but defaults to vim, emacs, nano, vi, or pico if **EDITOR** is not set. + +To edit the current configuration, run: + +```console +$ unitctl edit +``` + +The configuration loads into the editor, allowing you to make any necessary +changes. Once you save and close the editor, you see the following output: + +```console +{ +"success": "Reconfiguration done." +} +``` + +{{< note >}} +This command does not support operating on multiple instances of Unit at once. +{{< /note >}} + +### Importing the configuration from a folder + +The **import** command lets Unitctl read configuration files, certificates, and +NJS modules from a directory. Unitctl then converts these files into a payload +to reconfigure a Unit daemon. + +To export the configuration, run: + +```console +$ unitctl import /opt/unit/config +Imported /opt/unit/config/certificates/snake.pem -> /certificates/snake.pem +Imported /opt/unit/config/hello.js -> /js_modules/hello.js +Imported /opt/unit/config/put.json -> /config +Imported 3 files +``` + +### Exporting the configuration from Unit + +The **export** command queries a control API to fetch the running configuration +and NJS modules from a Unit process. The output does not include the currently +stored certificate bundles due to a technical limitation. The output is saved +as a tarball with the filename specified by the **-f** argument. You can also +use standard output with **-f -**, as shown in the examples below: + +```console +$ unitctl export -f config.tar +``` + +```console +$ unitctl export -f - +``` + +```console +$ unitctl export -f - | tar xf - config.json +``` + +```console +$ unitctl export -f - > config.tar +``` + +{{< warning >}} +The exported configuration omits certificates. +{{< /warning >}} + +{{< note >}} +This command does not support operating on multiple instances of Unit at once. +{{< /note >}} + +### Wait for a socket to be available + +All commands support waiting for Unix sockets to become available: + +```console +$ unitctl --wait-timeout-seconds=3 --wait-max-tries=4 import /opt/unit/config` +Waiting for 3s control socket to be available try 2/4... +Waiting for 3s control socket to be available try 3/4... +Waiting for 3s control socket to be available try 4/4... +Timeout waiting for unit to start has been exceeded +``` diff --git a/static/unit/downloads/Dockerfile.go.txt b/static/unit/downloads/Dockerfile.go.txt new file mode 100644 index 000000000..d65890139 --- /dev/null +++ b/static/unit/downloads/Dockerfile.go.txt @@ -0,0 +1,41 @@ +FROM unit:1.31.1-go1.21 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-go1.21 + +# port used by the listener in config.json +EXPOSE 8080 + +# application setup +RUN mkdir /www/ && go env -w GO111MODULE=auto && echo ' \ + package main; \ + import ( \ + "io"; \ + "net/http"; \ + "unit.nginx.org/go" \ + ); \ + func main() { \ + http.HandleFunc("/",func (w http.ResponseWriter, r *http.Request) {\ + io.WriteString(w, "Hello, Go on Unit!") \ + }); \ + unit.ListenAndServe(":8080", nil) \ + } \ + ' > /www/app.go \ + && go build -o /www/app /www/app.go \ +# final cleanup + && rm /www/app.go + +# prepare the app config for Unit +RUN echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/go_app" \ + } \ + }, \ + "applications": { \ + "go_app": { \ + "type": "external", \ + "working_directory": "/www/", \ + "executable": "/www/app" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json \ diff --git a/static/unit/downloads/Dockerfile.java.txt b/static/unit/downloads/Dockerfile.java.txt new file mode 100644 index 000000000..e11c146d0 --- /dev/null +++ b/static/unit/downloads/Dockerfile.java.txt @@ -0,0 +1,26 @@ +FROM unit:1.31.1-jsc11 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-jsc11 + +# port used by the listener in config.json +EXPOSE 8080 + +# application setup +RUN mkdir /www/ && echo \ + '<%@page language="java" contentType="text/plain"%> \ + <%="Hello, JSP on Unit!"%>' \ + > /www/index.jsp \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/java_app" \ + } \ + }, \ + "applications": { \ + "java_app": { \ + "type": "java", \ + "webapp": "/www/" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/Dockerfile.nodejs.txt b/static/unit/downloads/Dockerfile.nodejs.txt new file mode 100644 index 000000000..2893f5580 --- /dev/null +++ b/static/unit/downloads/Dockerfile.nodejs.txt @@ -0,0 +1,33 @@ +# keep our base image as small as possible +FROM unit:1.31.1-node20 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-node20 + +# port used by the listener in config.json +EXPOSE 8080 + +#application setup +RUN mkdir /www/ && echo '#!/usr/bin/env node \n\ + require("unit-http").createServer(function (req, res) { \ + res.writeHead(200, {"Content-Type": "text/plain"}); \ + res.end("Hello, Node.js on Unit!") \ + }).listen() \ + ' > /www/app.js \ +# make app.js executable; link unit-http locally + && cd /www && chmod +x app.js \ + && npm link unit-http \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/node_app" \ + } \ + }, \ + "applications": { \ + "node_app": { \ + "type": "external", \ + "working_directory": "/www/", \ + "executable": "app.js" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/Dockerfile.perl.txt b/static/unit/downloads/Dockerfile.perl.txt new file mode 100644 index 000000000..74436b6ea --- /dev/null +++ b/static/unit/downloads/Dockerfile.perl.txt @@ -0,0 +1,32 @@ +FROM unit:1.31.1-perl5.38 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-perl5.38 + +# port used by the listener in config.json +EXPOSE 8080 + +#application setup +RUN mkdir /www/ && echo ' \ + my $app = sub { \ + return [ \ + "200", \ + [ "Content-Type" => "text/plain" ], \ + [ "Hello, Perl on Unit!" ], \ + ] \ + }; \ + ' > /www/app.psgi \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/perl_app" \ + } \ + }, \ + "applications": { \ + "perl_app": { \ + "type": "perl", \ + "working_directory": "/www/", \ + "script": "/www/app.psgi" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/Dockerfile.php.txt b/static/unit/downloads/Dockerfile.php.txt new file mode 100644 index 000000000..21f5c2c3a --- /dev/null +++ b/static/unit/downloads/Dockerfile.php.txt @@ -0,0 +1,24 @@ +FROM unit:1.31.1-php8.2 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-php8.2 + +# port used by the listener in config.json +EXPOSE 8080 + +# application setup +RUN mkdir /www/ \ + && echo '' > /www/index.php \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/php_app" \ + } \ + }, \ + "applications": { \ + "php_app": { \ + "type": "php", \ + "root": "/www/" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/Dockerfile.python.txt b/static/unit/downloads/Dockerfile.python.txt new file mode 100644 index 000000000..570963f2a --- /dev/null +++ b/static/unit/downloads/Dockerfile.python.txt @@ -0,0 +1,28 @@ +FROM unit:1.31.1-python3.11 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-python3.11 + +# port used by the listener in config.json +EXPOSE 8080 + +# application setup +RUN mkdir /www/ && echo ' \n\ +def application(environ, start_response): \n\ + start_response("200 OK", [("Content-Type", "text/plain")]) \n\ + return (b"Hello, Python on Unit!") \ + ' > /www/wsgi.py \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/python_app" \ + } \ + }, \ + "applications": { \ + "python_app": { \ + "type": "python", \ + "path": "/www/", \ + "module": "wsgi" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/Dockerfile.ruby.txt b/static/unit/downloads/Dockerfile.ruby.txt new file mode 100644 index 000000000..6f4627579 --- /dev/null +++ b/static/unit/downloads/Dockerfile.ruby.txt @@ -0,0 +1,31 @@ +FROM unit:1.31.1-ruby3.2 +# Alternatively, you can download the base image from AWS ECR: +# FROM public.ecr.aws/nginx/unit:1.31.1-ruby3.2 + +# port used by the listener in config.json +EXPOSE 8080 + +# application setup +RUN mkdir /www/ && echo ' \ + app = Proc.new do |env| \ + ["200", { \ + "Content-Type" => "text/plain", \ + }, ["Hello, Ruby on Unit!"]] \ + end; \ + run app \ + ' > /www/config.ru \ +# prepare the app config for Unit + && echo '{ \ + "listeners": { \ + "*:8080": { \ + "pass": "applications/ruby_app" \ + } \ + }, \ + "applications": { \ + "ruby_app": { \ + "type": "ruby", \ + "working_directory": "/www/", \ + "script": "config.ru" \ + } \ + } \ + }' > /docker-entrypoint.d/config.json diff --git a/static/unit/downloads/grafana.patch b/static/unit/downloads/grafana.patch new file mode 100644 index 000000000..69882ad12 --- /dev/null +++ b/static/unit/downloads/grafana.patch @@ -0,0 +1,58 @@ +diff -r e23e42b2ca07 conf/defaults.ini +--- a/conf/defaults.ini Sat May 09 07:45:29 2020 +0000 ++++ b/conf/defaults.ini Sat May 09 07:47:01 2020 +0000 +@@ -29,7 +29,7 @@ + #################################### Server ############################## + [server] + # Protocol (http, https, h2, socket) +-protocol = http ++protocol = unit + + # The ip address to bind to, empty will bind to all interfaces + http_addr = +diff -r e23e42b2ca07 pkg/api/http_server.go +--- a/pkg/api/http_server.go Sat May 09 07:45:29 2020 +0000 ++++ b/pkg/api/http_server.go Sat May 09 07:47:01 2020 +0000 +@@ -7,6 +7,7 @@ + "github.com/grafana/grafana/pkg/services/search" + "net" + "net/http" ++ "unit.nginx.org/go" + "os" + "path" + "sync" +@@ -116,6 +117,13 @@ + if err != nil { + return errutil.Wrapf(err, "failed to open listener for socket %s", setting.SocketPath) + } ++ case setting.UNIT: ++ var err error ++ err = unit.ListenAndServe(hs.httpSrv.Addr, hs.macaron) ++ if err == http.ErrServerClosed { ++ hs.log.Debug("server was shutdown gracefully") ++ return nil ++ } + + // Make socket writable by group + if err := os.Chmod(setting.SocketPath, 0660); err != nil { +diff -r e23e42b2ca07 pkg/setting/setting.go +--- a/pkg/setting/setting.go Sat May 09 07:45:29 2020 +0000 ++++ b/pkg/setting/setting.go Sat May 09 07:47:01 2020 +0000 +@@ -30,6 +30,7 @@ + HTTP Scheme = "http" + HTTPS Scheme = "https" + HTTP2 Scheme = "h2" ++ UNIT Scheme = "unit" + SOCKET Scheme = "socket" + DEFAULT_HTTP_ADDR string = "0.0.0.0" + REDACTED_PASSWORD string = "*********" +@@ -679,6 +680,9 @@ + Protocol = SOCKET + SocketPath = server.Key("socket").String() + } ++ if protocolStr == "unit" { ++ Protocol = UNIT ++ } + + Domain, err = valueAsString(server, "domain", "localhost") + if err != nil { diff --git a/static/unit/downloads/unit-openapi.Dockerfile b/static/unit/downloads/unit-openapi.Dockerfile new file mode 100644 index 000000000..bac6188fa --- /dev/null +++ b/static/unit/downloads/unit-openapi.Dockerfile @@ -0,0 +1,157 @@ +FROM unit:node + +RUN mkdir -p /www/html && echo ' \ + \ + \ + \ + \ + \ + \ + \ +' > /www/html/index.html + +RUN echo '{ \ + "listeners": { \ + "*:8765": { \ + "pass": "routes/share" \ + }, \ + "*:8080": { \ + "pass": "applications/node-proxy" \ + } \ + }, \ + "routes": { \ + "share": [ \ + { \ + "action": { \ + "share": "/www/html/$uri" \ + } \ + } \ + ] \ + }, \ + "applications": { \ + "node-proxy": { \ + "type": "external", \ + "working_directory": "/www/", \ + "executable": "/usr/bin/env", \ + "arguments": [ \ + "node", \ + "--loader", \ + "unit-http/loader.mjs", \ + "--require", \ + "unit-http/loader", \ + "server.js" \ + ] \ + } \ + } \ +}' > /docker-entrypoint.d/config.json + +RUN echo 'var http = require("http"); \ +var httpProxy = require("http-proxy"); \ + \ +var proxy = httpProxy.createProxyServer({}); \ +var sendError = function(res, err) { \ + return res.status(500).send({ \ + error: err, \ + message: "An error occured in the proxy" \ + }); \ +}; \ + \ +proxy.on("error", function (err, req, res) { \ + sendError(res, err); \ +}); \ + \ +var enableCors = function(req, res) { \ + if (req.headers["access-control-request-method"]) { \ + res.setHeader("access-control-allow-methods", req.headers["access-control-request-method"]); \ + } \ + \ + if (req.headers["access-control-request-headers"]) { \ + res.setHeader("access-control-allow-headers", req.headers["access-control-request-headers"]); \ + } \ + \ + if (req.headers.origin) { \ + res.setHeader("access-control-allow-origin", req.headers.origin); \ + res.setHeader("access-control-allow-credentials", "true"); \ + } \ +}; \ + \ +proxy.on("proxyRes", function(proxyRes, req, res) { \ + enableCors(req, res); \ +}); \ + \ +proxy.on("error", function (err, req, res) { \ + sendError(res, err); \ +}); \ + \ +var enableCors = function(req, res) { \ + if (req.headers["access-control-request-method"]) { \ + res.setHeader("access-control-allow-methods", req.headers["access-control-request-method"]); \ + } \ + \ + if (req.headers["access-control-request-headers"]) { \ + res.setHeader("access-control-allow-headers", req.headers["access-control-request-headers"]); \ + } \ + \ + if (req.headers.origin) { \ + res.setHeader("access-control-allow-origin", req.headers.origin); \ + res.setHeader("access-control-allow-credentials", "true"); \ + } \ +}; \ + \ +proxy.on("proxyRes", function(proxyRes, req, res) { \ + enableCors(req, res); \ +}); \ + \ +var server = http.createServer(function(req, res) { \ + if (req.method === "OPTIONS") { \ + enableCors(req, res); \ + res.writeHead(200); \ + res.end(); \ + return; \ + } \ + \ + proxy.web(req, res, { \ + target: "http://127.0.0.1:9999", \ + secure: true, \ + changeOrigin: true \ + }, function(err) { \ + sendError(res, err); \ + }); \ +}); \ + \ +server.listen(8080);' > /www/server.js + +RUN echo '{ \ + "name": "oas", \ + "version": "1.0.0", \ + "description": "", \ + "main": "server.js", \ + "scripts": { \ + "test": "echo \"Error: no test specified\" && exit 1" \ + }, \ + "author": "", \ + "license": "ISC", \ + "dependencies": { \ + "http-proxy": "^1.18.1", \ + "unit-http": "^1.30.0" \ + } \ +}' > /www/package.json + +WORKDIR /www/ +RUN npm install + +EXPOSE 8080 +EXPOSE 8765 + +CMD ["unitd","--no-daemon","--control","127.0.0.1:9999"] diff --git a/static/unit/images/apollo.png b/static/unit/images/apollo.png new file mode 100644 index 000000000..a4dced058 Binary files /dev/null and b/static/unit/images/apollo.png differ diff --git a/static/unit/images/bugzilla.png b/static/unit/images/bugzilla.png new file mode 100644 index 000000000..4f593f09c Binary files /dev/null and b/static/unit/images/bugzilla.png differ diff --git a/static/unit/images/cakephp.png b/static/unit/images/cakephp.png new file mode 100644 index 000000000..f8b0fe9ca Binary files /dev/null and b/static/unit/images/cakephp.png differ diff --git a/static/unit/images/catalyst.png b/static/unit/images/catalyst.png new file mode 100644 index 000000000..35040bf83 Binary files /dev/null and b/static/unit/images/catalyst.png differ diff --git a/static/unit/images/codeigniter.png b/static/unit/images/codeigniter.png new file mode 100644 index 000000000..d1872a6ba Binary files /dev/null and b/static/unit/images/codeigniter.png differ diff --git a/static/unit/images/datasette.png b/static/unit/images/datasette.png new file mode 100644 index 000000000..4a9a1f7d4 Binary files /dev/null and b/static/unit/images/datasette.png differ diff --git a/static/unit/images/django.png b/static/unit/images/django.png new file mode 100644 index 000000000..7dc1ca65f Binary files /dev/null and b/static/unit/images/django.png differ diff --git a/static/unit/images/djangochannels.png b/static/unit/images/djangochannels.png new file mode 100644 index 000000000..80e69c5e6 Binary files /dev/null and b/static/unit/images/djangochannels.png differ diff --git a/static/unit/images/dokuwiki.png b/static/unit/images/dokuwiki.png new file mode 100644 index 000000000..7f4c4d3bc Binary files /dev/null and b/static/unit/images/dokuwiki.png differ diff --git a/static/unit/images/drupal.png b/static/unit/images/drupal.png new file mode 100644 index 000000000..178729062 Binary files /dev/null and b/static/unit/images/drupal.png differ diff --git a/static/unit/images/express.png b/static/unit/images/express.png new file mode 100644 index 000000000..2cbe0ba8b Binary files /dev/null and b/static/unit/images/express.png differ diff --git a/static/unit/images/fastapi.png b/static/unit/images/fastapi.png new file mode 100644 index 000000000..fa069878f Binary files /dev/null and b/static/unit/images/fastapi.png differ diff --git a/static/unit/images/grafana.png b/static/unit/images/grafana.png new file mode 100644 index 000000000..a3b75d2a6 Binary files /dev/null and b/static/unit/images/grafana.png differ diff --git a/static/unit/images/hg.png b/static/unit/images/hg.png new file mode 100644 index 000000000..348b27132 Binary files /dev/null and b/static/unit/images/hg.png differ diff --git a/static/unit/images/jira.png b/static/unit/images/jira.png new file mode 100644 index 000000000..fcd2fe9e3 Binary files /dev/null and b/static/unit/images/jira.png differ diff --git a/static/unit/images/joomla.png b/static/unit/images/joomla.png new file mode 100644 index 000000000..5b0be0ea9 Binary files /dev/null and b/static/unit/images/joomla.png differ diff --git a/static/unit/images/laravel.png b/static/unit/images/laravel.png new file mode 100644 index 000000000..ac04377f5 Binary files /dev/null and b/static/unit/images/laravel.png differ diff --git a/static/unit/images/mailman.png b/static/unit/images/mailman.png new file mode 100644 index 000000000..6c30e970b Binary files /dev/null and b/static/unit/images/mailman.png differ diff --git a/static/unit/images/matomo.png b/static/unit/images/matomo.png new file mode 100644 index 000000000..627218ac3 Binary files /dev/null and b/static/unit/images/matomo.png differ diff --git a/static/unit/images/modx.png b/static/unit/images/modx.png new file mode 100644 index 000000000..2251ded81 Binary files /dev/null and b/static/unit/images/modx.png differ diff --git a/static/unit/images/moin.png b/static/unit/images/moin.png new file mode 100644 index 000000000..e98224fe5 Binary files /dev/null and b/static/unit/images/moin.png differ diff --git a/static/unit/images/mw_install.png b/static/unit/images/mw_install.png new file mode 100644 index 000000000..d383d540b Binary files /dev/null and b/static/unit/images/mw_install.png differ diff --git a/static/unit/images/mw_ready.png b/static/unit/images/mw_ready.png new file mode 100644 index 000000000..db703a7df Binary files /dev/null and b/static/unit/images/mw_ready.png differ diff --git a/static/unit/images/nextcloud.png b/static/unit/images/nextcloud.png new file mode 100644 index 000000000..8e4c04c33 Binary files /dev/null and b/static/unit/images/nextcloud.png differ diff --git a/static/unit/images/openapi.png b/static/unit/images/openapi.png new file mode 100644 index 000000000..faa79db9e Binary files /dev/null and b/static/unit/images/openapi.png differ diff --git a/static/unit/images/opengrok.png b/static/unit/images/opengrok.png new file mode 100644 index 000000000..d62fd8482 Binary files /dev/null and b/static/unit/images/opengrok.png differ diff --git a/static/unit/images/phpbb.png b/static/unit/images/phpbb.png new file mode 100644 index 000000000..97842b4d4 Binary files /dev/null and b/static/unit/images/phpbb.png differ diff --git a/static/unit/images/phpmyadmin.png b/static/unit/images/phpmyadmin.png new file mode 100644 index 000000000..74edf94e9 Binary files /dev/null and b/static/unit/images/phpmyadmin.png differ diff --git a/static/unit/images/plone.png b/static/unit/images/plone.png new file mode 100644 index 000000000..8fda88451 Binary files /dev/null and b/static/unit/images/plone.png differ diff --git a/static/unit/images/rails.png b/static/unit/images/rails.png new file mode 100644 index 000000000..f202a2c43 Binary files /dev/null and b/static/unit/images/rails.png differ diff --git a/static/unit/images/redmine.png b/static/unit/images/redmine.png new file mode 100644 index 000000000..bb76ff090 Binary files /dev/null and b/static/unit/images/redmine.png differ diff --git a/static/unit/images/reviewboard.png b/static/unit/images/reviewboard.png new file mode 100644 index 000000000..c9044cad1 Binary files /dev/null and b/static/unit/images/reviewboard.png differ diff --git a/static/unit/images/roundcube-setup.png b/static/unit/images/roundcube-setup.png new file mode 100644 index 000000000..5d1c8638f Binary files /dev/null and b/static/unit/images/roundcube-setup.png differ diff --git a/static/unit/images/roundcube.png b/static/unit/images/roundcube.png new file mode 100644 index 000000000..80cb6fece Binary files /dev/null and b/static/unit/images/roundcube.png differ diff --git a/static/unit/images/springboot.png b/static/unit/images/springboot.png new file mode 100644 index 000000000..a3d96ffa4 Binary files /dev/null and b/static/unit/images/springboot.png differ diff --git a/static/unit/images/symfony.png b/static/unit/images/symfony.png new file mode 100644 index 000000000..958877874 Binary files /dev/null and b/static/unit/images/symfony.png differ diff --git a/static/unit/images/trac.png b/static/unit/images/trac.png new file mode 100644 index 000000000..f473284f2 Binary files /dev/null and b/static/unit/images/trac.png differ diff --git a/static/unit/images/wordpress.png b/static/unit/images/wordpress.png new file mode 100644 index 000000000..90e7bb571 Binary files /dev/null and b/static/unit/images/wordpress.png differ diff --git a/static/unit/images/yii1.1.png b/static/unit/images/yii1.1.png new file mode 100644 index 000000000..6054cc237 Binary files /dev/null and b/static/unit/images/yii1.1.png differ diff --git a/static/unit/images/yii2.png b/static/unit/images/yii2.png new file mode 100644 index 000000000..4c9b43d34 Binary files /dev/null and b/static/unit/images/yii2.png differ