lua-resty-redis: a Redis client that never blocks the event loop
Lua redis client driver for the ngx_lua based on the cosocket API
At a glance
- What is it?
- lua-resty-redis is the OpenResty Redis driver written against the ngx_lua cosocket API, so every command runs without blocking the nginx worker. Its two useful tricks are that command methods are generated on demand, which means Redis commands newer than the library still work, and that the connection lifecycle is explicit, with keepalive pooling handled by the caller rather than by a hidden client.
- Who is it for?
- lua-resty-redis fits an nginx or OpenResty service that needs Redis without adding a blocking client or a sidecar, and it fits a team that wants one API surface for every Redis command. It does not fit an application that is not already running inside nginx, and it does not fit a team that wants a client to handle connection pooling and reconnection for them.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Lua, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The client is the cosocket API in a different shape
lua-resty-redis is described as a Lua redis client driver for the ngx_lua nginx module, and the README says it is considered production ready.
The mechanism is the reason to use it rather than a generic Redis client. It takes advantage of ngx_lua's cosocket API, which ensures 100% nonblocking behavior. That matters more inside nginx than the phrase sounds, because a nginx worker is an event loop serving many requests at once, and a client that blocks on a socket read holds up every other request that worker is handling. A cosocket is the nginx-native way of talking to a remote service without stopping the loop.
The price is the environment. The library is a Lua library for the ngx_lua nginx module, and the README states that at least ngx_lua 0.5.14 or OpenResty 1.2.1.14 is required. If you are not already running nginx, there is no reason to reach for this, and if you are, the version floor is a real constraint for long-lived deployments.
The installation itself is a path entry when you are not using the OpenResty bundle, and the Makefile shows the supported layout, with the install target copying lib/resty/*.lua into the Lua library directory under a resty subdirectory, which is where require "resty.redis" finds it.
The connection model is the rest of the design. A client is created with redis:new(), connected to an address, and the connection is explicitly returned to a pool or closed. There is no background thread and no implicit reconnection, because in an event loop there is no thread to do either.
Command methods are generated, so new Redis commands work immediately
The API convention is simple enough to learn in a minute. All of the Redis commands have their own methods with the same name except all in lower case, and the arguments can be fed directly into the corresponding method call. The GET command accepts a single key argument, so you call the get method, and the LRANGE command accepts three arguments, so you call lrange with a key and two indexes.
local res, err = red:get("key")local res, err = red:lrange("nokey", 0, 1)The part that is more than a naming convention is that the methods are generated on demand, so every Redis command works this way, including commands newer than this library and multi-word commands, where the sub-command is just the first argument.
-- CLIENT SETNAME myapp
local res, err = red:client("setname", "myapp")
-- BITFIELD mykey GET u8 0
local res, err = red:bitfield("mykey", "get", "u8", 0)That is a genuine design advantage over a hand-written binding, where a Redis release that adds a command leaves you waiting for a library update. Here, a new command is available as soon as the server supports it, as long as its arguments fit the command and sub-command shape.
The return convention is uniform too. All these command methods return a single result on success and nil otherwise, and in case of errors or failures the method also returns a second value which is a string describing the error. That is the pattern every call site has to handle, and the hash operations, hmget and hmset, follow the same rule.
Pipelining returns one results table, and failures are per command
The pipeline API is the part of the library that most directly changes how you write a request handler, because it turns a sequence of round trips into one.
You call init_pipeline, issue the commands, and then commit_pipeline returns the results.
red:init_pipeline()
red:set("cat", "Marry")
red:set("horse", "Bob")
red:get("cat")
red:get("horse")
local results, err = red:commit_pipeline()
if not results then
ngx.say("failed to commit the pipelined requests: ", err)
return
end
for i, res in ipairs(results) do
if type(res) == "table" then
if res[1] == false then
ngx.say("failed to run command ", i, ": ", res[2])
else
-- process the table value
end
else
-- process the scalar value
end
endTwo details in that loop are worth understanding before you rely on it. A failed command inside a pipeline does not fail the pipeline. The commit succeeds and returns results, and the failure shows up as a table whose first element is false and whose second is the error message, which is why the loop checks the type and then the first element. And a successful command that returns a table value, a hash or an array, comes back as a table too, so the type check alone cannot distinguish success from failure.
The loop above is the pattern to copy. Anything else risks treating a failed command as a result, and because a pipeline returns a whole batch, one bad command can otherwise be lost in a table of successes.
Connection lifecycle is explicit, which is the point
The last thing the example does is return the connection to a pool:
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.say("failed to set keepalive: ", err)
return
endTen seconds of maximum idle time and a pool of size 100. Alternatively the connection can be closed right away with red:close().
That choice is not an implementation detail, it is the contract, and it is why the README has a whole section on error handling and the connection lifecycle. When an error occurs, the comment in the example says the connection is closed automatically when the request ends, and the point of set_keepalive is to avoid paying for a new TCP connection and a new authentication handshake on every request. A worker that never returns its connection to the pool will exhaust the server's client limit.
The error handling convention is the other half. Every call returns nil and an error string on failure, and the example checks before continuing, because a failed red:set leaves the connection in a state the rest of the handler must not trust.
The README's table of contents lists the sections that surround this contract, including Redis Authentication, Redis Transactions, Load Balancing and Failover, Debugging, Automatic Error Logging, a Check List for Issues and Limitations. Those headings are a good map of the ways this kind of client fails in production, and they are where you should look before operating it.
Resolvers, unix sockets and timeouts are nginx configuration
Because the driver is a cosocket client, the details of reaching Redis live in the nginx configuration rather than in Lua.
The example needs to specify the resolver to resolve the hostname, with a resolver directive pointing at a public DNS address, and then it can connect to an IP address directly or to a hostname. There is also the option to connect to a unix domain socket file listened by a redis server, with the connection string given as a unix path.
Timeouts are set on the client with set_timeouts, called with three values in the example for one second each, and the README's method list includes both set_timeout and set_timeouts, so a caller can set connect, send and read timeouts separately or together.
The consequence is that this library inherits the operational properties of nginx networking, including the requirement that a resolver be configured for name resolution, which is a frequent source of confusion for people coming from a client library that resolves names itself. There is also a security dimension: connecting by hostname means the worker performs DNS lookups on every new connection, and connecting by IP avoids that but hardcodes an address that may change.
The unix socket option is the one that changes the deployment story, because it removes both the DNS lookup and the TCP handshake from the connection path, at the cost of tying the nginx worker and Redis to the same host or a shared filesystem.
A stable version number, an active repository, and an unclassified licence
The release history in this repository is short and the last push is recent. The only listed release is v0.29 from 2020-10-09, and the last push to the repository was on 2026-10-18.
That gap is not a contradiction. A client library whose whole surface is generated from a command list has very little to change from release to release, and the version number here is not a marketing signal of activity in the way a feature release would be. The recent pushes are the more useful signal, and they are consistent with a mature library that is still maintained and still receives fixes.
The repository layout backs that reading. There is a t directory for the test suite, and the Makefile's test target runs it with prove, the Perl test harness, using the OpenResty test framework at../test-nginx/lib and pointing PATH at a debug OpenResty prefix. There is a valgrind.suppress file, a.luacheckrc for the Lua linter, and a dist.ini for packaging. Running the tests under a debug OpenResty build, with memory checking, is how you test a library that runs inside a server rather than as a standalone process.
The licence is the one loose end. The repository's licence metadata is not classified, and the README has a Copyright and License section. The project is part of the OpenResty family, but the file in this repository is what governs, and anyone embedding this in a commercial product should read it rather than infer it.
There is also a Community section with an English and a Chinese mailing list, and a Bugs and Patches section, which is the arrangement you would expect for a library with a long life and a wide install base.
Editorial conclusion
lua-resty-redis fits an nginx or OpenResty service that needs Redis without adding a blocking client or a sidecar, and it fits a team that wants one API surface for every Redis command. It does not fit an application that is not already running inside nginx, and it does not fit a team that wants a client to handle connection pooling and reconnection for them. Before you build on it, check the version you are pinning, because the only tagged release listed is v0.29 from 2020 while the repository is still receiving pushes, and read the Limitations section, since the library makes assumptions about how Redis is deployed.
Frequently asked questions
What is lua-resty-redis and what does it require?
It is a Lua Redis client driver for the ngx_lua nginx module, built on the cosocket API so that behavior is 100% nonblocking. The README states that at least ngx_lua 0.5.14 or OpenResty 1.2.1.14 is required, and that the library is considered production ready.
Does lua-resty-redis support Redis commands released after the library?
Yes. The command methods are generated on demand, so every Redis command works through the lower-case method, including commands newer than the library and multi-word commands where the sub-command is passed as the first argument, for example red:client("setname", "myapp").
How do I return a connection to the pool in lua-resty-redis?
Call set_keepalive with a maximum idle time in milliseconds and a pool size, for example red:set_keepalive(10000, 100) for ten seconds and a pool of 100. Alternatively close the connection immediately with red:close(). The README has a section on error handling and the connection lifecycle.
How does pipelining report a failed command in lua-resty-redis?
commit_pipeline returns the whole batch of results, and a command that failed comes back as a table whose first element is false and whose second is the error message, while a successful command returning a table value also comes back as a table. The README example checks the type and then the first element for each result.
How do I connect to Redis by hostname with lua-resty-redis?
You need to specify a resolver in the nginx configuration, because the cosocket API relies on nginx's resolver to resolve names, and then connect with the hostname and port. The README also shows connecting to an IP address directly, and connecting to a unix domain socket file listened by a redis server with a connection string of the form unix:/path/to/redis.sock.
Official sources
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.
[](https://hysenlabs.com/projects/openresty-lua-resty-redis)Was this useful?