# nginx-clojure: running JVM handlers inside the nginx worker process

> nginx-clojure is an nginx module that embeds Clojure, Java or Groovy programs, typically Ring handlers, into the server itself, with access, header, body and log hooks, shared memory state and coroutine-based sockets. This looks at what the module actually does and how tightly it is coupled to nginx.

**nginx-clojure/nginx-clojure** — Nginx module for embedding Clojure or Java or Groovy programs, typically those Ring based handlers.

- Repository: https://github.com/nginx-clojure/nginx-clojure
- Website: http://nginx-clojure.github.io
- Stars: 1,084 · Forks: 112
- Language: Java
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/nginx-clojure-nginx-clojure

## A JVM inside the nginx worker

nginx-clojure is an nginx module for embedding Clojure, Java or Groovy programs, typically Ring based handlers. The project site is nginx-clojure.github.io, the readme carries a BSD licence badge, and the module is published to Clojars.

The architectural idea is the whole point. Normally you run nginx in front of an application server, and the JVM application runs in its own process, its own container, behind a proxy hop. This module puts the JVM inside the nginx worker process, so a request can be served by code running in the same process that serves the static files, and the response never crosses a socket.

The feature list in the readme is long, and it is organised around the nginx extension points, which tells you how the module is built. There is an access handler, a header filter, a body filter, a log handler, a rewrite handler for dynamic proxying, a content Ring handler for a location, and an initialization handler that runs Clojure code when an nginx worker starts.

The claim at the end of the list is the one that explains the rest: lazy headers and direct memory operation between nginx and the JVM to handle dynamic content quickly, and nginx's zero copy file sending mechanism to handle static content controlled by Clojure or Java code. The module is not trying to make nginx a Java application server. It is trying to make the boundary between the two cheap.

The readme also notes one of nginx's own properties as a benefit, that worker processes are automatically restarted by a master process if they crash, which is a supervisor guarantee that a JVM application behind a proxy would not inherit.

## Five extension points, each writable in three languages

The readme lists the supported features as a set of numbered points, and the first block is the extension surface: compatible with Ring and the Ring based frameworks such as Compojure, and HTTP services written with simple handlers in Clojure, Java or Groovy, followed by the nginx access handler, the nginx header filter, the nginx body filter and the nginx log handler, each of which can be written in Clojure, Java or Groovy.

This matters because it is a filter chain, not a single entry point. The access handler runs before the content phase and can reject a request, the header filter can add or rewrite response headers, the body filter can transform the outgoing body, and the log handler sees the transaction for logging. The rewrite handler is described as dynamic proxying, where a Clojure, Java or Groovy handler sets a variable or returns an error before the proxy pass or content ring handler runs.

So there are two distinct modes of use embedded in one module. You can serve a request from your own code as the content handler for a location, or you can write a decision that runs earlier and then lets nginx carry on with its normal behaviour, including proxying to an upstream.

The configuration documentation has a page per hook, with separate sections for the rewrite handler, the access handler, the header filter, the body filter and the log handler, plus a page for the content Ring handler for a location and a page for the initialization handler that runs when an nginx worker starts. That last one is how you start a JVM-side subsystem once per worker rather than once per request.

## Shared memory, pub/sub and coroutine sockets

Three features show that this is a server component rather than a handler library, and they are also the parts most likely to matter in production.

The first is a shared map based on shared memory, and a shared map based Ring session store. A Ring session that lives in the JVM of one worker is invisible to the other workers, and nginx serves requests from several of them. A session store in shared memory is how a login survives a request that lands on a different worker, and the module also supports pub/sub among nginx worker processes, so one worker can publish a message that another receives.

The second is the coroutine-based socket. The readme describes a non-blocking coroutine based socket compatible with the Java Socket API that works well with existing Java libraries such as the Apache HTTP client and the MySQL JDBC drivers, and states that with this feature one Java main thread can handle thousands of connections. It can handle multiple sockets in parallel in sub coroutines, so two remote services can be invoked at the same time, and there is an asynchronous callback API of socket and channel for advanced usage. There is explicit support for the jdk19 built-in coroutine, namely Continuation.

The third is the request model: long polling and server sent events, plus server side WebSocket, with support for per-message compression extensions for WebSocket, and support for Sente.

There is also a built-in Jersey container to support Java standard RESTful web services, JAX-RS 2.0, and Tomcat 8 embedding support, so that servlet 3.1, jsp, sendfile and JSR-356 websocket work within nginx. The repository layout matches: `nginx-jersey/`, `nginx-tomcat8/` and `nginx-clojure-embed/` are separate modules, next to `src/`, `test/` and `example-projects/`.

## Getting the artifact from Clojars

The library is published to Clojars, and the readme gives the repository declaration and the three dependency forms.

The repository to add is:

```xml
<repository>
  <id>clojars.org</id>
  <url>http://clojars.org/repo</url>
</repository>
```

With Leiningen, which is where Clojure projects usually start:

```clojure
[nginx-clojure "0.6.0"]
```

With Gradle:

```
compile "nginx-clojure:nginx-clojure:0.6.0"
```

And with Maven:

```xml
<dependency>
  <groupId>nginx-clojure</groupId>
  <artifactId>nginx-clojure</artifactId>
  <version>0.6.0</version>
</dependency>
```

The documentation set is organised as a quick start, downloads, installation, configuration and a set of advanced topics. The configuration section has anchors for the JVM path, class path and other JVM options, the initialization handler for the nginx worker, the content Ring handler for a location, and the choice between the coroutine based socket, the asynchronous SocketChannel, or a thread pool for slow I/O operations. That last choice is the one worth thinking about before deploying, because it decides whether a slow dependency call blocks a worker.

