HAProxy’s Lua scripting allows you to inject arbitrary logic into request and response processing, effectively extending its capabilities beyond its core proxying functions.

Here’s HAProxy processing a request with a Lua script:

-- haproxy.cfg
frontend http_frontend
    bind *:80
    mode http
    http-request lua
    http-response lua
    default_backend http_backend

backend http_backend
    mode http
    server s1 127.0.0.1:8080
-- /etc/haproxy/lua/custom_logic.lua
local function handle_request(txn)
    -- Get the request URI
    local uri = txn.uri

    -- Log the URI
    ngx.log(ngx.INFO, "Processing request for URI: " .. uri)

    -- Example: Redirect if URI starts with /old-path
    if string.sub(uri, 1, 9) == "/old-path" then
        txn.set_status(301) -- Permanent redirect
        txn.add_header("Location", "/new-path" .. string.sub(uri, 10))
        return
    end

    -- Example: Add a custom header
    txn.add_header("X-Processed-By", "HAProxy-Lua")
end

local function handle_response(txn)
    -- Log the response status
    ngx.log(ngx.INFO, "Processing response with status: " .. txn.status)

    -- Example: Modify response body (caution: can be complex)
    -- This is a simplified example. For complex modifications,
    -- consider using a dedicated response body manipulation tool
    -- or ensuring your Lua script is efficient.
    if txn.status == 200 then
        local body = txn.body
        if body then
            txn.body = string.gsub(body, "example.com", "yourdomain.com")
        end
    end
end

-- Register the functions with HAProxy
haproxy.request.access(handle_request)
haproxy.response.access(handle_response)

To enable this, you’d typically configure HAProxy to load the Lua script:

# haproxy.cfg
global
    lua-load /etc/haproxy/lua/custom_logic.lua

frontend http_frontend
    bind *:80
    mode http
    http-request lua.handle_request  # Call the request handler
    http-response lua.handle_response # Call the response handler
    default_backend http_backend

backend http_backend
    mode http
    server s1 127.0.0.1:8080

The http-request lua.<function_name> directive tells HAProxy to execute the specified Lua function (handle_request in this case) during the request processing phase. Similarly, http-response lua.<function_name> executes the Lua function (handle_response) during the response processing phase.

This system solves the problem of needing to perform complex, dynamic logic that is difficult or impossible to achieve with HAProxy’s native configuration directives alone. You can implement custom authentication, request filtering, dynamic routing based on obscure criteria, response manipulation, and more, all within the highly performant HAProxy process.

Internally, HAProxy embeds the Lua interpreter. When a http-request lua or http-response lua directive is encountered, HAProxy calls the registered Lua function. The txn object (transaction) passed to these functions provides access to request and response attributes like headers, URI, body, and status codes, as well as methods to modify them.

The key levers you control are:

  • Lua script location: Where the .lua file resides on the HAProxy server.
  • lua-load directive: In the global section, this tells HAProxy which Lua files to load at startup.
  • http-request lua.<function_name> and http-response lua.<function_name>: In frontend or backend sections, these specify which Lua functions to execute at specific points in the request/response lifecycle.
  • Lua functions themselves: The actual code within your .lua files, defining the logic. You can have multiple functions and call them based on conditions.

The txn object is your primary interface. txn.uri, txn.headers, txn.status, and txn.body give you read access. Methods like txn.set_status(), txn.add_header(), txn.del_header(), txn.set_header(), txn.set_uri(), txn.set_body() allow modification. For more advanced control, you can also interact with HAProxy’s internal state using the ngx library, which provides functions for logging (ngx.log), sleeping (ngx.sleep), and more.

A common misconception is that Lua scripting adds significant overhead. While it’s not free, HAProxy’s Lua integration is highly optimized. The Lua interpreter is embedded, and the communication between HAProxy’s C core and the Lua VM is efficient. For most common tasks like header manipulation or simple conditional logic, the performance impact is negligible compared to the benefits of keeping that logic within the proxy. However, computationally intensive operations or blocking I/O within Lua will impact performance and potentially block HAProxy’s event loop. Use Lua for what it’s good at: fast, non-blocking logic.

The next step after mastering basic Lua scripting is to explore the haproxy.table API for dynamic data lookups and real-time configuration updates.

Want structured learning?

Take the full Haproxy course →