The Lua NGINX module Rocky Linux 10 brings powerful scripting capabilities to your web server. This module embeds the LuaJIT interpreter directly into NGINX, enabling dynamic request processing, custom authentication, complex routing logic, and API gateway functionality without external dependencies.
This comprehensive guide shows you how to install and configure the Lua NGINX module on Rocky Linux 10, AlmaLinux 10, and other Enterprise Linux 10 distributions. You will learn basic Lua scripting, common use cases, and production patterns. By the end, you will have the Lua NGINX module Rocky Linux 10 ready for custom server-side logic.
Why Use the Lua NGINX Module
The Lua NGINX module transforms NGINX from a static web server into a programmable application platform. Key benefits include:
- High performance: LuaJIT compiles to native machine code for near-C performance
- Non-blocking I/O: Cosocket API enables asynchronous operations without blocking workers
- Flexible request handling: Modify requests and responses at any processing phase
- Custom authentication: Implement OAuth, JWT validation, or custom auth schemes
- API gateway features: Rate limiting, request transformation, and routing logic
- No external processes: Everything runs inside NGINX worker processes
The module powers OpenResty, a popular web platform used by companies like Cloudflare, Kong, and Adobe. Installing it standalone provides the same capabilities with a simpler setup.
Prerequisites
Before installing the Lua NGINX module Rocky Linux 10 packages, ensure you have:
- Rocky Linux 10, AlmaLinux 10, RHEL 10, or Oracle Linux 10
- Root or sudo access to the server
- NGINX installed (or we will install it in this guide)
The GetPageSpeed repository provides pre-built Lua modules with all dependencies included.
Installation
Step 1: Configure GetPageSpeed Repository
The GetPageSpeed repository provides enterprise-grade NGINX modules including the Lua module. Install the repository:
sudo dnf -y install https://extras.getpagespeed.com/release-latest.rpm
Step 2: Install NGINX
If NGINX is not already installed:
sudo dnf -y install nginx
sudo systemctl enable --now nginx
Step 3: Install Lua Module
Install the NGINX Lua module:
sudo dnf -y install nginx-module-lua
This installs several components:
- nginx-module-lua: The main Lua module
- nginx-module-ndk: NGINX Development Kit (required dependency)
- lua5.1-resty-core: Core Lua library for NGINX
- lua5.1-resty-lrucache: LRU cache library
After installation, you will see:
----------------------------------------------------------------------
The nginx-module-lua has been installed.
To enable this module, add the following to /etc/nginx/nginx.conf
and reload nginx:
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;
----------------------------------------------------------------------
Step 4: Enable the Module
Add the module loading directives at the top of your NGINX configuration. The NDK module must load before the Lua module:
# /etc/nginx/nginx.conf
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;
user nginx;
worker_processes auto;
# ... rest of configuration
Step 5: Basic Configuration
Add a simple Lua endpoint to test the module:
http {
server {
listen 80;
location /lua {
default_type text/plain;
content_by_lua_block {
ngx.say("Hello from Lua!")
ngx.say("NGINX version: " .. ngx.var.nginx_version)
}
}
}
}
Test and reload:
sudo nginx -t && sudo systemctl reload nginx
Verify the module works:
curl http://localhost/lua
Output:
Hello from Lua!
NGINX version: 1.28.1
Understanding Processing Phases
The Lua module provides handlers for different NGINX processing phases:
| Phase | Directive | Purpose |
|---|---|---|
| init | init_by_lua_block | Global initialization at startup |
| init_worker | init_worker_by_lua_block | Per-worker initialization |
| set | set_by_lua_block | Set NGINX variables |
| rewrite | rewrite_by_lua_block | URL rewriting and redirection |
| access | access_by_lua_block | Authentication and access control |
| content | content_by_lua_block | Generate response content |
| header_filter | header_filter_by_lua_block | Modify response headers |
| body_filter | body_filter_by_lua_block | Modify response body |
| log | log_by_lua_block | Custom logging |
Each phase runs at a specific point in request processing. Choose the appropriate phase for your logic.
Common Use Cases
Custom Authentication
Implement basic authentication with Lua:
location /protected {
access_by_lua_block {
local auth = ngx.var.http_authorization
if not auth then
ngx.header["WWW-Authenticate"] = 'Basic realm="Restricted"'
ngx.exit(401)
end
local encoded = string.match(auth, "Basic%s+(.+)")
if not encoded then
ngx.exit(403)
end
local decoded = ngx.decode_base64(encoded)
local user, pass = string.match(decoded, "(.+):(.+)")
if user ~= "admin" or pass ~= "secret" then
ngx.exit(403)
end
}
proxy_pass http://backend;
}
JWT Token Validation
Validate JWT tokens without external services:
location /api {
access_by_lua_block {
local jwt = require "resty.jwt"
local auth = ngx.var.http_authorization
if not auth then
ngx.status = 401
ngx.say('{"error": "No token provided"}')
ngx.exit(401)
end
local token = string.match(auth, "Bearer%s+(.+)")
local secret = "your-secret-key"
local jwt_obj = jwt:verify(secret, token)
if not jwt_obj.verified then
ngx.status = 401
ngx.say('{"error": "Invalid token"}')
ngx.exit(401)
end
ngx.var.user_id = jwt_obj.payload.sub
}
proxy_pass http://backend;
}
Note: This requires the lua-resty-jwt library.
Dynamic Rate Limiting
Implement custom rate limiting with shared memory:
http {
lua_shared_dict rate_limit 10m;
server {
location /api {
access_by_lua_block {
local limit = ngx.shared.rate_limit
local key = ngx.var.remote_addr
local requests = limit:incr(key, 1, 0, 60)
if requests > 100 then
ngx.status = 429
ngx.header["Retry-After"] = 60
ngx.say("Rate limit exceeded")
ngx.exit(429)
end
}
proxy_pass http://backend;
}
}
}
Request Logging to External Service
Send request data to an analytics service:
location / {
log_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local data = {
uri = ngx.var.uri,
status = ngx.var.status,
time = ngx.var.request_time,
ip = ngx.var.remote_addr
}
httpc:request_uri("http://analytics.internal/log", {
method = "POST",
body = require("cjson").encode(data),
headers = { ["Content-Type"] = "application/json" }
})
}
proxy_pass http://backend;
}
Response Modification
Inject content into HTML responses:
location / {
body_filter_by_lua_block {
local chunk = ngx.arg[1]
if chunk then
chunk = string.gsub(chunk, "</body>",
'<script src="/analytics.js"></script></body>')
ngx.arg[1] = chunk
end
}
proxy_pass http://backend;
}
Dynamic Upstream Selection
Route requests based on custom logic:
http {
upstream backend_a {
server 10.0.0.1:8080;
}
upstream backend_b {
server 10.0.0.2:8080;
}
server {
location /api {
set $backend "";
rewrite_by_lua_block {
local user_type = ngx.var.cookie_user_type
if user_type == "premium" then
ngx.var.backend = "backend_a"
else
ngx.var.backend = "backend_b"
end
}
proxy_pass http://$backend;
}
}
}
Shared Memory Dictionaries
The Lua module provides shared memory for data sharing across workers:
http {
# Allocate 10MB for shared data
lua_shared_dict my_cache 10m;
server {
location /cache {
content_by_lua_block {
local cache = ngx.shared.my_cache
-- Set with 60 second expiration
cache:set("key", "value", 60)
-- Get value
local value = cache:get("key")
-- Increment counter
local count = cache:incr("visits", 1, 0)
ngx.say("Value: ", value)
ngx.say("Visits: ", count)
}
}
}
}
Shared dictionaries support:
get(key)– Retrieve valueset(key, value, exptime)– Store valueincr(key, value, init, init_ttl)– Atomic incrementdelete(key)– Remove keyflush_all()– Clear all data
Asynchronous Operations
The cosocket API enables non-blocking network operations:
location /fetch {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
-- Non-blocking HTTP request
local res, err = httpc:request_uri("http://api.example.com/data")
if not res then
ngx.status = 502
ngx.say("Failed: ", err)
return
end
ngx.say(res.body)
}
}
Install the HTTP client library:
sudo dnf -y install lua5.1-resty-http
Debugging and Logging
Use NGINX error log for debugging:
content_by_lua_block {
ngx.log(ngx.ERR, "Debug message")
ngx.log(ngx.WARN, "Variable value: ", ngx.var.uri)
}
View logs:
sudo tail -f /var/log/nginx/error.log
Performance Considerations
Optimize Lua code for production:
- Pre-compile code: Use
init_by_lua_blockfor one-time operations - Cache connections: Reuse database and HTTP connections
- Use shared dictionaries: Avoid repeated calculations
- Minimize string operations: Use table.concat for building strings
- Avoid global variables: Use local variables for better performance
Example of optimized initialization:
http {
init_by_lua_block {
-- Load modules once at startup
cjson = require "cjson"
http = require "resty.http"
}
server {
location /api {
content_by_lua_block {
-- Use pre-loaded modules
local data = cjson.decode(ngx.req.get_body_data())
ngx.say(cjson.encode({ status = "ok" }))
}
}
}
}
Troubleshooting
Module Not Loading
Symptom: unknown directive "content_by_lua_block"
Solutions:
- Verify both modules are loaded (NDK must come first)
- Check module files exist:
ls /usr/lib64/nginx/modules/ - Ensure directives are in the correct context
Lua Errors
Symptom: 500 errors with Lua stack traces
Solutions:
- Check error log:
tail /var/log/nginx/error.log - Wrap code in pcall for graceful error handling
- Verify required libraries are installed
Memory Issues
Symptom: Worker processes consuming excessive memory
Solutions:
- Set memory limits:
lua_max_running_timers 256 - Use shared dictionaries instead of per-request tables
- Explicitly clear large variables
SELinux Compatibility
The nginx-module-lua package from GetPageSpeed works correctly with SELinux in enforcing mode. No additional policy configuration is required.
Complete Production Configuration
Here is a production-ready Lua NGINX module Rocky Linux 10 configuration:
# /etc/nginx/nginx.conf
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Shared memory for rate limiting and caching
lua_shared_dict rate_limit 10m;
lua_shared_dict cache 50m;
# Pre-load modules at startup
init_by_lua_block {
cjson = require "cjson"
}
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api {
default_type application/json;
content_by_lua_block {
ngx.say(cjson.encode({
status = "ok",
server = ngx.var.server_name,
time = ngx.now()
}))
}
}
}
}
Test and apply:
sudo nginx -t && sudo systemctl reload nginx
Related Articles
For more NGINX configuration guides:
Conclusion
The Lua NGINX module Rocky Linux 10 extends NGINX into a programmable application platform. With the GetPageSpeed repository, installation includes all dependencies and works immediately with SELinux enabled.
The module enables custom authentication, dynamic routing, rate limiting, and API gateway features without external services. LuaJIT provides excellent performance, and the non-blocking cosocket API handles concurrent operations efficiently.
Start with simple content handlers and gradually add complexity as needed. The processing phase system gives precise control over when code executes during request handling. For production deployments, pre-load modules at startup and use shared dictionaries for data persistence across requests.