The module also ships prebuilt binaries through a SourceForge download link, and the project has a Travis CI build badge, which dates the continuous integration story.

## The coupling to nginx versions is the constraint to plan around

Two version facts decide how much trouble an adoption will be.

The first is the nginx range. The readme states HTTP V2 support in both the standard edition and the embedded edition, compiled against nginx 1.18.0 and later, and compatibility with the latest mainline version 1.23.3, while noting that 1.22.x, 1.20.x, 1.18.x, 1.14.x, 1.12.x, 1.8.x and 1.6.x are also fine but that older versions are not tested and maybe work. That is the honest shape of the support matrix for a module that links against nginx internals.

The second is the module's own version. The release history shows v0.6.1-alpha from 2024-05-13, v0.6.0 from 2023-03-18 and v0.5.3 from 2022-03-10, and the readme still says the latest release is v0.6.0, pointing at the release history for the changes. So the newest stable artefact is from March 2023 and the newest tag of any kind is an alpha from May 2024, while the last push to the repository was on 2026-09-11.

The platforms are broad for a native module: Linux x64, Linux x86 32-bit, Win32, Win64 and Mac OS X, with a FreeBSD version obtainable from the FreeBSD ports. Java support is listed for Java 8, 9, 10, 11, 12 and 19, which tells you the range the author tested rather than the range the JVM world is on now.

## Where nginx-clojure is the wrong tool

Four limits are clear.

The first is the coupling. An nginx module links against nginx internals and is rebuilt against specific nginx versions, so your upgrade path for nginx and your upgrade path for the module are the same decision. The readme's own list of supported nginx versions, ending at 1.23.3, is a snapshot. A deployment that tracks nginx mainline will eventually need a module build for that release, and the project does not publish one on any visible schedule.

The second is the JVM per worker. Because the module embeds a JVM in the nginx worker, each worker carries JVM memory and startup cost, and the feature list advertises that one Java main thread can handle thousands of connections through the coroutine socket. That is a good argument for the design, and a bad one for a deployment with many workers on a small machine, and it is a different shape from running a servlet container with a tuned heap.

The third is scope. This is for Clojure, Java and Groovy handlers, typically Ring based ones. If your application is already a normal servlet application with a war file, a container, an admin console and a deployment pipeline, this module gives you a JVM you manage yourself and an nginx you have to keep in step, and Jetty or Tomcat behind nginx does the same job with less coupling.

The fourth is the maturity of the artefact. A 0.x module whose newest stable is from 2023, with an alpha from 2024 as the only thing newer, is a dependency to adopt with care and to vendor if it matters to you. The readme points at external benchmarks for the performance claim, and there is a `HISTORY.md` for the change record, but the version stream is thin.

None of this makes it a bad project. It makes it a specialised one, and the specialisation is exactly what makes it good at the thing it does and awkward at everything else.

## Conclusion

nginx-clojure fits a team that wants nginx's process model and static file handling in front of JVM business logic, particularly a Clojure team using Ring with Compojure who wants the handler and the server in one process. It does not fit a team that wants an ordinary servlet container, a Jetty or Tomcat deployment, or a platform where JVM startup cost and memory per worker matter, because every nginx worker carries a JVM. It is also the wrong choice if you need an nginx version outside the range the module is tested against. Before you adopt it, read the installation and configuration pages for the nginx versions listed, and note that the newest published artefact is an alpha from 2024 while the stable line the readme points to is 0.6.0 from 2023.

## FAQ

### What does the nginx-clojure module do?

It embeds Clojure, Java or Groovy programs, typically Ring based handlers, inside the nginx worker process, and exposes the nginx access handler, header filter, body filter, log handler and rewrite handler to code written in any of those three languages.

### How do I add nginx-clojure to my build?

The artefact is on Clojars, so add the Clojars repository and then declare the dependency in your build tool: `[nginx-clojure "0.6.0"]` for Leiningen, `compile "nginx-clojure:nginx-clojure:0.6.0"` for Gradle, or the `nginx-clojure` groupId and artifactId with version 0.6.0 for Maven.

### Which nginx versions does nginx-clojure support?

The readme states HTTP/2 support compiled against nginx 1.18.0 and later, compatibility with the then latest mainline 1.23.3, and that 1.22.x, 1.20.x, 1.18.x, 1.14.x, 1.12.x, 1.8.x and 1.6.x also work, while older versions are not tested and may work. It is also available for Linux x64, Linux x86 32-bit, Win32, Win64 and Mac OS X.

### Can nginx-clojure run a normal Java web application?

There is a built-in Jersey container for JAX-RS 2.0 services and Tomcat 8 embedding support so that servlet 3.1, jsp, sendfile and JSR-356 websocket work inside nginx, and there are APIs for embedding the module into a standard Clojure, Java or Groovy application.

### How do nginx-clojure workers share state?

Through a shared map based on shared memory and a shared map based Ring session store, so sessions survive across workers, and through pub/sub among nginx worker processes. The module also offers a non-blocking coroutine based socket compatible with the Java Socket API, long polling and server sent events, and server side WebSocket with per-message compression extensions.

## Sources

- [Issues](https://github.com/nginx-clojure/nginx-clojure/issues)
- [nginx-clojure/nginx-clojure on GitHub](https://github.com/nginx-clojure/nginx-clojure)
- [Project website](http://nginx-clojure.github.io)
- [README](https://github.com/nginx-clojure/nginx-clojure/blob/master/README.md)
- [Releases](https://github.com/nginx-clojure/nginx-clojure/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nginx-clojure-nginx-clojure
