HAProxy
Integrate redirect lookups with HAProxy using Lua scripts and map files with the Edge Query API
HAProxy Integration
Integrate redirect lookups with HAProxy using Lua scripts for real-time API queries or map files for high-performance local lookups.
Overview
This guide shows you how to integrate redirect lookups into HAProxy, the reliable, high-performance load balancer. You'll learn two integration patterns:
- Map Files (Recommended): Periodic sync to local map files for O(1) lookups with zero latency overhead
- Lua Scripts (Advanced): Real-time API queries for always-fresh redirect data
Estimated time: 30 minutes
Prerequisites:
- HAProxy 2.9+ with Lua support (for real-time pattern)
- curl command-line tool
- API key for the Edge Query API
Check Lua support:
haproxy -vv | grep LuaIf you see "Built with Lua version", you have Lua support enabled.
Request Flow
flowchart TD
A[Client Request] --> B[HAProxy Frontend]
B --> C{Redirect Lookup}
C -->|Match Found| D[Return 301 Redirect]
C -->|No Match| E[Pass to Default Backend]
D --> F[Client]
E --> G[Origin Server]Pattern 1: Periodic Sync + Map Files (Recommended)
HAProxy's map files provide O(1) lookup performance with zero network overhead. This is the recommended approach for most use cases.
How It Works
- A sync script downloads redirects from the Edge Query API
- Redirects are written to a HAProxy map file
- HAProxy uses the
map()function to perform lookups - A cron job keeps the map file up to date
Step 1: Create the Sync Script
Create /usr/local/bin/sync-redirects.sh:
#!/bin/bash
set -euo pipefail
API_URL="https://api.3xx.app"
PROJECT_ID="YOUR_PROJECT_ID"
API_KEY="YOUR_API_KEY"
MAP_FILE="/etc/haproxy/redirects.map"
TEMP_FILE="/tmp/redirects.map.$$"
# Download map file from API
curl -sf \
-H "X-API-Key: ${API_KEY}" \
"${API_URL}/v1/export?projectId=${PROJECT_ID}&format=haproxy-map-exact" \
-o "${TEMP_FILE}"
# Atomic replacement
mv "${TEMP_FILE}" "${MAP_FILE}"
# Reload HAProxy configuration
systemctl reload haproxy
echo "Redirects synced: $(wc -l < ${MAP_FILE}) entries"Make the script executable:
chmod +x /usr/local/bin/sync-redirects.shInline verification: Run the sync script manually:
/usr/local/bin/sync-redirects.shYou should see output like "Redirects synced: 42 entries". Check the map file:
head /etc/haproxy/redirects.mapYou should see lines like:
/old-path https://example.com/new-path
/another-old-path https://example.com/another-new-pathStep 2: Configure HAProxy
Edit /etc/haproxy/haproxy.cfg:
global
log /dev/log local0
maxconn 2000
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
# Check if path exists in redirect map
# If found, redirect with 301 and the mapped destination
http-request redirect code 301 location %[path,map(/etc/haproxy/redirects.map)] if { path,map(/etc/haproxy/redirects.map) -m found }
# If no redirect found, pass to default backend
default_backend web_servers
backend web_servers
balance roundrobin
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 checkInline verification: Test the configuration:
haproxy -c -f /etc/haproxy/haproxy.cfgYou should see "Configuration file is valid".
Reload HAProxy:
systemctl reload haproxyTest a redirect:
curl -I http://localhost/old-pathYou should see:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-pathStep 3: Schedule Periodic Sync
Add a cron job to sync redirects every 5 minutes. Run crontab -e:
*/5 * * * * /usr/local/bin/sync-redirects.sh >> /var/log/redirect-sync.log 2>&1Advanced: Runtime Map API
HAProxy provides a runtime API for live map updates without reloading:
View current map entries:
echo "show map /etc/haproxy/redirects.map" | socat stdio /var/run/haproxy.sockAdd a redirect:
echo "add map /etc/haproxy/redirects.map /new-path https://example.com/destination" | socat stdio /var/run/haproxy.sockUpdate a redirect:
echo "set map /etc/haproxy/redirects.map /old-path https://example.com/updated-destination" | socat stdio /var/run/haproxy.sockClear all redirects:
echo "clear map /etc/haproxy/redirects.map" | socat stdio /var/run/haproxy.sockNote: Runtime changes are not persisted. Use the sync script for permanent updates.
Pattern 2: Real-Time API Lookup (Lua)
For scenarios requiring always-fresh redirect data, HAProxy can use Lua scripts to query the Edge Query API in real-time.
Trade-offs:
- ✅ Always fresh (no sync delay)
- ✅ No local storage required
- ❌ Adds network latency to each lookup
- ❌ Requires Lua support in HAProxy
- ❌ More complex error handling
Step 1: Create Lua Redirect Script
Create /etc/haproxy/redirect-lookup.lua:
local http = require("socket.http")
local json = require("json")
-- Configuration
local API_URL = "https://api.3xx.app"
local PROJECT_ID = os.getenv("REDIRECT_PROJECT_ID") or "YOUR_PROJECT_ID"
local API_KEY = os.getenv("REDIRECT_API_KEY") or "YOUR_API_KEY"
function redirect_lookup(txn)
local path = txn.sf:path()
-- Build API URL
local lookup_url = string.format(
"%s/v1/lookup?projectId=%s&path=%s",
API_URL,
PROJECT_ID,
path
)
-- Make HTTP request with error handling
local success, response_body, response_code = pcall(function()
local body, code = http.request({
url = lookup_url,
method = "GET",
headers = {
["X-API-Key"] = API_KEY,
["Accept"] = "application/json"
}
})
return body, code
end)
-- Handle errors
if not success or response_code ~= 200 then
core.Debug("Redirect lookup failed: " .. tostring(response_code))
return nil
end
-- Parse JSON response
local ok, data = pcall(json.decode, response_body)
if not ok then
core.Debug("Failed to parse JSON response")
return nil
end
-- Check if redirect exists
if data.redirect and data.redirect.destination then
core.Info("Redirect found: " .. path .. " -> " .. data.redirect.destination)
return data.redirect.destination
end
return nil
end
-- Register the function
core.register_fetches("redirect_lookup", redirect_lookup)Note: HAProxy's Lua environment has limitations. The above example uses LuaSocket which may require additional configuration. For production use, consider using the map file pattern or HAProxy's native HTTP client capabilities.
Step 2: Configure HAProxy with Lua
Edit /etc/haproxy/haproxy.cfg:
global
log /dev/log local0
maxconn 2000
user haproxy
group haproxy
daemon
# Load Lua script
lua-load /etc/haproxy/redirect-lookup.lua
# Set environment variables for API credentials
setenv REDIRECT_PROJECT_ID YOUR_PROJECT_ID
setenv REDIRECT_API_KEY YOUR_API_KEY
defaults
log global
mode http
option httplog
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
# Perform redirect lookup via Lua
http-request set-var(txn.redirect_dest) lua.redirect_lookup
# If redirect found, return 301
http-request redirect code 301 location %[var(txn.redirect_dest)] if { var(txn.redirect_dest) -m found }
# Otherwise, pass to backend
default_backend web_servers
backend web_servers
balance roundrobin
server web1 192.168.1.10:8080 check
server web2 192.168.1.11:8080 checkInline verification: Test the configuration:
haproxy -c -f /etc/haproxy/haproxy.cfgYou should see "Configuration file is valid".
Reload HAProxy:
systemctl reload haproxyTest a redirect:
curl -I http://localhost/test-pathAlternative: External Environment File
For better security, store credentials in a separate file:
Create /etc/haproxy/redirect-env:
REDIRECT_PROJECT_ID=your_project_id_here
REDIRECT_API_KEY=your_api_key_hereRestrict permissions:
chmod 600 /etc/haproxy/redirect-env
chown haproxy:haproxy /etc/haproxy/redirect-envUpdate your systemd service to load the environment file by creating /etc/systemd/system/haproxy.service.d/override.conf:
[Service]
EnvironmentFile=/etc/haproxy/redirect-envReload systemd and restart HAProxy:
systemctl daemon-reload
systemctl restart haproxyFallback Behavior
Map File Pattern
Missing map file: If the map file doesn't exist, HAProxy will log a warning but continue to operate. No redirects will be processed, and all requests will pass to the default backend.
Empty map file: Same behavior as missing file - requests pass through to backend.
Invalid map file format: HAProxy will log errors and may fail to reload. Use haproxy -c to validate before reloading.
Lua Pattern
API unreachable: The Lua script wraps HTTP calls in pcall() to catch errors. Failed lookups return nil, causing requests to pass through to the default backend.
Timeout: Configure socket timeouts in the Lua script to prevent hanging requests.
Invalid response: JSON parse errors are caught and logged. Request passes to backend.
Testing
Test Exact Match Redirect
curl -I http://localhost/old-pathExpected:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-pathTest Non-Existent Path
curl -I http://localhost/does-not-existExpected:
HTTP/1.1 200 OK
(response from backend)Verify Map File Reload
- Update a redirect in your dashboard
- Wait for sync (or run
/usr/local/bin/sync-redirects.shmanually) - Check map file:
grep "updated-path" /etc/haproxy/redirects.map - Test redirect:
curl -I http://localhost/updated-path
Debug Map Lookups
Add logging to see which redirects are matched:
frontend http-in
bind *:80
# Log the redirect destination if found
http-request set-var(txn.redirect_dest) path,map(/etc/haproxy/redirects.map)
http-request capture var(txn.redirect_dest) len 256 if { var(txn.redirect_dest) -m found }
http-request redirect code 301 location %[var(txn.redirect_dest)] if { var(txn.redirect_dest) -m found }
default_backend web_serversCheck logs:
tail -f /var/log/haproxy.logTroubleshooting
Configuration Validation Fails
Problem: haproxy -c -f /etc/haproxy/haproxy.cfg shows errors
Solution:
- Check syntax carefully - HAProxy config is whitespace-sensitive
- Verify map file exists before referencing it
- For Lua issues, check that Lua support is compiled in:
haproxy -vv | grep Lua
Map File Format Errors
Problem: Redirects not working after sync
Solution:
- Check map file format:
cat /etc/haproxy/redirects.map - Format should be:
source_path destination_url(space-separated, one per line) - No empty lines or comments in map file
- Verify file permissions:
ls -l /etc/haproxy/redirects.map
Lua Script Not Working
Problem: Lua redirects not executing
Possible causes:
- Lua not compiled in: Run
haproxy -vv | grep Lua- if no output, rebuild HAProxy with Lua support - LuaSocket not available: HAProxy's Lua environment may not include socket libraries by default
- Environment variables not set: Check
systemctl status haproxyand verify environment variables are loaded - HTTP client limitations: HAProxy's Lua HTTP client has limitations. Consider using the map file pattern instead.
Runtime Map API Not Working
Problem: socat commands fail
Solution:
- Verify stats socket is configured in HAProxy config:
global stats socket /var/run/haproxy.sock mode 660 level admin - Check socket permissions:
ls -l /var/run/haproxy.sock - Ensure user is in haproxy group:
groups
Redirects Not Updating
Problem: New redirects don't appear
Solution:
- Check sync script logs:
tail -f /var/log/redirect-sync.log - Verify cron job is running:
systemctl status cron - Test sync manually:
/usr/local/bin/sync-redirects.sh - Check API credentials in sync script
- Verify HAProxy was reloaded:
systemctl status haproxy
High Latency with Lua Pattern
Problem: Requests are slow with real-time lookups
Solution:
- Add timeout to Lua HTTP requests
- Consider switching to map file pattern for better performance
- Implement caching in Lua script for frequently accessed paths
- Monitor API response times
For more troubleshooting tips, see the Troubleshooting Guide.
Performance Considerations
Map File Pattern:
- Lookup time: O(1) constant time
- Memory usage: ~100 bytes per redirect
- Network overhead: Zero (local lookups only)
- Sync delay: 5 minutes (configurable)
Lua Pattern:
- Lookup time: Network RTT + API response time (~50ms)
- Memory usage: Minimal
- Network overhead: One API call per unique path lookup
- Freshness: Real-time
Recommendation: Use map files for production. The sync delay is acceptable for most use cases, and the performance benefits are significant.
Next Steps
- Configure nginx integration for comparison
- Set up Cloudflare Workers integration for edge deployments
- Explore redirect types to understand temporary vs permanent redirects
- Learn about prefix matching for advanced redirect patterns