An application server is the layer that executes your web application code — handling HTTP requests, running your business logic, and returning responses. While traditional web servers like Nginx or Apache serve static files and proxy to separate application processes, an application server runs your code directly, eliminating the proxy overhead and simplifying your stack.

Nginx Unit is a universal application server from the makers of Nginx. It runs application code in-process across eight language runtimes — Python, Go, PHP, Ruby, Node.js, Perl, Java, and WebAssembly — with dynamic configuration via REST API. In this guide, we compare it against Gunicorn (the Python WSGI standard) and Caddy (the modern auto-HTTPS web server with module support).

What Is an Application Server?

An application server sits between your web framework and the network. When a request arrives, the application server:

  • Manages worker processes or threads to handle concurrent requests
  • Executes your application code (Python WSGI/ASGI, PHP-FPM, Node.js, etc.)
  • Handles connection pooling and request lifecycle management
  • Provides process monitoring and automatic restarts on failure
  • Serves static files (in some implementations)

The key difference from a traditional web server + application backend architecture is process integration. Instead of Nginx → Unix socket → Gunicorn → your app, an integrated application server like Nginx Unit handles the entire chain in one process.

Comparison at a Glance

FeatureNginx UnitGunicornCaddy
GitHub Stars5,5639,300+58,000+
LanguageCPythonGo
Language Support8 runtimesPython onlyGo (modules for others)
ConfigurationREST API (JSON)Python config fileCaddyfile (declarative)
Hot ReloadZero-downtimeGraceful reloadZero-downtime
Static FilesYes (built-in)No (proxy needed)Yes (built-in)
TLS/HTTPSVia reverse proxyVia reverse proxyAutomatic (Let’s Encrypt)
HTTP/2Via reverse proxyVia reverse proxyNative
HTTP/3NoNoNative
Load BalancingVia reverse proxyVia reverse proxyBuilt-in
WebSocketsYesYes (ASGI)Yes
Docker ImagesOfficial (18+ variants)OfficialOfficial
Resource Usage~10MB per language~30MB per worker~15MB
Best ForMulti-language deploymentsPython-only appsSimple HTTPS + app serving

Nginx Unit: The Universal Application Server

Nginx Unit’s defining feature is multi-language support in a single binary. Instead of deploying a separate Gunicorn for Python, PHP-FPM for PHP, and pm2 for Node.js, you run one Unit instance that handles all of them. Each language runtime is loaded as a module, and you configure applications through a REST API — no restarts needed.

Key features:

  • Eight language runtimes — Python 3, Go, PHP, Ruby, Node.js, Perl, Java, WebAssembly
  • Dynamic configuration — add, remove, or update applications via REST API without restarting
  • Process isolation — each application runs in its own process group with configurable limits
  • Request routing — route by hostname, URL prefix, or source IP
  • Static file serving — serve assets directly without a separate web server
  • Zero-downtime reconfiguration — change settings without dropping connections

Docker Compose Setup

Nginx Unit provides official Docker images for each language runtime:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: "3"
services:
  unit:
    image: nginx/unit:python3.13
    container_name: nginx-unit
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./app:/var/app:ro
      - ./unit-config.json:/docker-entrypoint.d/config.json:ro
    environment:
      UNIT_CONTROL_SOCKET: "/var/run/control.unit.sock"

Initial configuration (JSON) that defines a Python application and static file serving:

 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
{
  "listeners": {
    "*:8080": {
      "pass": "routes"
    }
  },
  "routes": [
    {
      "match": {
        "uri": ["/static/*", "/media/*"]
      },
      "action": {
        "share": "/var/app/public$uri"
      }
    },
    {
      "action": {
        "pass": "applications/myapp"
      }
    }
  ],
  "applications": {
    "myapp": {
      "type": "python 3.13",
      "processes": 4,
      "working_directory": "/var/app",
      "module": "wsgi",
      "user": "nobody",
      "group": "nobody"
    }
  }
}

Runtime Configuration via REST API

One of Unit’s most powerful features is the ability to reconfigure applications at runtime without restarting. Send a PUT request to the control socket:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Add a new application without restarting
curl -X PUT --unix-socket /var/run/control.unit.sock \
  http://localhost/applications/newapp \
  -d '{
    "type": "python 3.13",
    "processes": 2,
    "working_directory": "/var/app/newapp",
    "module": "app"
  }'

