Supercharge your web application performance by integrating Redis as a caching backend for Nginx using lua-resty-redis. This tutorial covers Redis memory optimization, Nginx worker tuning, and comprehensive performance testing for production environments.
Prerequisites
- Root or sudo access
- At least 4GB RAM
- Basic understanding of web server concepts
What this solves
High-traffic websites often struggle with slow response times and server overload due to repeated database queries and file system access. This tutorial shows you how to implement Redis as a high-performance caching backend for Nginx using Lua scripting, reducing response times from hundreds of milliseconds to single digits. You'll also optimize Redis memory usage and tune Nginx worker processes for maximum throughput.
Step-by-step installation
Update system packages and install dependencies
Start by updating your package manager and installing the required packages for Nginx, Redis, and Lua integration.
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx redis-server lua-cjson libnginx-mod-http-lua build-essential git
Install lua-resty-redis library
Download and install the lua-resty-redis library that enables Nginx to communicate with Redis servers efficiently.
cd /tmp
git clone https://github.com/openresty/lua-resty-redis.git
cd lua-resty-redis
sudo mkdir -p /usr/local/share/lua/5.1/resty
sudo cp lib/resty/redis.lua /usr/local/share/lua/5.1/resty/
sudo chown root:root /usr/local/share/lua/5.1/resty/redis.lua
sudo chmod 644 /usr/local/share/lua/5.1/resty/redis.lua
Configure Redis for optimal caching performance
Optimize Redis configuration for high-performance caching with appropriate memory limits and eviction policies.
# Memory optimization
maxmemory 2gb
maxmemory-policy allkeys-lru
Performance tuning
tcp-keepalive 60
timeout 300
tcp-backlog 511
Persistence settings for cache
save ""
appendonly no
Network optimization
bind 127.0.0.1
port 6379
protected-mode yes
Logging
loglevel notice
logfile /var/log/redis/redis-server.log
Client output buffer limits
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
Configure Nginx with Lua and Redis integration
Set up Nginx with optimized worker processes and Lua path configuration for Redis integration.
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
Load Lua module
load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Lua package path
lua_package_path "/usr/local/share/lua/5.1/?.lua;;";
# Redis connection pool
lua_shared_dict redis_cache 10m;
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
client_max_body_size 16M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" rt=$request_time ut="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Create Redis caching virtual host
Configure a virtual host that implements Redis caching with automatic cache invalidation and fallback mechanisms.
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
# Cache configuration
set $cache_key "$scheme$request_method$host$request_uri";
set $cache_ttl 3600;
location / {
# Try Redis cache first
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
return
end
local cache_key = ngx.var.cache_key
local res, err = red:get(cache_key)
if res and res ~= ngx.null then
ngx.header["X-Cache"] = "HIT"
ngx.header["Content-Type"] = "text/html"
ngx.say(res)
ngx.exit(200)
end
red:set_keepalive(10000, 100)
}
# Cache miss - proxy to backend
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache response in Redis
body_filter_by_lua_block {
if ngx.status == 200 then
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
return
end
local cache_key = ngx.var.cache_key
local cache_ttl = tonumber(ngx.var.cache_ttl)
-- Get response body
local body = ngx.arg[1]
if body then
red:setex(cache_key, cache_ttl, body)
ngx.header["X-Cache"] = "MISS"
end
red:set_keepalive(10000, 100)
end
}
}
# Cache invalidation endpoint
location /cache/purge {
allow 127.0.0.1;
deny all;
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.status = 500
ngx.say("Failed to connect to Redis: ", err)
return
end
local pattern = ngx.var.arg_pattern or "*"
local keys, err = red:keys(pattern)
if keys then
for i, key in ipairs(keys) do
red:del(key)
end
ngx.say("Purged ", #keys, " cache entries")
else
ngx.status = 500
ngx.say("Error: ", err)
end
red:set_keepalive(10000, 100)
}
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
Optimize system kernel parameters
Configure kernel parameters for high-performance networking and memory management.
# Network optimization
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
Memory optimization
vm.swappiness = 1
vm.overcommit_memory = 1
File descriptor limits
fs.file-max = 2097152
Configure system limits for high performance
Set appropriate file descriptor and process limits for Nginx and Redis processes.
www-data soft nofile 65535
www-data hard nofile 65535
www-data soft nproc 32768
www-data hard nproc 32768
redis soft nofile 65535
redis hard nofile 65535
redis soft nproc 32768
redis hard nproc 32768
Enable and start services
Enable both Redis and Nginx services and apply the new configuration settings.
sudo sysctl -p /etc/sysctl.d/99-nginx-redis.conf
sudo systemctl enable redis-server
sudo systemctl restart redis-server
sudo ln -s /etc/nginx/sites-available/redis-cache /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl enable nginx
sudo systemctl restart nginx
Create performance monitoring script
Set up a monitoring script to track cache hit rates and performance metrics.
#!/bin/bash
Redis and Nginx cache performance monitoring
echo "=== Redis Cache Statistics ==="
redis-cli info stats | grep -E "(keyspace_hits|keyspace_misses|used_memory_human)"
echo "\n=== Cache Hit Rate ==="
HITS=$(redis-cli info stats | grep keyspace_hits | cut -d: -f2 | tr -d '\r')
MISSES=$(redis-cli info stats | grep keyspace_misses | cut -d: -f2 | tr -d '\r')
TOTAL=$((HITS + MISSES))
if [ $TOTAL -gt 0 ]; then
HIT_RATE=$(echo "scale=2; $HITS * 100 / $TOTAL" | bc)
echo "Hit Rate: ${HIT_RATE}%"
else
echo "Hit Rate: No data available"
fi
echo "\n=== Redis Memory Usage ==="
redis-cli info memory | grep -E "(used_memory_human|maxmemory_human)"
echo "\n=== Active Connections ==="
ss -tuln | grep -E ":(80|6379)" | wc -l
sudo chmod +x /usr/local/bin/redis-cache-stats.sh
Performance testing and benchmarking
Install Apache Bench for testing
Install benchmarking tools to measure cache performance improvements.
sudo apt install -y apache2-utils bc
Run cache performance tests
Execute benchmark tests to measure cache performance and response times.
# Test cache miss (first request)
ab -n 1000 -c 10 http://example.com/
Test cache hit (subsequent requests)
ab -n 1000 -c 50 http://example.com/
Monitor cache statistics
/usr/local/bin/redis-cache-stats.sh
Verify your setup
Confirm that Redis caching is working correctly and performance is optimized.
# Check service status
sudo systemctl status nginx redis-server
Test Redis connection
redis-cli ping
Test cache functionality
curl -H "Host: example.com" http://localhost/
curl -H "Host: example.com" http://localhost/ -H "X-Cache: check"
Verify cache entries
redis-cli keys "*"
Check Nginx configuration
sudo nginx -t
Monitor performance
/usr/local/bin/redis-cache-stats.sh
Test cache purge
curl "http://localhost/cache/purge?pattern=*"
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Lua module not found | Missing nginx-lua module | Install libnginx-mod-http-lua package and restart nginx |
| Redis connection failed | Redis not running or wrong port | Check redis service: sudo systemctl status redis-server |
| Cache not working | Lua script errors | Check nginx error log: sudo tail -f /var/log/nginx/error.log |
| High memory usage | No maxmemory limit set | Configure maxmemory and eviction policy in redis.conf |
| Low cache hit rate | Short TTL or frequent purges | Increase cache_ttl value and optimize cache keys |
| Performance not improved | Backend still slow | Profile backend application and database queries |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
DOMAIN="${1:-example.com}"
REDIS_MEMORY="${2:-2g}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Usage function
usage() {
echo "Usage: $0 [domain] [redis_memory]"
echo " domain: Domain name (default: example.com)"
echo " redis_memory: Redis memory limit (default: 2g)"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function
cleanup() {
log_error "Installation failed. Rolling back changes..."
systemctl stop nginx redis-server 2>/dev/null || true
rm -f /etc/nginx/sites-available/redis-cache 2>/dev/null || true
rm -f /etc/nginx/conf.d/redis-cache.conf 2>/dev/null || true
rm -rf /usr/local/share/lua 2>/dev/null || true
}
trap cleanup ERR
# Check prerequisites
check_prerequisites() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
if [[ $# -gt 2 ]]; then
usage
fi
}
# Detect distribution
detect_distro() {
if [[ ! -f /etc/os-release ]]; then
log_error "Cannot detect distribution"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
REDIS_CONF="/etc/redis/redis.conf"
NGINX_USER="www-data"
USE_SITES_AVAILABLE=true
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
REDIS_CONF="/etc/redis.conf"
NGINX_USER="nginx"
USE_SITES_AVAILABLE=false
if [[ "$ID" == "centos" ]] && [[ "${VERSION_ID%%.*}" -le 7 ]]; then
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
REDIS_CONF="/etc/redis.conf"
NGINX_USER="nginx"
USE_SITES_AVAILABLE=false
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected distribution: $ID"
}
# Install packages
install_packages() {
echo "[1/7] Installing packages..."
eval $PKG_UPDATE
case "$PKG_MGR" in
apt)
$PKG_INSTALL nginx redis-server lua-cjson libnginx-mod-http-lua build-essential git
;;
dnf)
$PKG_INSTALL epel-release
$PKG_INSTALL nginx redis nginx-mod-http-lua lua-cjson git gcc make
;;
yum)
$PKG_INSTALL epel-release
$PKG_INSTALL nginx redis lua-cjson git gcc make
;;
esac
log_info "Packages installed successfully"
}
# Install lua-resty-redis
install_lua_redis() {
echo "[2/7] Installing lua-resty-redis library..."
cd /tmp
if [[ -d lua-resty-redis ]]; then
rm -rf lua-resty-redis
fi
git clone https://github.com/openresty/lua-resty-redis.git
cd lua-resty-redis
mkdir -p /usr/local/share/lua/5.1/resty
cp lib/resty/redis.lua /usr/local/share/lua/5.1/resty/
chown root:root /usr/local/share/lua/5.1/resty/redis.lua
chmod 644 /usr/local/share/lua/5.1/resty/redis.lua
log_info "lua-resty-redis installed successfully"
}
# Configure Redis
configure_redis() {
echo "[3/7] Configuring Redis..."
cp "$REDIS_CONF" "$REDIS_CONF.backup"
cat > "$REDIS_CONF" << EOF
# Memory optimization
maxmemory ${REDIS_MEMORY}b
maxmemory-policy allkeys-lru
# Performance tuning
tcp-keepalive 60
timeout 300
tcp-backlog 511
# Persistence settings for cache
save ""
appendonly no
# Network optimization
bind 127.0.0.1
port 6379
protected-mode yes
# Logging
loglevel notice
logfile /var/log/redis/redis-server.log
# Client output buffer limits
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
# Security
requirepass $(openssl rand -base64 32)
EOF
# Create log directory
mkdir -p /var/log/redis
chown redis:redis /var/log/redis
chmod 755 /var/log/redis
log_info "Redis configured successfully"
}
# Configure Nginx
configure_nginx() {
echo "[4/7] Configuring Nginx..."
cp "$NGINX_CONF_DIR/nginx.conf" "$NGINX_CONF_DIR/nginx.conf.backup"
cat > "$NGINX_CONF_DIR/nginx.conf" << EOF
user $NGINX_USER;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Lua package path
lua_package_path "/usr/local/share/lua/5.1/?.lua;;";
# Redis connection pool
lua_shared_dict redis_cache 10m;
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
client_max_body_size 16M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" rt=\$request_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/conf.d/*.conf;
$([ "$USE_SITES_AVAILABLE" = true ] && echo "include /etc/nginx/sites-enabled/*;")
}
EOF
log_info "Nginx main configuration updated"
}
# Create virtual host
create_vhost() {
echo "[5/7] Creating Redis cache virtual host..."
if [[ "$USE_SITES_AVAILABLE" = true ]]; then
VHOST_FILE="$NGINX_SITES_DIR/redis-cache"
else
VHOST_FILE="$NGINX_SITES_DIR/redis-cache.conf"
fi
cat > "$VHOST_FILE" << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
location / {
set \$cache_key "\$scheme\$request_method\$host\$request_uri";
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
return ngx.exit(500)
end
local cache_key = ngx.var.cache_key
local res, err = red:get(cache_key)
if res ~= ngx.null then
ngx.header["X-Cache"] = "HIT"
ngx.say(res)
return
end
ngx.header["X-Cache"] = "MISS"
local content = "Hello from Redis-cached Nginx! Time: " .. ngx.time()
red:setex(cache_key, 300, content)
red:close()
ngx.say(content)
}
}
}
EOF
chmod 644 "$VHOST_FILE"
chown root:root "$VHOST_FILE"
if [[ "$USE_SITES_AVAILABLE" = true ]]; then
ln -sf "$VHOST_FILE" "$NGINX_ENABLED_DIR/"
fi
log_info "Virtual host created successfully"
}
# Start services
start_services() {
echo "[6/7] Starting services..."
systemctl enable redis-server redis 2>/dev/null || systemctl enable redis
systemctl enable nginx
systemctl restart redis-server redis 2>/dev/null || systemctl restart redis
systemctl restart nginx
log_info "Services started successfully"
}
# Verify installation
verify_installation() {
echo "[7/7] Verifying installation..."
# Check services
if ! systemctl is-active --quiet nginx; then
log_error "Nginx is not running"
exit 1
fi
if ! systemctl is-active --quiet redis-server && ! systemctl is-active --quiet redis; then
log_error "Redis is not running"
exit 1
fi
# Test Redis connection
if ! redis-cli ping >/dev/null 2>&1; then
log_error "Cannot connect to Redis"
exit 1
fi
# Test Nginx configuration
if ! nginx -t >/dev/null 2>&1; then
log_error "Nginx configuration test failed"
exit 1
fi
log_info "Installation verified successfully"
log_info "Redis + Nginx cache setup complete for domain: $DOMAIN"
log_info "Test your setup with: curl -H 'Host: $DOMAIN' http://localhost/"
}
# Main execution
main() {
check_prerequisites "$@"
detect_distro
install_packages
install_lua_redis
configure_redis
configure_nginx
create_vhost
start_services
verify_installation
}
main "$@"
Review the script before running. Execute with: bash install.sh