Skip to main content

Nginx Unit: PHP, Python and Node Without Extra Services

Nginx · 18.10.2026 · 6 min read
Illustration for “Nginx Unit: PHP, Python and Node Without Extra Services”

Nginx Unit is a separate application server from the nginx team: it runs PHP, Python, Node.js and a few other languages from a single management process, without separate services such as php-fpm or pm2. The nginx web server itself is not replaced: Unit takes over running the application, while TLS termination and static file serving in front of it are usually still handled by nginx. Here is when this combination simplifies administration, and when the familiar php-fpm or pm2 setup stays simpler.

How Unit differs from php-fpm and pm2

Unit is managed not through a text config reloaded by a signal, but through a REST API over a control socket: applications, listeners and routes are described as a JSON object and applied with a PUT request without stopping the process. Adding a new application or changing the number of workers does not require editing a configuration file and running a reload command, as it does with the php-fpm setup.

For Node.js the difference is fundamental: pm2 starts a regular Node.js process and watches it from the outside, while Unit is embedded into the application through the unit-http module, which replaces the standard http module. Moving an existing Node application to Unit requires changing the startup code, not just the server configuration.

Installing Unit and its processes

Unit is installed as a separate package plus language modules — for PHP, Python, Node.js and so on, built for a specific interpreter version. The main unitd process listens on the control.unit.sock control socket and spawns router processes and separate application processes, whose number is set in each application's configuration.

The role of nginx in this setup does not change: it remains the same web server described in the article on Nginx architecture. Nginx listens on port 80 or 443, serves static files directly and proxies dynamic requests to Unit through proxy_pass, like to any other upstream.

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

{
  "type": "php",
  "root": "/var/www/myapp/public/",
  "index": "index.php",
  "processes": { "max": 4, "spare": 1 }
}

Applications in the JSON configuration

Each application is described by its own object with a type, a path and a number of processes. A listener binds a network port or a unix socket to an application name, so a PHP site, a Python service and a Node backend can run on the same host at once, each with its own processes and limits.

Parameterphp-fpm + pm2Nginx Unit
Configuration formatseparate text ini and json files per servicea single JSON object through a REST API
Applying changesreload or restart of the servicea PUT request without stopping the process
Running Node.jsa regular process, watched from the outside by pm2the unit-http module inside the application code
Several languages on one hosta separate service per languageone unitd process for all languages

This split is convenient for hosting several applications in different languages on one VDS: there is no need to keep a separate systemd unit per language and sync their restarts by hand.

Nginx in front of Unit: proxying and static files

Unit listens on its own port or unix socket, and nginx in front of it works the same way as in front of php-fpm or any other backend: it accepts the connection, serves static files directly from the file system and proxies requests for dynamic content.

The base server block setup, TLS certificates and logs do not change and are covered in the article on configuring Nginx for a site on a VDS. In addition, the location block for dynamic requests points proxy_pass at the Unit listener instead of fastcgi_pass at php-fpm.

What to check before migrating

Before moving a production site to Unit, it is worth going through a short checklist — some limitations only show up in production, not on a test setup.

  • A Node.js application will need its startup code changed for unit-http — a plain process.listen call will not work with Unit without changes.
  • PHP needs the unit-php module built for the installed PHP version — a version mismatch will keep the application from starting.
  • The control.unit.sock control socket must stay reachable only locally and blocked from outside access by the firewall.
  • The number of application processes is set explicitly in the configuration, not derived automatically from load.
  • Application logs go to the shared Unit log by default, not to a separate per-process file as with pm2.

Frequently asked questions

Can nginx be removed and only Unit kept?

Technically Unit can listen on a port and serve static files on its own, but for a production site with TLS certificates and several domains on one VDS it is usually simpler to keep nginx in front of it — certificates and logs are then managed at the same level.

What if the server's PHP version does not match the Unit module?

Unit allows keeping several language modules at once — for example php81 and php83 as separate module files, and each application in the configuration points to the version it needs. A module is built for a specific PHP version and does not work for a neighboring minor version without a rebuild.

How to check that an application in Unit is running and responding?

The current configuration and status are read through the same control socket: a GET request to /config shows the active applications and listeners, and /status shows the number of processes and requests handled. Application startup errors go to the Unit log.

Was this article helpful?
← Back to Knowledge Base Ask Support