
Intro #
HAProxy Community is a popular open-source load balancer and reverse proxy. Besides distributing traffic between backend servers, HAProxy can make routing and access-control decisions before a request reaches the web application.
By combining HAProxy’s Lua support with the IP2Location DB11 MMDB and IP2Proxy PX10 MMDB databases, HAProxy can perform IP geolocation and anonymous proxy detection directly at the proxy layer.
In this tutorial, we will use IP2Location DB11 MMDB for geolocation, IP2Proxy PX10 MMDB for anonymous proxy detection, HAProxy Community Edition on Debian 13, and Debian’s lua-mmdb package for reading MMDB databases.
The final request flow will look like this:

The backend application itself does not need to open either database.
The IP2Location DB11 MMDB uses the compatible GeoLite2-City MMDB field structure, while the IP2Proxy PX10 MMDB uses the compatible GeoIP2-Anonymous-IP MMDB field structure. These MMDB editions contain only fields available through those compatible schemas.
1. Install HAProxy and the required packages #
Start by updating Debian:
sudo apt update
Install HAProxy, Lua 5.4, the MMDB Lua reader, the MMDB command-line utility, Python, and curl:
sudo apt install -y \
haproxy \
lua-mmdb \
lua5.4 \
mmdb-bin \
python3 \
curl
Add the path to .bashrc and reload:
echo 'export PATH="$PATH:/usr/sbin"' >> ~/.bashrc
source ~/.bashrc
Check the HAProxy version:
haproxy -v
Then verify that HAProxy was compiled with Lua support:
haproxy -vv | grep -i lua
You should see Lua mentioned in the HAProxy build information.
Verify that Lua can load the MMDB module:
lua5.4 -e 'local mmdb=require("mmdb"); print("lua-mmdb loaded successfully")'
Expected output:
lua-mmdb loaded successfully

2. Prepare the IP2Location and IP2Proxy databases #
If you don’t have an account, sign up for a free LITE account. Then, you can login and download the IP2Location DB11 LITE MMDB and the IP2Proxy PX10 LITE MMDB.
NOTE: The PX10 LITE contains just public proxies so you will need to purchase a paid PX10 subscription if you want to have access to other proxies data like residential proxies.
Download the zipped files then extract the MMDB files to your home folder like:
~/IP2LOCATION-DB11.MMDB
~/IP2PROXY-PX10.MMDB
Create a directory for the databases:
sudo mkdir -p /etc/haproxy/mmdb
Move IP2Location DB11:
sudo mv ~/IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB
Move IP2Proxy PX10:
sudo mv ~/IP2PROXY-PX10.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Set the ownership:
sudo chown root:haproxy \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Set the permissions:
sudo chmod 640 \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Your directory should now contain:
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
3. Test IP2Location DB11 before using HAProxy #
It is useful to verify that the database works before introducing HAProxy into the setup.
Query the country code:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
--ip 8.8.8.8 \
country iso_code
Query the country name:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
--ip 8.8.8.8 \
country names en
Query the region:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
--ip 8.8.8.8 \
subdivisions 0 names en
Query the city:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
--ip 8.8.8.8 \
city names en

