lua-resty-upstream-healthcheck turns Nginx upstream peers on and off from Lua
Health Checker for Nginx Upstream Servers in Pure Lua
At a glance
- What is it?
- lua-resty-upstream-healthcheck is a pure Lua library, shipped with OpenResty since 1.9.3.2, that runs active periodic health checks against the servers in an Nginx upstream group and marks failing peers down after a configurable number of failures. It exposes a plain text status page and a Prometheus metrics endpoint.
- Who is it for?
- Adopt lua-resty-upstream-healthcheck if you run OpenResty and need upstream peers taken out of rotation on their own, since the library ships with OpenResty and needs no extra dependency. Skip it if you are on plain Nginx with no Lua module, or if your platform already provides active health checks through a module you have standardised on.
- 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
What the library actually does inside Nginx
The library performs health checks for server peers defined in Nginx upstream groups that are named by the configuration. It does this with pure Lua, with no external service and no sidecar. The mechanism is a set of background timer-based light threads spawned at worker start, and the key operational property is that the health checker does not need any client traffic to function. The checks are performed actively and periodically, so a peer is marked down whether or not anyone is currently sending requests through the upstream group. The upstream servers themselves are ordinary Nginx upstream entries, and the library's job is to keep Nginx's own peer selection honest by failing peers out after a threshold and restoring them after a different threshold. The README describes the status as still under early development but already production ready, which is a combination worth taking at face value for a library of this age and lineage.
spawn_checker and the option set
The single entry point is spawn_checker, called in an init_worker_by_lua context, and it returns immediately because it is asynchronous, returning true on success or nil and a string describing an error otherwise. The options object is where the behaviour lives. The shm names the shared dictionary zone defined by lua_shared_dict, and upstream names the upstream group to monitor. The type is either http or https. The http_req is the raw HTTP request used for checking, which lets you point the check at a specific status endpoint. The port can differ from the original backend server port, and leaving it nil means the same as the original backend server. The interval is the check cycle in milliseconds, the timeout is the network operation timeout in milliseconds, and the concurrency option sets the concurrency level for test requests. There are also two https-only options, ssl_verify and host, both commented out by default.
fall, rise and the two thresholds that define the behaviour
The two most consequential settings are fall and rise. Fall is the number of successive failures before a peer is turned down, and rise is the number of successive successes before a peer is turned up. The example configuration uses fall = 3 and rise = 2, so three consecutive failures take a peer out and two consecutive successes put it back, and the two values are deliberately different so that a flapping backend does not get re-admitted on a single lucky response. The valid_statuses option is a list of HTTP status codes that count as healthy, and the example accepts 200 and 302, which is a common choice when a backend redirects to a canonical URL or to a login page. The fact that the status list is configurable rather than hardcoded to 200 is what makes the checker usable against real applications, where a health endpoint behind a proxy frequently answers with a redirect.
The status page and the Prometheus endpoint
Two observability endpoints are provided, and both are worth turning on before you trust the checker in production. The status_page method generates a detailed status report for all upstreams defined in the current Nginx server, and it can be called from any context. Its output groups peers under each upstream name, with primary peers and backup peers listed separately and each marked UP or DOWN. An upstream with no health checkers is marked with the text (NO checkers), which is a useful signal that a group has been declared but never wired to a checker. The prometheus_status_page method returns the same information in Prometheus format, and the example location prints it as plain text, so it can be scraped by a Prometheus instance without any extra exporter. Both locations in the example disable the access log, and the plain status page additionally restricts access with allow 127.0.0.1 and deny all.
Multiple upstream groups and shared memory sizing
Health checks on several upstream groups are done by calling spawn_checker multiple times in the init_worker_by_lua handler, one call per upstream group. The README explains why that is safe. Different upstreams' health checkers use different keys, always prefixed with the upstream name, so sharing a single lua_shared_dict among multiple checkers has no issues at all. The caveat is size. You need to compensate the size of the shared dict for multiple users, and the README says that with many upstreams, thousands or more, it is more optimal to use separate shm zones for each group of upstreams. The example config sizes the zone at 1m and comments that the size depends on the number of servers in the upstream group. That comment is the practical guidance: the memory you allocate has to cover the peers of every group that shares the zone.
Installing it and the OpenResty shortcut
Installation is usually a non-event, and the README leads with that. If you are using OpenResty 1.9.3.2 or later, you already have this library and all of its dependencies installed by default, and that is the recommended way of using it. For anything else, the library has to be compiled, and the repository ships a Makefile for that. The install target copies the Lua files into the resty/upstream directory of the Lua library path.
install: all
$(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/upstream/
$(INSTALL) lib/resty/upstream/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/upstream/The Makefile defaults to OPENRESTY_PREFIX=/usr/local/openresty and a PREFIX of /usr/local, with the Lua include and library directories derived from the prefix and the Lua version. The test target runs the test suite with prove against the OpenResty nginx binary on the PATH, and the lint target runs lua-format over the files in lib. In the Nginx configuration the library is then found through lua_package_path, pointing at the lib directory of the checkout in the example.
Where this is the wrong tool
Three limits. The first is the environment. This is a Lua library for OpenResty or an Nginx with the Lua module, and the whole design depends on init_worker_by_lua, lua_shared_dict and cosocket access. If your Nginx is the stock build without Lua, none of it applies and you are back to a health check module or an external checker such as a Consul or Kubernetes probe driving a balancer. The second is the scope of the check. The checker sends a fixed raw HTTP request and looks at the status code against valid_statuses, and that is a liveness and readiness answer at the HTTP level. It does not verify that a database the backend depends on is reachable, and it does not perform a functional check of the application logic behind the endpoint. The third is that the failure mode is quiet. The README is explicit that if you have spawned a health checker in init_worker_by_lua you should really check out the Nginx error log file to see whether any fatal errors are aborting the health checker threads. The status page showing all peers UP does not distinguish a working checker from a dead one, which is exactly why the log check belongs in your runbook.
Editorial conclusion
Adopt lua-resty-upstream-healthcheck if you run OpenResty and need upstream peers taken out of rotation on their own, since the library ships with OpenResty and needs no extra dependency. Skip it if you are on plain Nginx with no Lua module, or if your platform already provides active health checks through a module you have standardised on. Before you enable it, size the lua_shared_dict zone for the number of upstreams you check and read the Nginx error log, because a health checker thread that aborts leaves no trace in the status page.
Frequently asked questions
What does lua-resty-upstream-healthcheck do?
It performs active, periodic health checks for server peers in named Nginx upstream groups, spawning background timer-based light threads in init_worker_by_lua that mark a peer down after a configurable number of successive failures and up after a configurable number of successes.
Do I need to install lua-resty-upstream-healthcheck separately?
Not if you use OpenResty 1.9.3.2 or later, which ships the library and all its dependencies by default, and which the README calls the recommended way of using it. Otherwise you compile it, with the Makefile's install target copying the files into the resty/upstream directory of the Lua library path.
What do the fall and rise options control?
Fall is the number of successive failures before a peer is turned down, and rise is the number of successive successes before a peer is turned up. The example uses fall = 3 and rise = 2, so a peer leaves rotation after three consecutive failures and returns after two consecutive successes.
How do I expose the health check status of an Nginx upstream?
Call hc.status_page() from a content_by_lua_block for a plain text report that lists primary and backup peers with UP or DOWN, or hc.prometheus_status_page() for a Prometheus-formatted version. An upstream with no checkers is reported as (NO checkers).
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-upstream-healthcheck)Was this useful?