# lua-resty-http: an HTTP client for OpenResty that lives inside the nginx worker

> lua-resty-http is a Lua HTTP client cosocket driver for OpenResty and ngx_lua, offering both a single-shot buffered interface and a streaming interface for large response bodies. It runs only inside nginx worker processes, which is both its main constraint and the reason it integrates directly with OpenResty's connection pool and SSL handling.

**ledgetech/lua-resty-http** — Lua HTTP client cosocket driver for OpenResty / ngx_lua.

- Repository: https://github.com/ledgetech/lua-resty-http
- Stars: 2,079 · Forks: 630
- Language: Lua
- License: BSD-2-Clause
- Published: 2026-10-11 · Updated: 2026-10-11 · Language: en
- Canonical page: https://hysenlabs.com/projects/ledgetech-lua-resty-http

## cosocket ties the driver to nginx workers, not to standalone Lua programs

lua-resty-http depends on OpenResty's cosocket API, which is a non-blocking socket implementation built into the LuaJIT VM running inside nginx workers. Cosocket gives Lua code access to TCP and UDP connections without blocking the nginx event loop, and that property is what makes the library's HTTP calls non-blocking.

A direct consequence follows: this library cannot run in a plain Lua script, a cron job, or any context outside an nginx worker. Every require("resty.http") call and every httpc:request_uri() call happens inside a request-handling location block, an access phase, a content phase, or a background timer fired from within nginx. Engineers who are not already running OpenResty or an nginx build with ngx_lua cannot use this library without first deploying that infrastructure.

For teams already on OpenResty, that is not an extra cost. Every OpenResty application already has the event loop, LuaJIT and cosocket available. What the library provides is an HTTP client that participates in that event loop rather than blocking it.

## request_uri buffers the full body; request() leaves it on the wire until you read it

Two basic modes of operation are available. In the first, request_uri handles the complete cycle: one call that buffers the entire response and returns status, headers and body together. In the second, connection setup is done separately with connect(), the request is sent with request(), and the body is read chunk by chunk with a body_reader iterator.

A single-shot request looks like this:

```lua
local httpc = require("resty.http").new()

local res, err = httpc:request_uri("http://example.com/helloworld", {
    method = "POST",
    body = "a=1&b=2",
    headers = {
        ["Content-Type"] = "application/x-www-form-urlencoded",
    },
})
if not res then
    ngx.log(ngx.ERR, "request failed: ", err)
    return
end

local status = res.status
local length = res.headers["Content-Length"]
local body   = res.body
```

After that call, the full request/response cycle is complete and the connection is either closed or returned to the pool. Choosing between the two modes is a memory decision. Buffering a large response holds it in memory until the variable is garbage collected. Streaming lets you discard the body after processing each chunk, which the library documents as providing the ability to discard potentially large response bodies on the Lua side.

## luarocks install requires an explicit version number and a separate lua-resty-openssl

Installation uses a two-part command:

```
luarocks install lua-resty-http <version> && luarocks install lua-resty-openssl
```

Two details in that command matter. First, `<version>` is a placeholder, not a literal argument. Running the command without substituting a real version string will fail. From the rockspec file at the repository root, which is named `lua-resty-http-0.18.0-0.rockspec`, the current version string in LuaRocks format is `0.18.0-0`. Appending the version avoids migration conflicts, which is why the install command requires it rather than letting LuaRocks pick the latest.

Second, lua-resty-openssl is a prerequisite for all HTTP connections, including plain HTTP. Installing lua-resty-http without it is not enough. Looking at the Dockerfile, which runs in openresty/openresty:1.31.1.1-alpine-fat, the luarocks install for lua-resty-openssl is a separate step.

OPM (OpenResty Package Manager) had a package at opm.openresty.org, but it is explicitly described as dead: no new versions can be uploaded there. Any installation from OPM will be stuck at a version older than v0.18.0. Migrating to luarocks is the documented path.

## connect() builds a pool name from SSL and proxy parameters to prevent cross-pool leaks

connect() handles TCP connection, SSL handshake and HTTP proxy configuration in one call, and it constructs a connection pool name from the combination of scheme, host, port, SSL settings and proxy configuration. That automatic naming is why mixing SSL and non-SSL connections in the same pool does not happen by default: each distinct combination of those parameters gets its own pool.

Overriding that name is possible with the pool option, but the documentation is direct: "When in doubt, leave it blank." Setting a custom pool name bypasses the safety logic and can produce a pool that serves connections with mismatched SSL states.

Two other options are worth checking. ssl_verify defaults to true, which differs from many HTTP client libraries that default to false. pool_debug, when set to true, logs the pool name at DEBUG level, which is useful for verifying that connections land in the expected pool. connect() returns three values:

```lua
local ok, err, ssl_session = httpc:connect({
    scheme = "https",
    host = "127.0.0.1",
    port = 8080,
})
```