The returned values depend on the database release and the IP address being tested.
4. Test IP2Proxy PX10 #
For PX10, use an IP address that you know is classified as a proxy in your current database. You can skip this step if you don’t know of any proxy server IP address.
Check anonymous VPN:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB \
--ip YOUR_PROXY_TEST_IP \
is_anonymous_vpn
Check public proxy:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB \
--ip YOUR_PROXY_TEST_IP \
is_public_proxy
Check residential proxy:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB \
--ip YOUR_PROXY_TEST_IP \
is_residential_proxy
Check Tor exit node:
sudo mmdblookup \
--file /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB \
--ip YOUR_PROXY_TEST_IP \
is_tor_exit_node
These fields are Boolean values.
5. Create the Lua lookup script #
We will now create a Lua script that loads both MMDB databases when HAProxy starts, performs lookups against the client’s IP address, and stores the results as HAProxy transaction variables.
Create the file:
sudo nano /etc/haproxy/ip2lookup.lua
Paste the following code and save the file:
local mmdb = require("mmdb")
local args = table.pack(...)
if args.n < 2 then
error(
"ip2lookup.lua requires two arguments: " ..
"IP2Location DB11 MMDB and IP2Proxy PX10 MMDB"
)
end
local ip2location_path = args[1]
local ip2proxy_path = args[2]
--
-- Load IP2Location DB11.
--
local ip2location_db, geo_error = mmdb.read(ip2location_path)
if not ip2location_db then
error(
"Unable to open IP2Location database: " ..
tostring(geo_error)
)
end
--
-- Load IP2Proxy PX10.
--
local ip2proxy_db, proxy_error = mmdb.read(ip2proxy_path)
if not ip2proxy_db then
error(
"Unable to open IP2Proxy database: " ..
tostring(proxy_error)
)
end
--
-- Perform either an IPv4 or IPv6 lookup.
--
local function lookup(db, ip)
ip = tostring(ip)
if ip:find(":", 1, true) then
return db:search_ipv6(ip)
end
return db:search_ipv4(ip)
end
--
-- Safely retrieve a nested value.
--
local function get_value(record, ...)
local value = record
for i = 1, select("#", ...) do
if type(value) ~= "table" then
return nil
end
local key = select(i, ...)
value = value[key]
if value == nil then
return nil
end
end
return value
end
--
-- Convert a missing MMDB field into an empty string.
--
local function value_to_string(value)
if value == nil then
return ""
end
return tostring(value)
end
--
-- Convert Boolean values to text.
--
local function boolean_to_string(value)
if value == true then
return "true"
end
return "false"
end
--
-- Register a HAProxy HTTP request action called "ip2lookup".
--
core.register_action(
"ip2lookup",
{ "http-req" },
function(txn)
local ip = tostring(txn.sf:src())
-----------------------------------------------------------
-- IP2Location DB11
-----------------------------------------------------------
local geo = lookup(ip2location_db, ip)
txn:set_var(
"txn.ip2location_country_code",
value_to_string(
get_value(
geo,
"country",
"iso_code"
)
)
)
txn:set_var(
"txn.ip2location_country_name",
value_to_string(
get_value(
geo,
"country",
"names",
"en"
)
)
)
txn:set_var(
"txn.ip2location_region_code",
value_to_string(
get_value(
geo,
"subdivisions",
1,
"iso_code"
)
)
)
txn:set_var(
"txn.ip2location_region_name",
value_to_string(
get_value(
geo,
"subdivisions",
1,
"names",
"en"
)
)
)
txn:set_var(
"txn.ip2location_city",
value_to_string(
get_value(
geo,
"city",
"names",
"en"
)
)
)
txn:set_var(
"txn.ip2location_postal_code",
value_to_string(
get_value(
geo,
"postal",
"code"
)
)
)
txn:set_var(
"txn.ip2location_latitude",
value_to_string(
get_value(
geo,
"location",
"latitude"
)
)
)
txn:set_var(
"txn.ip2location_longitude",
value_to_string(
get_value(
geo,
"location",
"longitude"
)
)
)
txn:set_var(
"txn.ip2location_timezone",
value_to_string(
get_value(
geo,
"location",
"time_zone"
)
)
)
-----------------------------------------------------------
-- IP2Proxy PX10
-----------------------------------------------------------
local proxy = lookup(ip2proxy_db, ip)
txn:set_var(
"txn.ip2proxy_is_anonymous",
boolean_to_string(
get_value(
proxy,
"is_anonymous"
)
)
)
txn:set_var(
"txn.ip2proxy_is_anonymous_vpn",
boolean_to_string(
get_value(
proxy,
"is_anonymous_vpn"
)
)
)
txn:set_var(
"txn.ip2proxy_is_hosting_provider",
boolean_to_string(
get_value(
proxy,
"is_hosting_provider"
)
)
)
txn:set_var(
"txn.ip2proxy_is_public_proxy",
boolean_to_string(
get_value(
proxy,
"is_public_proxy"
)
)
)
txn:set_var(
"txn.ip2proxy_is_residential_proxy",
boolean_to_string(
get_value(
proxy,
"is_residential_proxy"
)
)
)
txn:set_var(
"txn.ip2proxy_is_tor_exit_node",
boolean_to_string(
get_value(
proxy,
"is_tor_exit_node"
)
)
)
end
)
HAProxy can then use these variables anywhere normal HAProxy variables are supported.
6. Create a simple backend web application #
To make the tutorial easy to test, we will create a small Python web server.
The server simply displays the IP2Location and IP2Proxy headers it receives from HAProxy.
Create the file:
sudo nano /opt/ip2location-haproxy-demo.py
Paste the code and save the file:
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
interesting_headers = {}
for name, value in self.headers.items():
lower_name = name.lower()
if (
lower_name.startswith("x-ip2location-")
or lower_name.startswith("x-ip2proxy-")
or lower_name == "x-demo-route"
):
interesting_headers[name] = value
response = {
"message": "HAProxy IP intelligence demo",
"headers_received_by_backend": interesting_headers,
}
body = json.dumps(
response,
indent=2
).encode("utf-8")
self.send_response(200)
self.send_header(
"Content-Type",
"application/json"
)
self.send_header(
"Content-Length",
str(len(body))
)
self.end_headers()
self.wfile.write(body)
server = HTTPServer(
("127.0.0.1", 8080),
Handler
)
print(
"Demo web server listening on http://127.0.0.1:8080"
)
server.serve_forever()
Run it:
sudo python3 /opt/ip2location-haproxy-demo.py
Expected output:
Demo web server listening on http://127.0.0.1:8080
Open another SSH session and test the backend directly:
curl http://127.0.0.1:8080/
You should get something similar to:
{
"message": "HAProxy IP intelligence demo",
"headers_received_by_backend": {}
}

The header list is currently empty because we haven’t sent the request through HAProxy yet.
7. Back up the HAProxy configuration #
Create a backup before making any changes:
sudo cp \
/etc/haproxy/haproxy.cfg \
/etc/haproxy/haproxy.cfg.backup
Edit HAProxy:
sudo nano /etc/haproxy/haproxy.cfg
8. Configure HAProxy #
For this tutorial, use the following configuration:
global
log /dev/log local0
log /dev/log local1 notice
user haproxy
group haproxy
daemon
lua-load /etc/haproxy/ip2lookup.lua /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
defaults
log global
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web
bind *:80
mode http
############################################################
# LOCAL DEMONSTRATION IP OVERRIDE
#
# Requests originating from localhost can specify:
#
# X-Demo-IP: 8.8.8.8
#
# HAProxy will temporarily use that IP as the source address.
#
# Remove this section after testing.
############################################################
acl local_demo src 127.0.0.0/8
acl demo_ip_present req.hdr(X-Demo-IP) -m found
http-request set-src req.hdr_ip(X-Demo-IP) if local_demo demo_ip_present
############################################################
# PERFORM IP2LOCATION + IP2PROXY LOOKUPS
############################################################
http-request lua.ip2lookup
############################################################
# REMOVE DEMONSTRATION HEADER
############################################################
http-request del-header X-Demo-IP
############################################################
# IP2LOCATION ACLs
############################################################
acl country_my var(txn.ip2location_country_code) -m str MY
acl country_sg var(txn.ip2location_country_code) -m str SG
acl country_us var(txn.ip2location_country_code) -m str US
############################################################
# IP2PROXY ACLs
############################################################
acl is_anonymous var(txn.ip2proxy_is_anonymous) -m str true
acl is_vpn var(txn.ip2proxy_is_anonymous_vpn) -m str true
acl is_hosting var(txn.ip2proxy_is_hosting_provider) -m str true
acl is_public_proxy var(txn.ip2proxy_is_public_proxy) -m str true
acl is_residential_proxy var(txn.ip2proxy_is_residential_proxy) -m str true
acl is_tor var(txn.ip2proxy_is_tor_exit_node) -m str true
############################################################
# SECURITY EXAMPLE
#
# Reject Tor exit nodes before the request reaches the
# backend application.
############################################################
http-request deny deny_status 403 if is_tor
############################################################
# IP2LOCATION REQUEST HEADERS
############################################################
http-request set-header X-IP2Location-Country-Code %[var(txn.ip2location_country_code)]
http-request set-header X-IP2Location-Country-Name %[var(txn.ip2location_country_name)]
http-request set-header X-IP2Location-Region-Code %[var(txn.ip2location_region_code)]
http-request set-header X-IP2Location-Region-Name %[var(txn.ip2location_region_name)]
http-request set-header X-IP2Location-City %[var(txn.ip2location_city)]
http-request set-header X-IP2Location-Postal-Code %[var(txn.ip2location_postal_code)]
http-request set-header X-IP2Location-Latitude %[var(txn.ip2location_latitude)]
http-request set-header X-IP2Location-Longitude %[var(txn.ip2location_longitude)]
http-request set-header X-IP2Location-Time-Zone %[var(txn.ip2location_timezone)]
############################################################
# IP2PROXY REQUEST HEADERS
############################################################
http-request set-header X-IP2Proxy-Is-Anonymous %[var(txn.ip2proxy_is_anonymous)]
http-request set-header X-IP2Proxy-Is-Anonymous-VPN %[var(txn.ip2proxy_is_anonymous_vpn)]
http-request set-header X-IP2Proxy-Is-Hosting-Provider %[var(txn.ip2proxy_is_hosting_provider)]
http-request set-header X-IP2Proxy-Is-Public-Proxy %[var(txn.ip2proxy_is_public_proxy)]
http-request set-header X-IP2Proxy-Is-Residential-Proxy %[var(txn.ip2proxy_is_residential_proxy)]
http-request set-header X-IP2Proxy-Is-Tor-Exit-Node %[var(txn.ip2proxy_is_tor_exit_node)]
############################################################
# COUNTRY-BASED ROUTING
############################################################
use_backend app_malaysia if country_my
default_backend app_global
backend app_malaysia
mode http
http-request set-header X-Demo-Route malaysia
server app1 127.0.0.1:8080 check
backend app_global
mode http
http-request set-header X-Demo-Route global
server app1 127.0.0.1:8080 check
The important part is this instruction:
http-request lua.ip2lookup
It executes the Lua function before HAProxy evaluates the IP2Location and IP2Proxy ACLs.
9. Validate the HAProxy configuration #
Do not restart HAProxy immediately.
First validate the configuration:
sudo haproxy -c \
-f /etc/haproxy/haproxy.cfg
If an error occurs, fix it before restarting HAProxy.
10. Restart HAProxy #
Restart the service:
sudo systemctl restart haproxy
Check its status:
sudo systemctl status haproxy \
--no-pager
If HAProxy fails to start, view the logs:
sudo journalctl \
-u haproxy \
-n 100 \
--no-pager
11. Demo: IP2Location lookup #
Normally, HAProxy looks up the real connecting client IP.
For testing, our configuration allows localhost to specify an IP using:
X-Demo-IP
For example:
curl \
-H "X-Demo-IP: 8.8.8.8" \
http://127.0.0.1/
The result should resemble:
{
"message": "HAProxy IP intelligence demo",
"headers_received_by_backend": {
"x-ip2location-country-code": "US",
"x-ip2location-country-name": "United States of America",
"x-ip2location-region-code": "CA",
"x-ip2location-region-name": "California",
"x-ip2location-city": "Mountain View",
"x-ip2location-postal-code": "94043",
"x-ip2location-latitude": "37.38605",
"x-ip2location-longitude": "-122.08385",
"x-ip2location-time-zone": "America/Los_Angeles",
"x-ip2proxy-is-anonymous": "false",
"x-ip2proxy-is-anonymous-vpn": "false",
"x-ip2proxy-is-hosting-provider": "false",
"x-ip2proxy-is-public-proxy": "false",
"x-ip2proxy-is-residential-proxy": "false",
"x-ip2proxy-is-tor-exit-node": "false",
"x-demo-route": "global"
}
}

