Install and configure OpenResty web server with Lua scripting and performance optimization

Intermediate 45 min Apr 03, 2026 43 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Set up OpenResty as a high-performance web server and API gateway with embedded Lua scripting capabilities, SSL certificates, and advanced caching optimization for production workloads.

Prerequisites

  • Root or sudo access
  • At least 2GB RAM
  • Basic understanding of web servers

What this solves

OpenResty transforms NGINX into a powerful application platform by embedding LuaJIT, enabling dynamic content generation, API gateway functionality, and real-time request processing. This tutorial configures OpenResty for production use with SSL certificates, performance optimizations, and Lua scripting for custom logic that would typically require separate application servers.

Step-by-step installation

Add OpenResty repository

Install the official OpenResty repository to get the latest stable version with all required modules.

sudo apt update
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo dnf update -y
sudo dnf install -y wget
wget https://openresty.org/package/rhel/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/

Install OpenResty

Install OpenResty with essential modules for Lua scripting, SSL support, and performance optimization.

sudo apt install -y openresty openresty-opm openresty-restydoc
sudo dnf install -y openresty openresty-opm openresty-restydoc

Create directory structure

Set up proper directory structure for OpenResty configuration, Lua scripts, and SSL certificates.

sudo mkdir -p /etc/openresty/conf.d
sudo mkdir -p /etc/openresty/lua
sudo mkdir -p /etc/openresty/ssl
sudo mkdir -p /var/log/openresty
sudo mkdir -p /var/cache/openresty

Configure main OpenResty settings

Create the main configuration file with performance optimizations and Lua integration enabled.

user www-data;
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;