Callers who need session reuse can capture that third value. Callers who do not need it can simply ignore the return position.

## body_reader iterates over chunks and must be drained before calling set_keepalive

In streaming mode, once request() returns successfully, status and headers are available immediately in the response table, but the body remains unread on the wire. body_reader is an iterator that reads one chunk at a time at a caller-specified buffer size:

```lua
local reader = res.body_reader
local buffer_size = 8192

repeat
    local buffer, err = reader(buffer_size)
    if err then
        ngx.log(ngx.ERR, err)
        break
    end

    if buffer then
        -- process
    end
until not buffer

local ok, err = httpc:set_keepalive()
```

set_keepalive() must come after the loop ends, not before. Returning a connection to the pool while the body is still unread would corrupt the next request that picks up that connection. Streaming gives you memory control at the cost of that responsibility.

Trailers, if the server sends them, are accessible through read_trailers() on the response object after the body has been fully read. For multiple back-to-back requests to the same backend, request_pipeline() sends them over a single connection and returns the responses in order.

## mTLS and W3C trace context each require a minimum version of the underlying module

Two features carry explicit version requirements. mTLS support uses ssl_client_cert and ssl_client_priv_key in the connect() options table, and both require ngx_lua_http_module at v0.10.23 or later. Setting those options on an older ngx_lua build will not work.

W3C trace context propagation via the traceparent header was added in v0.18.0. Teams upgrading from v0.17.x who want distributed tracing support need v0.18.0 specifically.

For context on the gap between releases: v0.17.2 was dated 2024-02-29, and v0.18.0 arrived on 2026-07-06. A deployment that has been running v0.17.x has been on that version for over two years. Anyone planning that upgrade should read the v0.18.0 release notes, since a two-year gap between releases can mean behavioural changes beyond the listed new features.

## Four deprecated methods mark the boundary between the old TCP-only API and the current one

Four methods are marked as deprecated: connect_proxy, ssl_handshake, proxy_request and proxy_response. They may be removed in future versions.

connect_proxy and ssl_handshake belong to the older TCP-only connection pattern, where connect() was called with just a host and port and ssl_handshake() was a separate step. That pattern is now described as strongly discouraged in favour of the current connect() options table, which handles SSL and proxy setup together and builds a safe pool name automatically. Codebases that still call httpc:connect(host, port) followed by httpc:ssl_handshake() should migrate to the options-table form.

proxy_request and proxy_response are a higher-level pair for forwarding an incoming nginx request to a backend and returning the response. Neither has a documented replacement method. Their deprecation is stated without an alternative, so any code relying on them will need its own replacement strategy before those methods are removed.

One naming detail that affects package discovery: on LuaRocks, the package is listed under the username pintsized, not ledgetech. The URL in the installation instructions confirms this: https://luarocks.org/modules/pintsized/lua-resty-http. A search on LuaRocks under the ledgetech organization name will not find it.

## Conclusion

lua-resty-http suits any OpenResty deployment where you need HTTP client calls from inside nginx workers, whether to upstream APIs, internal services or external hosts over SSL or mTLS. Skip it if you are writing a standalone Lua program that runs outside nginx, since cosocket is unavailable there. Avoid setting a custom pool name in connect() unless you understand OpenResty connection pool semantics, because the module's own pool naming prevents SSL connections from leaking into non-SSL pools. Before using mTLS or the traceparent header, verify that your ngx_lua version meets the floors documented for each feature.

## FAQ

### How do I install lua-resty-http?

Run luarocks install lua-resty-http with an explicit version number, then run luarocks install lua-resty-openssl separately, since it is a prerequisite for all connections. OPM is dead and no longer receives updates.

### Can lua-resty-http be used outside of OpenResty?

No. It depends on the cosocket API, which is only available inside nginx worker processes running OpenResty or an nginx build with ngx_lua. Running it in a standalone Lua script is not possible.

### What is the difference between request_uri and request in lua-resty-http?

request_uri is a single-shot call that buffers the entire response body and handles connection management automatically. request() is used in streaming mode: call connect() first, then request(), then read the body in chunks with the body_reader iterator.

### Does lua-resty-http support mTLS?

Yes, by passing ssl_client_cert and ssl_client_priv_key in the connect() options table. Both require ngx_lua_http_module at version v0.10.23 or later.

### What version of lua-resty-http added W3C trace context support?

W3C trace context propagation via the traceparent header was added in v0.18.0, released on 2026-07-06.

## Sources

- [Issues](https://github.com/ledgetech/lua-resty-http/issues)
- [ledgetech/lua-resty-http on GitHub](https://github.com/ledgetech/lua-resty-http)
- [License: BSD-2-Clause](https://github.com/ledgetech/lua-resty-http/blob/master/LICENSE)
- [README](https://github.com/ledgetech/lua-resty-http/blob/master/README.md)
- [Releases](https://github.com/ledgetech/lua-resty-http/releases)

---

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