NGINX Unit 1.8.0 Is Now Available, Introduces Internal Routing

by

Today we’re announcing NGINX Unit 1.8.0, which introduces support for internal routing along with other features. In this post, we’re sharing details about all features and a preview of upcoming developments:

Introducing Internal Routing

We’re introducing the internal routing feature with a different approach than we normally use – this time it’s example first, description after:

“routes”: {
“blogs”: [
{
“match”: {
“uri”: [“/phpmyadmin/*”, “/wp-admin/*”]
},
“action”: {
“pass”: “applications/blogs-admin”
}
},
{
“match”: {
“host”: “!blog.example.com”
},
“action”: {
“pass”:”applications/goaway”
}
},
{
“match”: {
“method”: “POST”,
“uri”: “/media/upload”
},
“action”: {
“pass”:”applications/uploader”
}
}
]
}

Now, the details.

When we released the initial version of NGINX Unit, some of our users wondered why the API included a separate listener object – it perhaps wasn’t clear why it was necessary. In NGINX Unit 1.8.0, we’re introducing the internal routing feature that makes use of the listener object in a more sophisticated way and enables new functionality in the application server.

Internal routing enables granular control over the target application based on the sets of policies that you define. Sample use cases include:

  • Requests to administrative URLs need a different security group and fewer application processes than the main application.
  • Requests to a wrong hostname need to be handled by a separate application that does not consume system resources.
  • POST requests are handled by a special app, maybe written in a different language.

Full documentation for internal routing is available at https://unit.nginx.org/configuration/#routes.

Introducing the routes Object

The new routes object is accessible through the NGINX Unit API, and located inside the /config object.

If you only need one set of routing rules for all available listeners, you can define the routes object as a JSON array:

“routes”: [ { … }, { … }, { … } ]

If you need more than one set of rules, define routes as a JSON object. Each named object inside of it is an array of one or more rules:

“routes”: {
“blogs-routes”: [ { … } , { … } ],
“security-routes”: [ { … }, { … } ],
“misc-routes”: [ { … } ]
}

You use the route names defined here when referring to the routes in other sections of the configuration.

Defining Routing Rules

Each rule in the route object consists of a match object and an action object.

The match Object

The match object defines the characteristics a request must have for the corresponding action to happen. You can omit the match if the rule is unconditional (“match always”). Version 1.8.0 supports three match objects:

  • host – The HTTP Host header, or the hostname that you usually see in the browser’s address bar
  • uri – The URI without the query string
  • method – The HTTP method, such as GET, POST, or PUT

Version 1.8.0 supports three types of matching:

  • Exact match.
  • Partial match. Use the * character (asterisk) to match any number of characters at the beginning or the end of the value, for example /api/*, *.php, or P* (the last to match the PUT and POST methods).
  • Negative match. Use the ! character (exclamation point) in front of the value, for example !api.example.com.

In future releases, we plan to support partial match at any position in the value, and after that regular expressions.

A match object can be defined either as a string or as an array.

The action Object

The other object in routing rules, action, is mandatory. NGINX Unit 1.8.0 supports only one action, pass, which passes the request to the specified application or route. We plan to add other actions, such as redirects, HTTP return codes, static files, and maybe more.

You can pass requests to either an application:

“action”: {
“pass”: “applications/blogs-general”
}

or a route:

“action”: {
“pass”: “routes/blogs”
}

Creating Routing Chains

Because the action object in a route object is the same as the action object in a listener object, you can chain routes together, as in the following example. Make sure you don’t loop them!

“routes”: {
“host-method”: [
{
“match”: {
“host”: “example.com”,
“method”: [“GET”, “HEAD”]
},
“action”: {
“pass”: “routes/url-only”
}
},
{
“match”: {
“host”: “blog.example.com”
},
“action”: {
“pass”: “applications/blog”
}
}
],
“url-only”: [
{
“match”: {
“uri”: “/api/*”
},
“action”: {
“pass”: “applications/myapp”
}
}
]
}

Dynamically Changing Routing Rules

Like all other configuration objects in NGINX Unit, the routes object is accessed through the NGINX Unit API and stored in memory rather than in a file. This means you don’t have to edit a configuration file to change a routing rule, nor do you need to restart NGINX Unit.

As previously mentioned, a route is represented as an array of one or more rules. When you want to change a rule, you reference it by its numerical index in the array, starting with 0.

In the following examples, we change each of the three routing rules in the example from the beginning of the post. Here’s the example again:

“routes”: {
“blogs”: [
{
“match”: {
“uri”: [“/phpmyadmin/*”, “/wp-admin/*”]
},
“action”: {
“pass”: “applications/blogs-admin”
}
},
{
“match”: {
“host”: “!blog.example.com”
},
“action”: {
“pass”:”applications/goaway”
}
},
{
“match”: {
“method”: “POST”,
“uri”: “/media/upload”
},
“action”: {
“pass”:”applications/uploader”
}
}
]
}

In the first rule (index 0), we set the application that processes the main blogs route to applications/cms, replacing applications/blogs-admin:

curl -X PUT -d ‘”applications/cms”‘ –unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/0/action/pass

In the second rule (index 1), we set the matched hostname to www.example.com, replacing !blog.example.com:

curl -X PUT -d ‘”www.example.com”‘ –unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/1/match/host

In the third rule (index 2), we set the matched methods to both POST and PUT, instead of just POST:

curl -X PUT -d ‘[“POST”, “PUT”]’ –unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/2/match/method

Minimal Config for Internal Routing

There are different approaches to system configuration. Many engineers prefer to read full documentation first and create correct configuration from scratch. I usually build my configurations iteratively, starting with the minimal mandatory config and gradually expanding it with the values I want for the options. For this blog, I started with this minimal config (yours might be different):

{
“certificates”: {},
“config”: {
“listeners”: {
“*:8400”: {
“pass”: “routes/example”
}
},

“applications”: {
“blogs”: {
“type”: “php”,
“root”: “/home/nick/demo/routing”,
“script”: “demo.php”
}
},

“routes”: {
“example”: [
{
“match”: {
“host”: “*”,
“uri”: “*”,
“method”: “*”
},

“action”: {
“pass”: “applications/blogs”
}
}
]
}
}
}

New Third Number in Versioning

Eagle‑eyed users of NGINX Unit might have noticed that the latest version, 1.8.0, has one more digit than previous version numbers. Starting with version 1.7.1, we added the final (revision) number to the existing major and minor version numbers used for versions 0.1 through 1.7.

This small change allows us to create patch versions for any security fixes and other important updates that do not alter functionality or add major features. Version 1.7.1 was the first example of such an update.

NGINX Unit Available from Remi Repositories

Like any other software project, programming languages grow and evolve. However, it is quite common for operating system maintainers to freeze the version of programming languages that they package with a specific OS distribution. Judging from our experience, enterprise users in turn tend to freeze the operating system version in their stack and refrain from upgrading for as long as possible.

For example, we know of quite a few enterprises still using CentOS 6, which was released in 2011 and is set to EOL in late 2020. The CentOS 6 package includes PHP version 5.3, which is nearly 10 years old and has already reached its EOL.

Besides upgrading to the latest OS version monthly, the most popular solution for this problem is to use a third‑party repository of OS packages that include more recent versions of PHP and other languages.

Remi Collet, a community contributor from France, has been maintaining a number of such PHP repositories for many years. At the beginning of 2019, Remi added NGINX Unit packages to his repositories for CentOS, RHEL, and Fedora. These packages contain NGINX Unit modules that run all versions of PHP that he provides.

Now you can have the best of both worlds – keep your infrastructure maintenance team happy by skipping the operating system upgrade and have your apps run the latest PHP packages on our modern application server. See Remi’s announcement and setup instructions.

Experimental Support for Java Servlet Containers

Java is ubiquitous – there’s not much need to elaborate on that. We are working to build a supported solution for Java application engineers.

Our own Maxim Romanov worked in a separate branch last year on support for running applications leveraging certain technology described in the Java® 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 provide feedback.

Java is a registered trademark of Oracle and/or its affiliates.

Upcoming Support for Static File Serving

Soon, we’ll start working on serving static files. The ability to serve static files will make NGINX Unit not only the most versatile application server, but a full‑fledged web server as well.

Upcoming Support for Proxying

We’ve had a recent surge of questions about support for proxying. We’re happy to announce that our engineering team has this issue in their sights as well. When they’re done, NGINX Unit will be able to receive an HTTP request and do one of the following: process it internally with an app in any of the supported languages, deliver a static file from the filesystem, or proxy the request further to another NGINX Unit instance or a different backend.

Conclusion and Next Steps

Internal routing makes your applications more configurable and dynamic. We plan to extend this functionality in future releases to other Layer 7 objects such as HTTP headers and cookies, and to Layer 3 objects such as IP addresses and ports. Try it in your web application configuration, and let us know what configs you come up with. You can ask questions in our GitHub issue tracker (https://github.com/nginx/unit/issues) or the NGINX Unit mailing list (https://mailman.nginx.org/mailman/listinfo/unit).

Full documentation for internal routing is available at https://unit.nginx.org/configuration/#routes.

Want to create the next big feature for NGINX Unit? We are hiring! Check out the C/Unix engineer openings at https://www.nginx.com/careers/current-openings/.