Open-source project
openresty/stream-lua-nginx-module avatar
openresty/stream-lua-nginx-module

stream-lua-nginx-module: putting Lua into the Nginx TCP and UDP path

Embed the power of Lua into NGINX TCP/UDP servers

750 stars210 forksCBSD-2-Clause

At a glance

What is it?
ngx_stream_lua_module is the OpenResty module that brings the Lua directive set into the Nginx stream subsystem, so TCP and UDP servers can be written in Lua through content_by_lua_block and friends. It is a port of ngx_http_lua, it is BSD-2-Clause licensed, and the last push was on 2026-10-18.
Who is it for?
Adopt stream-lua-nginx-module if you need to write TCP or UDP server logic in Lua inside Nginx, especially for protocol inspection, TLS handshake work or a custom proxy decision, and take it from OpenResty so you are not maintaining your own Nginx build. Skip it if your traffic is plain HTTP, because ngx_http_lua already covers that and the stream module is a separate port with its own version number.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A port of ngx_http_lua aimed at generic stream clients

The module exists because the HTTP module could not reach TCP traffic. The README describes it as a port of ngx_http_lua to the Nginx stream subsystem so as to support generic stream/TCP clients, and it states that the available Lua APIs and Nginx directives remain the same as those of the ngx_http_lua module. That single sentence is the design brief, and it is also the source of most of the surprises you will hit. The people using this already know Lua inside Nginx, and the point is to reuse that knowledge on connections that never become HTTP requests. A TCP server that speaks a line protocol, a UDP endpoint that answers a custom packet, an SSL server that makes a decision during the handshake: those are the cases this module is aimed at. It is also worth naming what it is not. It is not a separate scripting runtime. It shares the LuaJIT based execution model and the API vocabulary of the HTTP module, so an existing resty library that assumes an HTTP request context is not going to drop in unchanged.

The phase directives that shape a stream handler

The directive list is long, but it is not a flat list of settings. It separates into phases and into Lua execution points, and the stream-specific ones are the interesting part. content_by_lua_block and content_by_lua_file run the handler for a connection. preread_by_lua_block and preread_by_lua_file run before the routing decision, which is where you can inspect the first bytes of a connection and influence what Nginx does with it. balancer_by_lua_block and balancer_by_lua_file let Lua choose the upstream peer. log_by_lua_block and log_by_lua_file hook the logging phase. The init_by_lua and init_worker_by_lua pairs handle master and worker startup, and the ssl_client_hello_by_lua, ssl_certificate_by_lua and proxy_ssl_verify_by_lua families let Lua participate in TLS. Alongside them sit the settings you would expect from the HTTP module, ported directly, including lua_code_cache, lua_package_path, lua_package_cpath, lua_shared_dict and the family of lua_socket_* timeouts and pool sizes. The README is explicit that these are ported directly from ngx_http_lua and that you should consult the ngx_http_lua documentation for their usage and behaviour, which tells you where the gaps in the stream documentation are.

content_by_lua_block on a plain TCP port

The synopsis shows the smallest useful configuration. A TCP server listening on port 1234, with the connection logic written in Lua:

nginx
events {
    worker_connections 1024;
}

stream {
    # define a TCP server listening on the port 1234:
    server {
        listen 1234;

        content_by_lua_block {
            ngx.say("Hello, Lua!")
        }
    }
}

The parts that differ from a familiar HTTP setup are small but important. The server block lives inside the stream context rather than http, and the handler is content_by_lua_block rather than a location or content_by_lua in the HTTP sense. There is no request URI, no headers and no HTTP method to work with, so the Lua code has to deal with raw bytes. What you get in exchange is the whole Nginx stream machinery around your Lua code, including the worker and connection model, and the same ngx API family.

Reading and writing the downstream socket with ngx.req.socket

Real work in a stream handler means talking to the peer, and the SSL example shows how. The handler asks for the socket in true mode, receives a line from downstream, and compares it:

nginx
        content_by_lua_block {
            local sock = assert(ngx.req.socket(true))
            local data = sock:receive()  -- read a line from downstream
            if data == "thunder!" then
                ngx.say("flash!")  -- output data
            else
                ngx.say("boom!")
            end
            ngx.say("the end...")
        }

The boolean argument to ngx.req.socket selects whether the socket is reused for multiple exchanges, which matters when you are implementing a request and response cycle by hand. The pattern to take away is that a stream handler is a program that reads and writes, not a function that returns a response. The same example shows a TLS terminated server listening on 4343 ssl, with the certificate paths, cipher list, session cache and session timeout configured the way you would in the HTTP server. That configuration block is also where a careful reader will notice the cipher list includes RC4 and DES based suites, which belong in a modern deployment only as a deliberate compatibility choice, if at all.