error_log /var/log/openresty/error.log warn;
pid /var/run/openresty.pid;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    include /usr/local/openresty/nginx/conf/mime.types;
    default_type application/octet-stream;
    
    # Lua package path
    lua_package_path "/etc/openresty/lua/?.lua;;";
    lua_code_cache on;
    
    # Performance optimizations
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript
               application/javascript application/xml+rss
               application/json application/xml;
    
    # Security headers
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=web:10m rate=5r/s;
    
    # Logging format
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    '$request_time $upstream_response_time';
    
    access_log /var/log/openresty/access.log main;
    
    # Cache zones
    proxy_cache_path /var/cache/openresty levels=1:2 keys_zone=web_cache:10m max_size=1g inactive=60m;
    
    include /etc/openresty/conf.d/*.conf;
}

Generate SSL certificate

Create a self-signed SSL certificate for development or prepare directory for production certificates.

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/openresty/ssl/server.key \
    -out /etc/openresty/ssl/server.crt \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"

Create Lua utility library

Set up a common Lua library for shared functions and utilities across your applications.

local _M = {}

-- JSON response helper
function _M.json_response(data, status)
    status = status or 200
    ngx.status = status
    ngx.header.content_type = "application/json"
    ngx.say(require("cjson").encode(data))
    ngx.exit(status)
end

-- Input validation
function _M.validate_required_fields(data, required_fields)
    for _, field in ipairs(required_fields) do
        if not data[field] or data[field] == "" then
            return false, "Missing required field: " .. field
        end
    end
    return true
end

-- Rate limiting helper
function _M.check_rate_limit(key, limit, window)
    local dict = ngx.shared.rate_limit
    local current = dict:get(key) or 0
    
    if current >= limit then
        return false
    end
    
    local ok, err = dict:incr(key, 1, 0, window)
    if not ok then
        ngx.log(ngx.ERR, "Rate limit error: ", err)
        return false
    end
    
    return true
end

-- Database connection helper
function _M.get_redis_connection()
    local redis = require "resty.redis"
    local red = redis:new()
    red:set_timeouts(1000, 1000, 1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "Redis connection failed: ", err)
        return nil
    end
    
    return red
end

return _M

Configure virtual host with Lua integration

Create a virtual host configuration that demonstrates Lua scripting for API endpoints and dynamic content.

server {
    listen 80;
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL configuration
    ssl_certificate /etc/openresty/ssl/server.crt;
    ssl_certificate_key /etc/openresty/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # HSTS header
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Document root
    root /var/www/html;
    index index.html index.htm;
    
    # Rate limiting for web pages
    limit_req zone=web burst=20 nodelay;
    
    # Static file caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # API endpoint with Lua
    location /api/ {
        limit_req zone=api burst=10 nodelay;
        
        # CORS headers for API
        add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        
        # Handle preflight requests
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin "*";
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "Content-Type, Authorization";
            add_header Content-Length 0;
            return 204;
        }
        
        access_by_lua_block {
            local utils = require("utils")
            
            -- API key validation
            local api_key = ngx.var.http_x_api_key
            if not api_key then
                utils.json_response({error = "API key required"}, 401)
            end
            
            -- Rate limiting per API key
            local limit_key = "api:" .. api_key
            if not utils.check_rate_limit(limit_key, 100, 3600) then
                utils.json_response({error = "Rate limit exceeded"}, 429)
            end
        }
        
        location = /api/health {
            content_by_lua_block {
                local utils = require("utils")
                local response = {
                    status = "ok",
                    timestamp = ngx.time(),
                    server = ngx.var.hostname
                }
                utils.json_response(response)
            }
        }
        
        location = /api/users {
            content_by_lua_block {
                local utils = require("utils")
                local cjson = require("cjson")
                
                if ngx.var.request_method == "GET" then
                    -- Simulate user data retrieval
                    local users = {
                        {id = 1, name = "John Doe", email = "john@example.com"},
                        {id = 2, name = "Jane Smith", email = "jane@example.com"}
                    }
                    utils.json_response({data = users})
                    
                elseif ngx.var.request_method == "POST" then
                    -- Handle user creation
                    ngx.req.read_body()
                    local body = ngx.req.get_body_data()
                    
                    if not body then
                        utils.json_response({error = "Request body required"}, 400)
                    end
                    
                    local ok, data = pcall(cjson.decode, body)
                    if not ok then
                        utils.json_response({error = "Invalid JSON"}, 400)
                    end
                    
                    local valid, err = utils.validate_required_fields(data, {"name", "email"})
                    if not valid then
                        utils.json_response({error = err}, 400)
                    end
                    
                    -- Simulate user creation
                    local new_user = {
                        id = math.random(1000, 9999),
                        name = data.name,
                        email = data.email,
                        created_at = os.date("%Y-%m-%d %H:%M:%S")
                    }
                    
                    utils.json_response({data = new_user}, 201)
                else
                    utils.json_response({error = "Method not allowed"}, 405)
                end
            }
        }
    }
    
    # Dynamic content with caching
    location /dynamic/ {
        proxy_cache web_cache;
        proxy_cache_valid 200 5m;
        proxy_cache_key "$request_uri$is_args$args";
        
        content_by_lua_block {
            local utils = require("utils")
            
            -- Generate dynamic content
            local content = {
                message = "Hello from OpenResty Lua!",
                timestamp = os.date("%Y-%m-%d %H:%M:%S"),
                request_id = ngx.var.request_id,
                client_ip = ngx.var.remote_addr
            }
            
            ngx.header.content_type = "text/html"
            ngx.say("Dynamic Content")
            ngx.say("

" .. content.message .. "

") ngx.say("

Generated at: " .. content.timestamp .. "

") ngx.say("

Request ID: " .. content.request_id .. "

") ngx.say("

Your IP: " .. content.client_ip .. "

") ngx.say("") } } # Main location location / { try_files $uri $uri/ =404; } }

Create sample HTML page

Set up a basic HTML page to test the web server functionality.

sudo mkdir -p /var/www/html
sudo tee /var/www/html/index.html > /dev/null <<'EOF'



    OpenResty with Lua
    
    
    


    

OpenResty with Lua Scripting

Your OpenResty server is running successfully with Lua integration!

Available Endpoints

API Health Check

GET /api/health

Returns server status and timestamp

Users API

GET /api/users - List all users

POST /api/users - Create new user

Requires X-API-Key header

Dynamic Content

GET /dynamic/

Generates dynamic HTML content with caching

EOF

Set proper permissions

Configure correct ownership and permissions for OpenResty files and directories.

Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions.
sudo chown -R www-data:www-data /var/www/html
sudo chown -R www-data:www-data /var/log/openresty
sudo chown -R www-data:www-data /var/cache/openresty
sudo chmod 755 /var/www/html
sudo chmod 644 /var/www/html/index.html
sudo chmod 750 /etc/openresty
sudo chmod 640 /etc/openresty/nginx.conf
sudo chmod 600 /etc/openresty/ssl/server.key
sudo chmod 644 /etc/openresty/ssl/server.crt

Create systemd service

Set up a systemd service file for proper OpenResty management and auto-start on boot.

[Unit]
Description=OpenResty HTTP Server
After=network.target remote-fs.target nss-lookup.target
Wants=network.target

[Service]
Type=forking
PIDFile=/var/run/openresty.pid
ExecStartPre=/usr/local/openresty/bin/openresty -t -c /etc/openresty/nginx.conf
ExecStart=/usr/local/openresty/bin/openresty -c /etc/openresty/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
Restart=on-failure
RestartSec=5
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target

Enable and start OpenResty

Start the OpenResty service and enable it to start automatically on system boot.

sudo systemctl daemon-reload
sudo systemctl enable openresty
sudo systemctl start openresty

Configure firewall

Open HTTP and HTTPS ports in the firewall to allow web traffic.

sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Performance optimization

Configure kernel parameters

Optimize kernel settings for high-performance web serving and connection handling.

# Network optimizations
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_slow_start_after_idle = 0

File handling

fs.file-max = 2097152

Memory management

vm.swappiness = 10
sudo sysctl -p /etc/sysctl.d/99-openresty.conf

Optimize systemd limits

Increase system limits for the OpenResty service to handle more concurrent connections.

[Service]
LimitNOFILE=65535
LimitNPROC=4096
LimitCORE=infinity
sudo mkdir -p /etc/systemd/system/openresty.service.d
sudo systemctl daemon-reload
sudo systemctl restart openresty

Set up logging and monitoring

Configure log rotation

Set up automatic log rotation to prevent disk space issues and maintain log history.

/var/log/openresty/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 640 www-data www-data
    sharedscripts
    postrotate
        if [ -f /var/run/openresty.pid ]; then
            kill -USR1 cat /var/run/openresty.pid
        fi
    endscript
}

Create monitoring script

Set up a basic monitoring script to check OpenResty status and performance metrics.

#!/bin/bash

OpenResty monitoring script

LOGFILE="/var/log/openresty/monitor.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') echo "[$TIMESTAMP] Starting OpenResty health check" >> $LOGFILE

Check if OpenResty is running

if ! systemctl is-active --quiet openresty; then echo "[$TIMESTAMP] ERROR: OpenResty is not running" >> $LOGFILE systemctl restart openresty exit 1 fi

Check HTTP response

HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/) if [ "$HTTP_STATUS" != "200" ]; then echo "[$TIMESTAMP] ERROR: HTTP check failed with status $HTTP_STATUS" >> $LOGFILE fi

Check HTTPS response

HTTPS_STATUS=$(curl -s -k -o /dev/null -w "%{http_code}" https://localhost/) if [ "$HTTPS_STATUS" != "200" ]; then echo "[$TIMESTAMP] ERROR: HTTPS check failed with status $HTTPS_STATUS" >> $LOGFILE fi

Check API endpoint

API_STATUS=$(curl -s -H "X-API-Key: test123" -o /dev/null -w "%{http_code}" http://localhost/api/health) if [ "$API_STATUS" != "200" ]; then echo "[$TIMESTAMP] ERROR: API health check failed with status $API_STATUS" >> $LOGFILE fi echo "[$TIMESTAMP] Health check completed" >> $LOGFILE
sudo chmod 755 /usr/local/bin/openresty-monitor.sh

Schedule monitoring with cron

Set up automated monitoring to run every 5 minutes and alert on issues.

sudo crontab -e
/5    * /usr/local/bin/openresty-monitor.sh

Verify your setup

# Check OpenResty status
sudo systemctl status openresty

Test configuration syntax

sudo /usr/local/openresty/bin/openresty -t -c /etc/openresty/nginx.conf

Test HTTP response

curl -I http://localhost/

Test HTTPS response

curl -k -I https://localhost/

Test API endpoint

curl -H "X-API-Key: test123" http://localhost/api/health

Test dynamic content

curl http://localhost/dynamic/

Check logs

sudo tail -f /var/log/openresty/access.log sudo tail -f /var/log/openresty/error.log

Common issues

SymptomCauseFix
Service fails to startConfiguration syntax errorsudo /usr/local/openresty/bin/openresty -t -c /etc/openresty/nginx.conf
API returns 401 errorsMissing X-API-Key headerAdd -H "X-API-Key: your-key" to curl requests
Lua script errorsSyntax error in Lua codeCheck error log: sudo tail /var/log/openresty/error.log
SSL certificate warningsSelf-signed certificateUse -k flag with curl or install proper SSL certificate
Permission denied errorsIncorrect file ownershipsudo chown -R www-data:www-data /var/www/html
High memory usageLua code cache disabledEnsure lua_code_cache on; in production

Next steps

Automated install script

Run this to automate the entire setup

#openresty #nginx-lua #lua-scripting #web-server #api-gateway

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer