Usually, we’d start a blog post like this one rather light‑heartedly, but the mood of the times is undoubtedly somber, the reasons for that being too numerous to joke about. However, we hope you’re doing well – and again, we have some news to share.
For starters, we at the NGINX Unit team believe that the term “isolation” doesn’t necessarily deserve the bad rap it has gained recently around the globe, and our most recent release, NGINX Unit 1.18.0, has arrived with an accordingly themed major update.
isolation family of application settings, introduced in NGINX Unit 1.11.0, now includes a new
rootfs object. If the underlying OS allows it, you can use
rootfs to designate an arbitrary directory as the filesystem root.
An application configured this way is locked within the confines of the /var/app/sandbox/ directory, unable to access any files or directories outside of it. All path settings that you provide for the app need to account for that, as illustrated by the
home objects in the example above.
The potential applications for this capability, especially combined with other isolation features, are significant. Effectively, it enables you to configure and run apps as lightweight on‑demand containers, improving their security, isolating them from each other and the underlying OS, and enhancing the granularity of your infrastructure. That is exactly the goal of our containerization initiative, and isolation of the filesystem root is an important milestone in our endeavor.
While isolating your app, NGINX Unit conveniently ensures the appropriate language runtime is still available for all languages we support. Moreover, you can use
rootfs to maintain different versions of your app’s runtime (modules, libraries, and so on) and toggle between them seamlessly. An appropriately detailed hands‑on demonstration is beyond the scope of this blog post, so keep an eye out for an upcoming blog dedicated to the topic. Meanwhile, you can try this addition to see the benefits it can bring to your own infrastructure.
For further details, see our Process Isolation configuration guide.
The second innovation introduced in NGINX Unit 1.18.0 is specific to PHP. It aims to simplify the daily work of the PHP-on-Unit crowd who previously had to maintain rather cumbersome configurations for their beloved apps.
The NGINX Unit team initially noticed that more often than not PHP web apps require several modes of operation for the different ways that individual scripts within the same application can be run – and that’s not even mentioning the myriad existing ways to handle static content. Usually, the approach boils down to one of the following:
- A single script handles incoming requests for all URIs, routing them internally
- Each request is handled by the script explicitly named in the URI
NGINX Unit has included options for both approaches since the very introduction of PHP support. Nonetheless, the sheer number of possible combinations in real deployments was begging for a change. To give you a picture: almost every time a PHP application was set up in NGINX Unit, several application objects had to be defined, along with intricate routing rules that channeled the requests among them, as in this example:
Here, requests that explicitly name a PHP script are passed to the direct app, while all other requests are passed to the script_index app, which handles them by means of the multipurpose index.php script at the root of the app (pretty URIs are a common use case for the latter). Because the two apps are distinct, they must be separately configured in the
applications object, but you can readily see there are quite a few duplicated options.
Such redundancy makes sense if you wish to fine‑tune the settings for each section of your web application (section being loosely defined as an arbitrary set of PHP scripts and URIs that need to be configured as a single entity) or assign a dedicated process pool to each individual section of your app. However, NGINX Unit runs such sections as separate apps even when that is not your end goal, which can result in unwanted overhead: the app requires several processes to run (at least one per each section), but some of them remain idle most of the time (wasting CPU cycles and memory), while others may be constantly overloaded with a torrent of requests. There needs to be a way to share processes between the individual sections of an app.
Moreover, the one-app-per-section approach requires repeating the same setting values over and over, causing redundancy, unnecessary risk of misconfiguration, and lack of logical cohesion between the individual sections of the app that were represented as standalone entities. To update a redundant setting, multiple non‑atomic configuration updates (API calls) are necessary, which raises concerns about potential inconsistencies. All of these problems led us to introduce a new approach in version 1.18.0.
Now, instead of having to set up a single application object for each and every section of your app that handles its scripts in a customized way, you can use the new
targets option to make them all peacefully work together under the roof of a single app:
Each target within the app can have its own combination of the
script options, independently running its own scripts. However, all targets share such application‑wide settings as
processes. Keep in mind, however, that it’s not currently possible to manage resource distribution between individual targets within an app.
The new approach centralizes and error‑proofs application management while allowing you to flexibly control the many endpoints of any application with individual targets. Moreover, you can combine it with the older method if need be, grouping or decoupling your app configurations as you see fit.
NGINX Plus 1.18.0 implements URL encoding (also called percent encoding), which is used in URIs most often to represent characters outside the English‑language alphabet (such as characters with diacritical markings like the umlaut or tilde) and characters that can have a special meaning (such as the forward slash as the divider between elements in a URI) when they don’t have that meaning. You can use URL encoding to escape characters in the
uri options. There are two main use cases.
First, you can escape the forward‑slash character as
%2F in the argument to a
Second, you can create filters with the
arguments options that contain characters which have special meaning in NGINX Unit routing, or even target individual bytes:
uri filter expects a single literal asterisk (
*, URL‑encoded as
%2A), and the
arguments filter expects the percent sign (
%, URL‑encoded as
%25) as the key with a value consisting of an exclamation point (
!, URL‑encoded as
%21) followed by a diacritic UTF‑8 character such as Ö or Å: the byte value
%C3 usually signals a set of diacritic characters and is followed here by an arbitrary byte sequence (represented by the asterisk in the value definition).
These two sample queries illustrate the effect of the complete configuration:
$ curl -v 'http://localhost:80/*?%=!Ü' ... < HTTP/1.1 301 Moved Permanently < Location: http://fancyurls.example.com < Server: Unit/1.18.0 $ curl -v 'http://localhost:80/*?%=!ò' ... < HTTP/1.1 301 Moved Permanently < Location: http://fancyurls.example.com < Server: Unit/1.18.0
You can see that both requests are successfully redirected which means NGINX Unit has matched the asterisk and exclamation point literals and the diacritic characters as intended. Effectively, this means you can single out arbitrary byte sequences and characters in different encodings.
Enhancements Introduced in NGINX Unit 1.17.0
In addition to the headliner changes we made in NGINX Unit 1.18.0, version 1.17.0 introduced some notable but smaller‑scale updates.
Instant Responses and Redirects
NGINX Unit 1.17.0 introduced support for instant responses and redirections during routing. The new route
return allows you to reply to a request with an arbitrary HTTP response status code and also provide a redirect
location when that is required by the status code’s semantics. Here we define a permanent redirect for the /legacy/ URI:
Fractional Server Weights in Upstreams
Version 1.17.0 also added support for fractional weights in the
upstreams object introduced in version 1.16.0. With the original integer weights, you often had to adjust the entire weighting scheme to change a single server’s share of requests. Consider this initial configuration, which divides requests evenly among three servers (each has the default weight of 1):
If you then want one server to get only half as many requests as the others (a distribution represented with integers as 2:2:1), you actually have to change the weight on the two servers whose relative proportion is staying the same, which is somewhat counterintuitive:
With fractional weights, you need to change the weight only on the server whose proportion is changing:
The difference between the two schemes is not great with just three servers, but fractional weights can significantly reduce the number of necessary changes when there are many servers.
This is a brief recap of the new features in versions 1.17.0 and 1.18.0. As mentioned above, very soon we’ll publish a dedicated post delving into the details of the newly introduced
rootfs feature: its technicalities and advantages, potential use cases, less-than-obvious risks, and unexpected side effects. Stay tuned!
Meanwhile, the NGINX Unit team continues to work on even more fabulous improvements while simultaneously perfecting what’s already there: our current roadmap includes plans to support configuration variables, add some HTTP goodness, and extend wildcard support in request matching patterns.
For a list of the changes and bug fixes in releases 1.17.0 and 1.18.0, see the NGINX Unit changelog.
NGINX Plus subscribers get support for NGINX Unit at no additional charge. Start a free 30‑day trial of NGINX Plus today.