The actual values will depend on the database release.
The important part is that the backend application did not perform any lookup itself.
The sequence was:

12. Demo: country-based ACL routing #
The configuration defines:
acl country_my var(txn.ip2location_country_code) -m str MY
HAProxy then uses:
use_backend app_malaysia if country_my
The logic is :

Find an IP that your current DB11 identifies as Malaysia and test it:
curl \
-H "X-Demo-IP: YOUR_MALAYSIAN_TEST_IP" \
http://127.0.0.1/
You should see:
"X-Demo-Route": "malaysia"

Try another IP:
curl \
-H "X-Demo-IP: 8.8.8.8" \
http://127.0.0.1/
You should normally see:
"X-Demo-Route": "global"
This demonstrates that IP2Location data can directly control HAProxy’s backend selection.
13. Route several countries to different backend servers #
You can easily expand the ACL rules.
For example:
acl visitor_my var(txn.ip2location_country_code) -m str MY
acl visitor_sg var(txn.ip2location_country_code) -m str SG
acl visitor_us var(txn.ip2location_country_code) -m str US
use_backend malaysia_servers if visitor_my
use_backend singapore_servers if visitor_sg
use_backend us_servers if visitor_us
default_backend global_servers
This could produce an architecture such as:

This can be useful for regional routing, localization, performance optimization, and infrastructure segmentation.
14. Demo: IP2Proxy ACLs #
The Lua script converts PX10 Boolean results into the strings:
true
false
That makes them easy to use both in HAProxy ACL rules and HTTP headers.
General anonymous traffic:
acl is_anonymous var(txn.ip2proxy_is_anonymous) -m str true
Anonymous VPN:
acl is_vpn var(txn.ip2proxy_is_anonymous_vpn) -m str true
Hosting provider:
acl is_hosting var(txn.ip2proxy_is_hosting_provider) -m str true
Public proxy:
acl is_public_proxy var(txn.ip2proxy_is_public_proxy) -m str true
Residential proxy:
acl is_residential_proxy var(txn.ip2proxy_is_residential_proxy) -m str true
Tor exit node:
acl is_tor var(txn.ip2proxy_is_tor_exit_node) -m str true
Once these ACLs have been defined, they can be used in normal HAProxy rules.
15. Demo: block Tor exit nodes #
Our example configuration contains:
http-request deny deny_status 403 if is_tor
Find an IP that your current PX10 database identifies as a Tor exit node.
Then run:
curl -i \
-H "X-Demo-IP: YOUR_TOR_TEST_IP" \
http://127.0.0.1/
If PX10 identifies the IP as a Tor exit node, HAProxy should return:
HTTP/1.1 403 Forbidden
The request flow becomes:

This is one of the major benefits of performing the lookup in HAProxy: unwanted traffic can be rejected before it consumes application resources.
16. Block anonymous VPN traffic #
If your website policy requires VPN blocking, you could add:
http-request deny deny_status 403 if is_vpn
Or block both Tor and anonymous VPN traffic:
http-request deny deny_status 403 if is_tor
http-request deny deny_status 403 if is_vpn
However, blocking all VPN traffic is not appropriate for every website.
Instead, you could send those visitors to another backend for additional verification:
use_backend verification_servers if is_vpn
Or send all anonymous traffic there:
use_backend verification_servers if is_anonymous
This gives you considerably more flexibility than simply allowing or blocking traffic.
17. Combine several IP2Proxy classifications #
You can define several ACLs:
acl anonymous var(txn.ip2proxy_is_anonymous) -m str true
acl hosting var(txn.ip2proxy_is_hosting_provider) -m str true
acl residential_proxy var(txn.ip2proxy_is_residential_proxy) -m str true
Then use them in separate policies:
use_backend verification_servers if anonymous
use_backend verification_servers if residential_proxy
Another example would be to treat data center traffic differently:
use_backend restricted_backend if hosting
You are not limited to blocking. The same IP intelligence can affect routing, authentication, rate limiting, logging, or any other HAProxy policy.
18. Headers available to your final website #
HAProxy sends the following IP2Location information to the backend:
X-IP2Location-Country-Code
X-IP2Location-Country-Name
X-IP2Location-Region-Code
X-IP2Location-Region-Name
X-IP2Location-City
X-IP2Location-Postal-Code
X-IP2Location-Latitude
X-IP2Location-Longitude
X-IP2Location-Time-Zone
It also sends:
X-IP2Proxy-Is-Anonymous
X-IP2Proxy-Is-Anonymous-VPN
X-IP2Proxy-Is-Hosting-Provider
X-IP2Proxy-Is-Public-Proxy
X-IP2Proxy-Is-Residential-Proxy
X-IP2Proxy-Is-Tor-Exit-Node
This means your final web application does not need its own IP2Location SDK, IP2Proxy SDK, MMDB reader, or direct access to the MMDB files.
HAProxy already performed the lookup.
19. Access the headers in PHP #
Read the country code:
<?php
$countryCode = $_SERVER['HTTP_X_IP2LOCATION_COUNTRY_CODE'] ?? '';
echo $countryCode;
Read the city:
<?php
$city = $_SERVER['HTTP_X_IP2LOCATION_CITY'] ?? '';
echo $city;
Check for an anonymous VPN:
<?php
$isVpn = $_SERVER['HTTP_X_IP2PROXY_IS_ANONYMOUS_VPN'] ?? 'false';
if ($isVpn === 'true') {
echo 'Anonymous VPN detected';
}
Check for Tor:
<?php
$isTor = $_SERVER['HTTP_X_IP2PROXY_IS_TOR_EXIT_NODE'] ?? 'false';
if ($isTor === 'true') {
echo 'Tor exit node detected';
}
20. Prevent clients from spoofing the headers #
A visitor could attempt to send something like:
X-IP2Location-Country-Code: MY
or:
X-IP2Proxy-Is-Tor-Exit-Node: false
But the HAProxy configuration uses set-header, for example:
http-request set-header X-IP2Location-Country-Code %[var(txn.ip2location_country_code)]
HAProxy therefore replaces the client’s value with the value generated by the actual DB11 lookup.
The same applies to the IP2Proxy headers.
Your application should nevertheless trust these headers only when the connection came through your trusted HAProxy instance.
A production architecture should look like:

