Nginx Unit - A new way to run web applications

Miguel Wolf, 4 November 2022

Nginx is known for their popular web server, which is most used on the internet according to a survey by Netcraft.

Since a couple of years Nginx has been developing a new product called Nginx Unit: A modern universal app server with an integrated runtime for many programming languages.

Its key features are:

  • Serving static files as a web server
  • Natively running application code in multiple languages
  • Acting as a reverse proxy

Own illustration after https://youtu.be/5NVWRl-NC1k?t=1361

As shown in the picture above Unit currently supports the following programming languages:

  • PHP
  • Python
  • Ruby
  • Perl
  • Golang
  • Node.js
  • Java (only Servlet)

Additional to these languages .NET Core is on their roadmap.

Unit’s goal is to solve some common challenges of developing and running modern web applications. It simplifies the application stack for web applications by combining several layers into a single component. A web application stack is often a complex ecosystem with many layers and different owners on the dev and ops side. For example on the one side it is required to change runtime configuration on the other side to change web server configuration.

Unit solves this problem with an integrated runtime for multiple programming languages. Using these functionality applications can be configured in a more efficient way.

Own illustration after https://youtu.be/5NVWRl-NC1k?t=307

With Unit PHP application can be deployed side by side with a Python application on the same instance. Multiple applications are running isolated in own processes within Unit. If one application fails or runs into an error only these processes are restarted, and other applications remain untouched.

Security is one big topic in Unit. Therefore, client connections are handled by a separate non-privileged router process. Unit uses Linux namespaces, cgroups, and filesystem isolation to provide a secure runtime for each application.

Despite that Unit comes with SSL/TLS support and can handle certificates. End-to-end TLS often involves decrypted connections, which offer opportunities to intercept. The app server offers real end-to-end encryption as the runtime is integrated.

Configuration

Unit comes with a modern RESTful JSON API for its configuration. This is one big difference to the common Nginx web server which is configured through multiple configuration files and a CLI tool.

A difficulty with Nginx is that its configuration doesn’t follow a common standard. Unit is entirely configurable with JSON, which is easier to maintain, and programmatic changes are simplified with a declarative configuration. It is also familiar to developers who have already worked with Kubernetes.

The REST API of Unit uses the classical http methods GET, POST, PUT and DELETE. Units REST API can be configured to listen on a port or connect to it through a unix socket like it is done in the following example. The following example shows a curl command to apply a config.json. Unit then sends a JSON response with “Reconfiguration done”:

   curl -X PUT 
   --data-binary @path/to/config.json 
   --unix-socket /var/run/unit/control.sock 
   http://localhost/config/

As this is a classical REST API the configuration of a specific application can be retrieved with a GET call on /config/applications/name-of-your-application. Unit will respond only with the json config of this application.

For the configuration there are three key components of Unit:

  • Listeners: Configuring host, ip and port –> could lead to Routes or directly to Applications
  • Routes: Matching of routes, paths and files –> lead to Applications
  • Applications: Configuration of single applications (path to the code, environment variables, maximum processes, timeouts/limits, etc.)

Own illustration after https://youtu.be/5NVWRl-NC1k?t=2101

Example

The following shows a configuration example of a Symfony application.

Unit is listening on each host name and port 8080. Requests are passed to routes. There are two actions defined, the first at line 15 matches all PHP files and leads to the application at line 37 (the path at pass represents the path in the json config), the second action (line 20) matches static files on the file system and has a fallback to the application (analogue try_files in Nginx).

At applications there is one application defined named symfony of type PHP (line 29 and 30). There are some configurations about Unit’s process handling and the targets which are referenced at the routes section. The first target (line 37) leads directly to the public directory where all relevant starting PHP files are placed. The second target index (line 40) leads directly to the main entry point of a Symfony application the index.php.