# Update process count for an existing app
curl -X PUT --unix-socket /var/run/control.unit.sock \
  http://localhost/applications/myapp/processes \
  -d '8'

# View current configuration
curl --unix-socket /var/run/control.unit.sock \
  http://localhost/

Multi-Language Setup

Here’s a Docker Compose configuration that runs multiple language runtimes on different ports:

 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
version: "3"
services:
  unit-python:
    image: nginx/unit:python3.13
    container_name: unit-python
    restart: unless-stopped
    ports:
      - "8001:8080"
    volumes:
      - ./python-app:/var/app:ro

  unit-php:
    image: nginx/unit:php8.4
    container_name: unit-php
    restart: unless-stopped
    ports:
      - "8002:8080"
    volumes:
      - ./php-app:/var/app:ro

  unit-go:
    image: nginx/unit:go1.24
    container_name: unit-go
    restart: unless-stopped
    ports:
      - "8003:8080"
    volumes:
      - ./go-app:/var/app:ro

Gunicorn: The Python WSGI Standard

Gunicorn (Green Unicorn) is the de facto standard WSGI HTTP server for Python applications. It’s the default application server for Django, Flask, FastAPI (via uvicorn/gunicorn combo), and most Python web frameworks. While it only supports Python, it does that one thing exceptionally well.

Key features:

  • WSGI and ASGI support — sync and async Python applications
  • Worker types — sync, gevent, eventlet, uvicorn workers
  • Pre-fork worker model — robust process management with master/worker architecture
  • Graceful reload — SIGHUP restarts workers without dropping connections
  • Mature ecosystem — 15+ years of development, used by millions of deployments
  • Simple configuration — Python-based config file with sensible defaults

Docker Compose Setup

 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
version: "3"
services:
  web:
    build: .
    container_name: gunicorn-app
    restart: unless-stopped
    command: gunicorn --bind 0.0.0.0:8000 \
      --workers 4 \
      --worker-class uvicorn.workers.UvicornWorker \
      --timeout 120 \
      --access-logfile - \
      --error-logfile - \
      myapp.wsgi:application
    volumes:
      - ./static:/app/static:ro
    expose:
      - "8000"

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
      - ./static:/app/static:ro
    depends_on:
      - web

Standard gunicorn.conf.py for production:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import multiprocessing

bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 120
keepalive = 5
accesslog = "-"
errorlog = "-"
loglevel = "info"
preload_app = True

Caddy: Auto-HTTPS Web Server with Module Support

Caddy is primarily known as a web server with automatic HTTPS, but its module system lets it serve as an application server for Go, PHP (via FastCGI), and reverse-proxy to any backend. The key advantage over Nginx Unit and Gunicorn is zero-config TLS — Caddy automatically obtains and renews Let’s Encrypt certificates.

Key features:

  • Automatic HTTPS — obtain and renew certificates without any configuration
  • HTTP/3 support — built-in QUIC and HTTP/3 (no other app server offers this)
  • Caddyfile syntax — human-readable, declarative configuration
  • Module ecosystem — extend with Go plugins for additional functionality
  • Reverse proxy — first-class proxy support with load balancing and health checks
  • Static file serving — efficient static asset delivery with automatic compression

Docker Compose Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: "3"
services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./app:/srv/app:ro
      - caddy-data:/data
      - caddy-config:/config

volumes:
  caddy-data:
  caddy-config:

