Nginx Lua Integration
Introduction
Nginx is renowned for its performance, stability, and extensive feature set as a web server and reverse proxy. While Nginx's static configuration files are powerful, sometimes you need more dynamic functionality. This is where Nginx Lua integration comes in.
Lua integration allows you to embed Lua code directly into your Nginx configuration, enabling dynamic request processing, complex routing logic, and even full web applications—all while maintaining Nginx's exceptional performance characteristics.
In this guide, we'll explore how to extend Nginx with Lua scripting to unlock powerful customizations for your web server.
Prerequisites
Before diving into Nginx Lua integration, you should have:
- Basic knowledge of Nginx configuration
- Familiarity with programming concepts (functions, variables, conditionals)
- A server with Nginx installed
Understanding OpenResty
While you can compile Nginx with Lua support manually, the most common approach is to use OpenResty—a distribution of Nginx that comes bundled with Lua support and many useful libraries.
OpenResty extends Nginx by bundling:
- The Lua JIT compiler
- The ngx_lua module
- Various Lua libraries specifically designed for Nginx
Installation
Let's start by installing OpenResty:
Ubuntu/Debian
# Add OpenResty repository
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo apt-get -y install software-properties-common
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"
sudo apt-get update
sudo apt-get -y install openresty
CentOS/RHEL
# Add OpenResty repository
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo yum install openresty
macOS
brew tap openresty/brew
brew install openresty
Once installed, you can verify your installation:
openresty -v
You should see something like:
nginx version: openresty/1.19.3.1
Basic Lua Integration
OpenResty provides several directives for embedding Lua code in your Nginx configuration:
content_by_lua_block
: Generates response content using Luaaccess_by_lua_block
: Runs during access phase for authentication/authorizationheader_filter_by_lua_block
: Modifies response headersbody_filter_by_lua_block
: Modifies response bodylog_by_lua_block
: Runs after request completion, ideal for logginginit_by_lua_block
: Runs when Nginx starts
Let's look at a simple example:
http {
server {
listen 80;
location /hello {
content_by_lua_block {
ngx.say("Hello, Lua in Nginx!")
}
}
}
}
When you request /hello
, Nginx will execute the Lua code and return "Hello, Lua in Nginx!".
The ngx.* API
OpenResty provides the ngx.*
API for interacting with Nginx functionality:
Accessing Request Data
location /request-info {
content_by_lua_block {
ngx.say("URI: ", ngx.var.uri)
ngx.say("Method: ", ngx.req.get_method())
ngx.say("User-Agent: ", ngx.req.get_headers()["User-Agent"])
-- Get query parameters
ngx.req.read_body() -- Read request body first
local args, err = ngx.req.get_uri_args()
if args.name then
ngx.say("Hello, ", args.name, "!")
else
ngx.say("Hello, anonymous user!")
end
}
}
Try accessing: /request-info?name=YourName
Output:
URI: /request-info
Method: GET
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Hello, YourName!
HTTP Response Control
location /status-code {
content_by_lua_block {
local code = ngx.var.arg_code or 200
ngx.status = code
ngx.header["X-Custom-Header"] = "Custom Value"
ngx.say("Responding with status code: ", code)
}
}
Try accessing: /status-code?code=404
Output (with status code 404):
Responding with status code: 404
External Lua Files
For better organization, you can store Lua code in separate files:
Create a file /etc/openresty/lua/greeter.lua
:
local _M = {}
function _M.greet(name)
if name then
return "Hello, " .. name .. "!"
else
return "Hello, anonymous user!"
end
end
return _M
Then in your Nginx configuration:
http {
# Set the search path for Lua modules
lua_package_path "/etc/openresty/lua/?.lua;;";
server {
listen 80;
location /greet {
content_by_lua_block {
local greeter = require "greeter"
local name = ngx.var.arg_name
ngx.say(greeter.greet(name))
}
}
}
}
Try accessing: /greet?name=World
Output:
Hello, World!
Practical Example: Simple Authentication
Let's create a simple authentication system using Lua:
Create a file /etc/openresty/lua/auth.lua
:
local _M = {}
-- In production, use a database or secure storage
local users = {
alice = {password = "secret1", role = "admin"},
bob = {password = "secret2", role = "user"}
}
function _M.authenticate(username, password)
local user = users[username]
if user and user.password == password then
return user
else
return nil, "Invalid username or password"
end
end
return _M
Now implement authentication in your Nginx config:
http {
lua_package_path "/etc/openresty/lua/?.lua;;";
server {
listen 80;
# Login endpoint
location /login {
content_by_lua_block {
ngx.req.read_body()
local args = ngx.req.get_post_args()
if not args.username or not args.password then
ngx.status = 400
ngx.say("Username and password required")
return
end
local auth = require "auth"
local user, err = auth.authenticate(args.username, args.password)
if user then
-- Set a simple cookie (not secure for production!)
ngx.header["Set-Cookie"] = "user=" .. args.username .. "; Path=/"
ngx.say("Login successful! Welcome, " .. args.username)
else
ngx.status = 401
ngx.say(err)
end
}
}
# Protected area
location /protected {
access_by_lua_block {
local cookie = ngx.var.cookie_user
if not cookie then
ngx.status = 401
ngx.say("Authentication required")
return ngx.exit(ngx.HTTP_OK)
end
local auth = require "auth"
local users = {
alice = {password = "secret1", role = "admin"},
bob = {password = "secret2", role = "user"}
}
if not users[cookie] then
ngx.status = 401
ngx.say("Invalid session")
return ngx.exit(ngx.HTTP_OK)
end
}
content_by_lua_block {
ngx.say("Welcome to the protected area, ", ngx.var.cookie_user, "!")
}
}
}
}
To test this:
- First login with a POST request:
curl -X POST "http://localhost/login" -d "username=alice&password=secret1" -c cookies.txt
Output:
Login successful! Welcome, alice
- Then access the protected area:
curl -b cookies.txt http://localhost/protected
Output:
Welcome to the protected area, alice!
Advanced Example: Rate Limiting
Let's implement custom rate limiting using Lua and shared memory:
http {
# Define a shared memory zone for rate limiting
lua_shared_dict my_limit_req_store 10m;
server {
listen 80;
location /api {
access_by_lua_block {
local limit_req = require "resty.limit.req"
-- Create a new rate limiter: 10 requests per second
local lim, err = limit_req.new("my_limit_req_store", 10, 10)
if not lim then
ngx.log(ngx.ERR, "failed to create limiter: ", err)
return ngx.exit(500)
end
-- Use the client IP as the key
local key = ngx.var.remote_addr
-- Check if this request exceeds the limit
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(429) -- Too Many Requests
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
-- Optional: you can actually delay the request if you want
if delay > 0 then
ngx.sleep(delay)
end
}
content_by_lua_block {
ngx.say("API response (rate limited to 10 req/sec)")
}
}
}
}
This implementation:
- Creates a shared memory zone to track request rates
- Limits requests to 10 per second per IP address
- Returns 429 Too Many Requests if the limit is exceeded
Database Interaction
Lua can connect to databases. Let's see a simple Redis example:
http {
lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;";
server {
listen 80;
location /counter {
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000) -- 1 second
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("Failed to connect to Redis: ", err)
return
end
-- Increment a counter
local count, err = red:incr("page_views")
if not count then
ngx.say("Failed to increment counter: ", err)
return
end
ngx.say("You are visitor number ", count)
-- Put connection back to pool
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.log(ngx.ERR, "Failed to set keepalive: ", err)
end
}
}
}
}
This example:
- Connects to a Redis server
- Increments a counter for each page view
- Returns the counter value to the visitor
Performance Considerations
While Lua in Nginx is fast, there are some best practices to follow:
-
Use connection pooling: For database connections, use connection pools to avoid the overhead of establishing new connections.
-
Avoid blocking operations: Nginx is event-driven; blocking operations will decrease performance.
-
Use shared memory wisely: Shared dictionaries are useful but limited in size.
-
Cache expensive operations: Use the
lua_shared_dict
to cache results of expensive operations.
Example of caching:
lua_shared_dict cache 10m;
location /cached-data {
content_by_lua_block {
local cache = ngx.shared.cache
local data = cache:get("my_data")
if data then
ngx.say("From cache: ", data)
return
end
-- Simulate expensive operation
ngx.sleep(1)
data = "This data was expensive to generate: " .. ngx.time()
-- Cache for 60 seconds
cache:set("my_data", data, 60)
ngx.say("Freshly generated: ", data)
}
}
Summary
Nginx Lua integration through OpenResty provides a powerful way to extend Nginx's capabilities with dynamic processing:
-
Key Benefits:
- Add dynamic processing to a high-performance web server
- Access Nginx internals through the
ngx.*
API - Create sophisticated request handling logic
- Build complete web applications within Nginx
-
Common Use Cases:
- Authentication and authorization
- Rate limiting and traffic control
- Content transformation and manipulation
- API gateways
- Dynamic configurations
-
Best Practices:
- Organize code in separate Lua modules
- Use connection pooling for databases
- Avoid blocking operations
- Cache expensive operations
- Be mindful of shared memory usage
Further Resources
To continue learning about Nginx Lua integration:
Exercises
-
Basic: Create a simple endpoint that returns the current time in different formats based on a query parameter.
-
Intermediate: Implement a request logging system that records details about requests to a specific endpoint in a Redis database.
-
Advanced: Create a simple API gateway that routes requests to different backend services based on the URL path and adds authentication.
-
Expert: Build a complete URL shortener service with Redis for storage and custom rate limiting for creation of new shortened URLs.
By integrating Lua with Nginx, you gain the flexibility of a programming language while maintaining Nginx's performance advantages. This combination is particularly powerful for creating API gateways, custom authentication systems, and specialized web applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)