Following this approach applications based on different programming languages can be configured.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
   "listeners":{
      "*:8080":{
         "pass":"routes"
      }
   },
   "routes":[
      {
         "match":{
            "uri":[
               "*.php",
               "*.php/*"
            ]
         },
         "action":{
            "pass":"applications/symfony/direct"
         }
      },
      {
         "action":{
            "share":"/app/public$uri",
            "fallback":{
               "pass":"applications/symfony/index"
            }
         }
      }
   ],
   "applications":{
      "symfony":{
         "type":"php",
         "processes":{
            "max":25,
            "spare":10,
            "idle_timeout":120
         },
         "targets":{
            "direct":{
               "root":"/app/public/"
            },
            "index":{
               "root":"/app/public/",
               "script":"index.php"
            }
         }
      }
   }
}

Status/Usage Statistics

Like PHP Fast Common Gateway Interface (CGI) Process Manager (FPM) Unit provides an endpoint to get usage statistics mapped to the path /status. There you get information about connections, requests and single applications. The following json shows an example of a statistic response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
   "connections":{
      "accepted":2,
      "active":2,
      "idle":0,
      "closed":0
   },
   "requests":{
      "total":2
   },
   "applications":{
      "symfony":{
         "processes":{
            "running":4,
            "starting":0,
            "idle":2
         },
         "requests":{
            "active":2
         }
      }
   }
}

To get a statistic for a single application, the following path can be queried: /status/applications/name-of-your-application

Nginx Unit vs. PHP-FPM

Usually PHP applications can be served by the Apache webserver with PHP module or by Nginx with FastCGI protocol connected to PHP-FPM.

Unit uses the PHP-CLI interface to connect to the PHP SAPI (Server API) compared to PHP-FPM which uses FPM-CGI. Unit is more robust under high load and performs better in terms of response times.

One big advantage of Unit compared to PHP-FPM is, that only the applications worker processes are restarted after a config change and processes of other applications running on the same instance remain untouched. If PHP-FPM is used on a shared hosting system with different pools for different projects and the pool configuration of one project needs to be changed, the whole PHP-FPM system process needs to be restarted which restarts all worker processes across all pools.

Another advantage is, that Unit can serve PHP and static files together. If PHP applications and static files need to be served, PHP-FPM can only be used in conjunction with a web server in front, as PHP-FPM can only serve PHP applications.

Load tests

To compare the performance of PHP-FPM and Unit I performed load tests. For the tests I used the Symfony demo application (blog page) with PHP in version 8.1.10. Tests were executed on my local development environment (MacBook Pro, Intel Core i7, 16GB RAM). Unit was installed directly to the system and called directly. The Nginx in a docker container was connected to a PHP-FPM installation which was also installed directly without running in a container. The load tests itself were performed by using Gatling.

Both runtimes were configured to use 25 processes in maximum (pm.max_children for PHP-FPM and processes.max for Unit).

First, I ran a load test with 1000 requests in 60 seconds with the following results:

Both Unit and PHP-FPM could handle all requests and no failures occurred. As shown in the results, the mean response time of Unit is approx. 181% better compared to PHP-FPM. Note the different scale of the number of requests.

The following statistic shows a small difference at the minimum response time, but a larger difference at the maximum mean time. The standard deviation is twice as high. The response time of Unit is more constant compared to PHP-FPM.

In the second load test 1000 requests were executed in 30 seconds. Unit handled those requests in less time. PHP-FPM took 51 seconds to handle the requests while Unit only needed 35 seconds. Nearly 200 requests out of 1000 failed with PHP-FPM while Unit could handle all requests.

The statistics shows that Unit had a higher minimum response time but performed better at the maximum response time and the mean response time. In addition to that the standard deviation of Unit is nearly a tenth.

Conclusion

Unit is a modern and performant web server and app runtime which is stable under high traffic. It impresses through its dynamical REST API configuration and broad support on programming languages. It is a real alternative to classical application stacks like Nginx in conjunction with PHP-FPM for PHP applications.

Unit is no replacement for Nginx because of Nginx’s wide opportunities to add headers, rewrites, etc. which Unit currently does not have (yet).

Web applications typically need a couple of components like web servers, reverse proxies and so on. Unit combines these functions in a single component with one configuration. In this way Unit allows to deliver an entire web application or API endpoint by the same server.

Start with Unit here!