UNIX domain sockets and the cost of flushing

The module is not limited to TCP ports. The README gives a UNIX domain socket example, and it shows a detail that matters for latency:

nginx
        content_by_lua_block {
            ngx.say("What's up?")
            ngx.flush(true)  -- flush any pending output and wait
            ngx.sleep(3)  -- sleeping for 3 sec
            ngx.say("Bye bye...")
        }

ngx.flush(true) pushes any pending output to the peer and waits, which is how a handler confirms the client received the first line before sleeping. Without an explicit flush, output sits in the buffer, and a client waiting for a prompt sees nothing. The same applies to a long lived connection where you want the peer to act on your instruction before you continue. This is the practical difference between writing a stream handler and writing an HTTP handler: there is no request completion to imply that the response has been delivered, so the flush and sleep calls are part of the protocol, not a performance tweak.

Not shipped with Nginx, so OpenResty is the low friction route

The README is explicit about one thing in the opening section: this module is not distributed with the Nginx source, and the installation instructions are a separate section of the document. The repository layout matches what you would expect for a standalone Nginx module, with a config directory, src/ for the C sources, t/ for the test suite, util/, a DEV_NOTES.md file and a valgrind.suppress file, which indicates the project is developed and validated as a patch against Nginx rather than as a library you link. The lower friction path is stated in the same paragraph: the module is a core component of OpenResty, and if you are using this module then you are essentially using OpenResty. So the practical decision is between installing OpenResty, which already contains the module, and carrying your own Nginx build with the module added, which you have to repeat for every Nginx upgrade. The README's own installation section is where the details live, and it is the section to read first before choosing.

Version v0.0.16, no tagged releases, and a BSD-2-Clause licence

Version numbering here is worth reading carefully. The document describes ngx_stream_lua v0.0.16, released on 17 Jan, 2025, and the status is stated as production ready. The version number reads like a pre-release marker even though the status says otherwise, and that combination is worth keeping in mind when you plan an upgrade path. The repository has no GitHub releases, so the version tags on the code repository are the only version reference, and the last push was on 2026-10-18, which is more recent than the documented v0.0.16 release, so the working tree is ahead of the version the README describes. Licensing is BSD-2-Clause, a permissive licence with attribution obligations and no copyleft requirement, which matters if you intend to patch the module and ship it inside your own Nginx distribution. The module also points to English and Chinese mailing lists and a bugs and patches location, so there is a route for reporting problems beyond the issue tracker.

Editorial conclusion

Adopt stream-lua-nginx-module if you need to write TCP or UDP server logic in Lua inside Nginx, especially for protocol inspection, TLS handshake work or a custom proxy decision, and take it from OpenResty so you are not maintaining your own Nginx build. Skip it if your traffic is plain HTTP, because ngx_http_lua already covers that and the stream module is a separate port with its own version number. Verify first that the v0.0.16 tag matches the features you need, since the module tracks ngx_http_lua directive by directive, and read the Nginx compatibility section in the README before assuming a directive you know from the HTTP module behaves identically in the stream context.

Frequently asked questions

What is the difference between ngx_stream_lua and ngx_http_lua?

ngx_stream_lua is a port of ngx_http_lua to the Nginx stream subsystem, so it can serve generic TCP and UDP clients. The README states that the available Lua APIs and Nginx directives remain the same as those of the HTTP module, so an HTTP handler has to be rewritten for a connection that has no request, headers or method.

Is stream-lua-nginx-module part of the standard Nginx distribution?

No. The README states that the module is not distributed with the Nginx source and points to its own installation instructions. It is a core component of OpenResty, so using the module means using OpenResty unless you maintain a separate Nginx build.

Which directives can run Lua in a stream server?

The ported set includes content_by_lua_block, preread_by_lua_block, balancer_by_lua_block, log_by_lua_block, the init_by_lua and init_worker_by_lua pairs, and the ssl_client_hello_by_lua and ssl_certificate_by_lua families. The README notes these are ported directly from ngx_http_lua and refers you to that module's documentation for usage details.

How do I read data from a TCP client in a Lua stream handler?

You request the downstream socket with ngx.req.socket(true) and then call sock:receive() to read a line, as shown in the README's SSL example. Output is written with ngx.say, and ngx.flush(true) pushes pending output to the peer and waits.

What licence does stream-lua-nginx-module use?

The project is licensed under BSD-2-Clause, a permissive licence that requires attribution but does not impose copyleft on code that links to or ships with it. The repository also carries a LICENSE file at its root.

Official sources

  1. Issues
  2. License: BSD-2-Clause
  3. openresty/stream-lua-nginx-module on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openresty-stream-lua-nginx-module.svg)](https://hysenlabs.com/projects/openresty-stream-lua-nginx-module)

Was this useful?