Web Server Load Balancing with NGINX Plus

Welcome to the latest installment in our series of blogs about new features in NGINX Unit! Admittedly, it’s been a while since we provided an update, so the time has come to share the details about our latest versions – NGINX Unit 1.23.0 and 1.24.0  – and explore how they make the life of an NGINX Unit enthusiast a whole lot easier.

But first, some great news about our increasingly international team: two more developers, Oisín Canty and Zhidao Hong (洪志道), joined us this year, bringing with them lots of diverse expertise, some serious affection for NGINX Unit, and a major boost in overall productivity. Both have hit the ground running, coming up with a plethora of new features in mere months and participating in the design and development of the capabilities discussed below. Meanwhile, Andrei Suvorov, our next‑newest team member, has already proven his worth, providing essential developments to our latest releases. Speaking of which…

TLS: SNI and Configuration Commands

The first set of changes to NGINX Unit revolves around its implementation of the TLS protocol. Version 1.23.0 introduces support for the Server Name Indication (SNI) extension to TLS, providing a straightforward way to map certificates between the multiple sites and domains you serve from a single NGINX Unit deployment.

First, let’s briefly review how NGINX Unit has handled certificates since support for TLS was added in version 1.4.0: you bundle up certificate chains, upload them via the control API, and assign the bundles to listeners. Pretty neat, but the one-to-one mapping between bundles and listeners had its limitations. Now you can specify an array of bundles in a single listener:

Behind the scenes, NGINX Unit works its magic to make the most appropriate choice for each arriving request, using the common and alternative names of the certificates in each bundle. If the client specifies a server name, NGINX Unit responds with a certificate from the corresponding bundle. If the name matches multiple bundles, exact matches have priority over wildcards such as * If any ambiguity remains, NGINX Unit uses the first bundle on the list, which it also does when there’s no match or the request doesn’t include a server name at all.

Finally, those familiar with the NGINX ssl_conf_command and ssl_ciphers directives will probably rejoice that NGINX Unit now also enables you to supply a set of OpenSSL configuration commands per listener:

Static Content: MIME Filtering

Unit’s content‑handling abilities received a major boost in version 1.24.0. You can now use Unit’s support of built‑in and customizable MIME types in your routing schemes. Simple as that may seem, it tidies up routing schemes that involve static content sharing. Consider this route portion:

Here, we effectively separate static content from .php scripts within a single share action, achieving what previously required two different route steps.

The types array supports the same pattern mechanism that the route conditions use, so we can employ some neat magic to restrict the static content types we serve. When NGINX Unit cannot infer the MIME type of a requested file, it considers the type to be empty. Thus, we can configure Unit to serve only files whose MIME types it recognizes by including the negated empty string ("!") – meaning “do not serve files with empty MIME types” – in the types array:

Note that you can also define custom MIME types to control which file types Unit serves. In this example, we define the set of filenames and extensions with type text/plain that Unit recognizes and allows to be used in the types array:

This shows how you can use the global mime_types option to extend the capabilities of the types option beyond what’s built in, adapting it to your routing purposes.

Static Content: Chrooting and Path Restrictions

NGINX Unit 1.24.0 introduces three new options for fine‑tuning how Unit serves static content on servers running Linux kernel version 5.6 and higher. They are intended to prevent accidental or deliberate misuse of NGINX Unit’s mechanics for serving static content. The first one is chroot:

This option sets the root directory for pathname resolution within the directory named by the share option. One notable effect is that symbolic links to absolute pathnames within the share directory are treated as relative to the new root. Given the configuration above, for example, if you create a symbolic link in /www/data/static/ to /log/app.log, it is resolved as /www/data/log/app.log. Note, however, that the treatment of symbolic links is also affected by the setting of the follow_symlinks option, discussed next.

The two remaining options, follow_symlinks and traverse_mounts, control NGINX Unit’s attitude toward (surprise!) symbolic links and mount points:

When these options are set to false (the default is true), requests fail if they require resolution of a symbolic link or mount point within the share directory (here, /www/data/static/); Unit is effectively locked within the confines of the share directory.

The interaction of chroot with follow_symlinks and traverse_mounts can be bit intricate so let’s take a moment to consider the merged configuration:

When chroot is set, the values of follow_symlinks and traverse_mounts only affect portions of the path after the new root. This means that subdirectories that are a part of the chroot path (here, www/ and data/) can be symbolic links or mount points, but any symbolic links or mount points beyond the final element in the chroot path (here, that includes static/) are not resolved.

Node.js Override

We hope you’ll be glad to learn that Node.js® apps now require zero code alterations to run in Unit. This is achieved with a new loader module (unit-http/loader.mjs) that works behind the scenes at application startup, laying all the necessary groundwork for a truly server‑agnostic app:

Admittedly, this looks a bit cumbersome. It looks like a win to us, though, because it makes a significant redesign unnecessary, instead achieving the desired effect by using NGINX Unit’s existing mechanics for running a designated executable (here, /usr/bin/env).

Python Targets

NGINX Unit 1.24.0 extends to Python apps the level of granularity previously granted only to PHP apps. You can configure several scripts within a single app to simplify your routing and application setup:

This example puts two modules, foo/ and bar/, into the context of a single app and its processes, which conserves system resources otherwise consumed by running multiple scripts that comprise a single app. Elsewhere in the configuration, you can pass requests to these portions of your app quite independently:


In this blog post, we’ve discussed Unit versions 1.23.0 and 1.24.0, but version 1.25.0 is not too far away, so stay alert for the new features and numerous bug fixes it’s about to bring to your table.

As always, we invite you to check out our roadmap, where you can find out whether your favorite feature is going to be implemented any time soon, and rate and comment on our in‑house initiatives. Feel free to open new issues in our repo on GitHub and share your ideas for improvement.

For a complete list of the changes and bug fixes in versions 1.23.0 and 1.24.0, see the NGINX Unit changelog.

NGINX Plus subscribers get support for NGINX Unit at no additional charge. Start your free 30-day trial today or contact us to discuss your use cases.

Hero image
NGINX 企阅版全解析



Artem Konev

Senior Technical Writer


F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 了解更多相关信息。