A Caddyfile that serves a Python app via reverse proxy and handles static files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
example.com {
    # Reverse proxy to Gunicorn/Python app
    handle /api/* {
        reverse_proxy localhost:8000
    }

    # Serve static files directly
    handle /static/* {
        root * /srv/app/static
        file_server
        encode gzip
    }

    # Default: serve the app frontend
    handle {
        root * /srv/app/frontend
        file_server
        try_files {path} /index.html
    }

    tls {
        # Automatic Let's Encrypt
    }
}

When to Use Each Server

Choose Nginx Unit When:

  • You run multiple languages (Python + PHP + Node.js) and want a single process
  • You need dynamic reconfiguration via REST API (auto-scaling, blue-green deployments)
  • You want process-level isolation without container overhead
  • You’re already in the Nginx ecosystem and want consistent tooling
  • You need to serve applications without a separate reverse proxy layer

Choose Gunicorn When:

  • You’re running a Python-only application
  • You want the most battle-tested Python application server
  • Your framework (Django, Flask, FastAPI) has built-in Gunicorn support
  • You need async support via uvicorn workers for ASGI applications
  • You prefer simple, file-based configuration over REST API management

Choose Caddy When:

  • You want automatic HTTPS with zero configuration
  • You need HTTP/3 support for modern browsers
  • You’re running a Go application and want to embed the server
  • You want a single binary that handles TLS, proxy, and static files
  • You prefer human-readable Caddyfile syntax over JSON or Python config

Why Run an Application Server Directly?

The traditional architecture of placing a reverse proxy (Nginx) in front of an application server (Gunicorn/uWSGI) adds complexity: two processes to manage, two configurations to keep in sync, and a Unix socket or TCP connection between them. An integrated application server eliminates this middle layer.

Reduced attack surface — one process instead of two means fewer open ports, fewer configuration files, and fewer potential misconfigurations. Nginx Unit handles both the network listener and the application execution in a single binary.

Simplified deployment — instead of managing Nginx virtual hosts, upstream blocks, proxy headers, and socket permissions, you configure everything through a single JSON API. This is especially valuable in containerized environments where each additional service adds orchestration complexity.

Better resource utilization — eliminating the proxy layer reduces memory overhead and inter-process communication latency. For high-throughput applications, the direct connection between the network socket and application code can improve response times by 10-20%.

For related application server options, see our Tomcat vs WildFly vs Gunicorn comparison and the OpenResty vs Nginx vs Caddy guide for HTTP/3 web servers. If you need a reverse proxy management UI, our Nginx Proxy Manager comparison covers that as well.

FAQ

Can Nginx Unit replace Nginx entirely?

For application serving, yes — Unit handles HTTP listeners, routing, and static files without needing Nginx in front. However, Unit lacks some Nginx features like advanced caching, complex rewrite rules, and stream (TCP/UDP) proxying. Many production setups still use Nginx as a front-end reverse proxy for TLS termination and caching, with Unit handling application execution behind it.

Is Nginx Unit production-ready?

Yes, but with caveats. Nginx Unit is stable and used in production by many organizations. However, it is still considered “general availability” rather than “mature” — some language modules receive updates less frequently than others, and the REST API configuration model requires a different operational mindset than traditional config files. The official Docker images are well-maintained with regular security updates.

How does Nginx Unit handle process limits and resource isolation?

Unit supports per-application process limits (processes), memory limits (limits), and user/group isolation (user/group). Each application runs in its own process group, so a memory leak in one app doesn’t affect others. For stronger isolation (cgroups, namespaces), run Unit inside a container (Docker/Podman) — the official images are configured for this.

Can I use Gunicorn without Nginx?

Technically yes, but it’s not recommended for production. Gunicorn is designed to be behind a reverse proxy that handles TLS termination, static files, buffering, and connection management. Running Gunicorn directly exposed to the internet means it handles slow clients, TLS handshakes, and DDoS mitigation — none of which it’s optimized for. At minimum, use a simple reverse proxy like Caddy in front.

Does Caddy support PHP natively?

Caddy supports PHP via the FastCGI protocol, using the php_fastcgi directive. This connects to a PHP-FPM process (separate container or local service) — similar to how Nginx handles PHP. Caddy does not embed a PHP interpreter like Nginx Unit does. For a full PHP setup with Caddy, you need both the Caddy container and a php:fpm container.

What is the performance difference between Nginx Unit and Gunicorn?

For Python applications, Gunicorn with uvicorn workers typically matches or exceeds Unit’s Python module in raw throughput, because Gunicorn is purpose-built for Python. Unit’s advantage is not peak Python performance but multi-language flexibility and dynamic reconfiguration. If you only run Python, Gunicorn is the better choice. If you run Python, PHP, and Node.js on the same server, Unit simplifies your architecture significantly.