Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large tech infrastructures with ease.
Nginx is a powerhouse for serving web content, but did you know you can extend its functionality with Lua scripts? The OpenResty bundle, which embeds Lua into Nginx via the ngx_lua module, lets you write custom logic to handle requests, manipulate responses, and more. This tutorial dives into practical examples to show you how to use Lua scripts in Nginx, complete with working code and clear explanations.
If you're new to this, don’t worry—I’ll keep it straightforward and easy to understand. By the end, you’ll know how to set up Lua with Nginx, write scripts, and apply them to real-world scenarios. Let’s get started.
Why Lua with Nginx? Understanding the Power Combo
Lua is a lightweight, embeddable scripting language, and when paired with Nginx through OpenResty, it unlocks dynamic request processing without sacrificing performance. Unlike traditional CGI or FastCGI, Lua runs directly in Nginx’s event loop, keeping things fast.
Here’s what you can do:
- Rewrite URLs dynamically based on logic.
- Authenticate requests using external APIs or databases.
- Modify headers or content on the fly.
- Rate-limit or cache responses with custom rules.
Feature | Benefit | Example Use Case |
---|---|---|
Dynamic Rewriting | Alter URLs based on conditions | Redirect users by geo-location |
Content Modification | Inject or transform response data | Add custom analytics scripts |
Access Control | Validate requests | API key verification |
To follow along, you’ll need OpenResty installed. It’s a superset of Nginx with the Lua module baked in. Check the OpenResty installation guide for setup steps.
Setting Up OpenResty for Lua Scripting
First, let’s get your environment ready. Install OpenResty (available for Linux, macOS, and Windows via WSL). On Ubuntu, it’s as simple as:
sudo apt-get update
sudo apt-get install -y openresty
Verify it’s running:
openresty -v
# Output: openresty/1.x.x
Next, configure Nginx to recognize Lua scripts. OpenResty uses the ngx_lua module, which provides directives like lua_code_cache
and content_by_lua_block
. Here’s a minimal Nginx config to test:
# /usr/local/openresty/nginx/conf/nginx.conf
http {
server {
listen 80;
server_name localhost;
location /hello {
content_by_lua_block {
ngx.say("Hello, Lua!")
}
}
}
}
Restart OpenResty:
sudo openresty -s reload
Visit http://localhost/hello
in your browser. You should see Hello, Lua!. This confirms Lua is working.
Your First Lua Script: Dynamic Response
Let’s write a simple Lua script to return a dynamic response based on query parameters. This example grabs a name
parameter from the URL and responds with a greeting.
# /usr/local/openresty/nginx/conf/nginx.conf
http {
server {
listen 80;
server_name localhost;
location /greet {
content_by_lua_block {
local name = ngx.var.arg_name or "Guest"
ngx.say("Hello, " .. name .. "!")
-- Output: Hello, [name]! (e.g., Hello, Alice! if ?name=Alice)
}
}
}
}
Reload OpenResty and visit http://localhost/greet?name=Alice
. You’ll see Hello, Alice!. Without a name
parameter, it defaults to Hello, Guest!.
Key points:
-
ngx.var.arg_name
accesses query parameters. -
ngx.say
sends output to the client. - The
..
operator concatenates strings in Lua.
Rewriting URLs with Lua Logic
URL rewriting is a common task. Let’s create a script that redirects users based on their User-Agent. If the request comes from a mobile device, we’ll redirect to a mobile site.
http {
server {
listen 80;
server_name localhost;
location / {
rewrite_by_lua_block {
local ua = ngx.var.http_user_agent or ""
if string.match(ua, "Mobile") then
ngx.redirect("/mobile", 302)
end
-- No output; redirects if mobile, else continues
}
}
location /mobile {
content_by_lua_block {
ngx.say("Welcome to the mobile site!")
-- Output: Welcome to the mobile site!
}
}
}
}
Test it by curling with a mobile User-Agent:
curl -A "Mozilla/5.0 (iPhone)" http://localhost/
# Output: Welcome to the mobile site!
Key points:
-
rewrite_by_lua_block
runs during the rewrite phase. -
ngx.redirect(url, status)
sends a redirect response. - Use
string.match
for pattern matching in Lua.
Authenticating Requests with Lua
Let’s build an API key authentication system. This script checks for a valid X-API-Key
header before allowing access.
http {
server {
listen 80;
server_name localhost;
location /api {
access_by_lua_block {
local key = ngx.var.http_x_api_key
if key ~= "secret123" then
ngx.status = 403
ngx.say("Invalid API key")
ngx.exit(403)
-- Output: Invalid API key (if key is wrong)
end
}
content_by_lua_block {
ngx.say("Access granted!")
-- Output: Access granted! (if key is correct)
}
}
}
}
Test with curl:
curl -H "X-API-Key: secret123" http://localhost/api
# Output: Access granted!
curl -H "X-API-Key: wrong" http://localhost/api
# Output: Invalid API key
Key points:
-
access_by_lua_block
runs during the access phase. -
ngx.exit(status)
stops processing after setting a response. - The
~=
operator checks for inequality in Lua.
For production, you’d likely query a database or external service for key validation. The lua-resty-http library can help with HTTP requests.
Modifying Response Content Dynamically
Let’s inject content into responses. This example adds a custom footer to HTML responses.
http {
server {
listen 80;
server_name localhost;
location /page {
content_by_lua_block {
local html = [[
<html>
<body>
<h1>Welcome</h1>
</body>
</html>
]]
ngx.say(html)
-- Output: HTML content (before footer)
}
body_filter_by_lua_block {
local data = ngx.arg[1] or ""
local footer = "<footer>Powered by Lua</footer></html>"
if string.match(data, "</body>") then
data = string.gsub(data, "</body>", footer)
end
ngx.arg[1] = data
-- Output: HTML with footer added
}
}
}
}
Visit http://localhost/page
. You’ll see the HTML with Powered by Lua in a footer.
Key points:
-
body_filter_by_lua_block
modifies response bodies. -
ngx.arg[1]
holds the response chunk; modify it to change output. -
string.gsub
replaces substrings in Lua.
Rate Limiting with Lua and Shared Memory
Rate limiting is crucial for APIs. Let’s implement a simple rate limiter using Lua’s shared memory dictionary.
http {
lua_shared_dict rate_limit 10m; # Define shared memory
server {
listen 80;
server_name localhost;
location /limit {
access_by_lua_block {
local dict = ngx.shared.rate_limit
local key = ngx.var.remote_addr
local limit = 5 -- Requests per minute
local window = 60 -- Seconds
local count = dict:get(key) or 0
if count >= limit then
ngx.status = 429
ngx.say("Rate limit exceeded")
ngx.exit(429)
-- Output: Rate limit exceeded (if limit hit)
end
dict:incr(key, 1, 0, window)
-- No output; allows request if under limit
}
content_by_lua_block {
ngx.say("Request allowed")
-- Output: Request allowed
}
}
}
}
Test by sending multiple requests:
for i in {1..6}; do curl http://localhost/limit; done
# Output: "Request allowed" 5 times, then "Rate limit exceeded"
Key points:
-
lua_shared_dict
creates a shared memory zone. -
dict:incr(key, value, init, expiry)
increments a counter with expiration. -
ngx.var.remote_addr
gets the client’s IP.
Debugging and Best Practices for Lua Scripts
Debugging Lua in Nginx can be tricky since errors go to Nginx’s error log. Enable debug logging in your config:
error_log logs/error.log debug;
Best practices:
-
Cache Lua code: Keep
lua_code_cache on
(default) for performance. -
Handle errors: Use
pcall
in Lua to catch runtime errors. - Test locally: Use a staging environment before deploying.
- Keep scripts small: Large scripts can block Nginx’s event loop.
Here’s an example with error handling:
http {
server {
listen 80;
server_name localhost;
location /safe {
content_by_lua_block {
local ok, err = pcall(function()
local result = some_function() -- Assume this might fail
ngx.say(result)
end)
if not ok then
ngx.status = 500
ngx.say("Error: " .. err)
-- Output: Error: [error message]
end
-- Output: [result] (if no error)
}
}
}
}
Check /usr/local/openresty/nginx/logs/error.log
for issues.
Where to Go Next with Lua and Nginx
You’ve now seen how to handle dynamic responses, rewrites, authentication, content modification, and rate limiting with Lua in Nginx. To level up, try these:
-
Explore libraries: Use
lua-resty-redis
for Redis integration or ` lua-resty-mysql for MongoDB querying. - Scale with OpenResty: Combine Lua with OpenResty’s built-in libraries for caching or proxying.
- Contribute: Check out OpenResty’s GitHub for community scripts and modules.
Experiment with these examples in a local setup. Lua’s simplicity and Nginx’s speed make them a killer combo for custom web logic. Start small, test thoroughly, and you’ll be scripting like a pro in no time.
Top comments (0)