Avoid exposing the application publicly in a way that allows users to bypass HAProxy:

Otherwise, a visitor could send fabricated IP intelligence headers directly to your application.
21. Make sure you are looking up the real client IP #
The Lua script uses:
local ip = tostring(txn.sf:src())
If the visitor connects directly to HAProxy:

then src normally represents the visitor’s IP.
However, many production architectures look like:

In that situation, HAProxy may see the IP of the upstream proxy rather than the visitor.
You need to restore the real visitor IP using a trusted mechanism before running lua.ip2lookup.
For example, your upstream proxy might use the PROXY protocol, or it might provide a trusted forwarding header.
Do not blindly accept arbitrary X-Forwarded-For values from the public Internet.
Otherwise, a visitor could send:
X-Forwarded-For: 1.2.3.4
and cause HAProxy to perform the lookup against an IP chosen by the attacker.
22. Why this tutorial uses lua-load #
Our configuration uses:
lua-load /etc/haproxy/ip2lookup.lua /etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB /etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
HAProxy also supports per-thread Lua states through:
lua-load-per-thread
A per-thread Lua state can improve concurrency because several HAProxy threads can execute Lua simultaneously.
However, lua-mmdb loads MMDB data into Lua memory.
If you use a separate Lua state for every HAProxy thread, the database memory usage may conceptually become:

That can consume considerably more memory when DB11 and PX10 are large.
For a straightforward tutorial, lua-load keeps the setup simpler and avoids loading a separate copy for every HAProxy thread.
For a high-traffic production environment, benchmark both CPU throughput and memory usage before deciding which approach is better for your workload.
23. Updating DB11 and PX10 #
When a new database release becomes available, replace the existing DB11 file:
sudo mv \
NEW-IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB
Replace PX10:
sudo mv \
NEW-IP2PROXY-PX10.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Restore ownership:
sudo chown root:haproxy \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Restore permissions:
sudo chmod 640 \
/etc/haproxy/mmdb/IP2LOCATION-DB11.MMDB \
/etc/haproxy/mmdb/IP2PROXY-PX10.MMDB
Validate the configuration:
sudo haproxy -c \
-f /etc/haproxy/haproxy.cfg
Then reload HAProxy:
sudo systemctl reload haproxy
The newly started HAProxy process will load the updated databases.
24. Remove the demonstration IP override before production #
The following rules exist only to make local testing convenient:
acl local_demo src 127.0.0.0/8
acl demo_ip_present req.hdr(X-Demo-IP) -m found
http-request set-src req.hdr_ip(X-Demo-IP) if local_demo demo_ip_present
Remove them before using the configuration in production.
You can also remove:
http-request del-header X-Demo-IP
once the testing mechanism is gone.
In production, HAProxy should use the actual trusted client IP.
25. What can HAProxy do with the lookup results? #
With IP2Location DB11, you can implement country-based routing, regional server selection, localization, time-zone detection, location-aware headers, logging, and other geolocation-driven policies.
With IP2Proxy PX10, you can implement anonymous proxy detection, VPN detection, Tor detection, public proxy detection, residential proxy detection, hosting-provider detection, additional verification, routing policies, or access-control decisions.
The overall design becomes:

Conclusion #
HAProxy Community does not need a dedicated IP2Location or IP2Proxy HAProxy plugin to use the IP2Location DB11 MMDB and IP2Proxy PX10 MMDB databases.
On Debian 13, the required packages can be installed directly with:
sudo apt install -y \
haproxy \
lua-mmdb \
lua5.4 \
mmdb-bin
HAProxy then loads a small Lua script:

The lookup results can be used for HAProxy ACLs, backend routing, request blocking, request headers, response headers, and application logic.
For example, IP2Location DB11 can identify the visitor’s country and route the request to a regional backend, while IP2Proxy PX10 can detect a Tor exit node and reject the request before it ever reaches the application.
At the same time, HAProxy can pass useful IP2Location and IP2Proxy data to PHP, Python, Node.js, or another backend through ordinary HTTP request headers.
