
Intro #
Caddy is a modern, open-source web server written in Go. It is commonly used as a reverse proxy, static file server, and HTTPS-enabled web server because it can automatically obtain and renew TLS certificates with very little configuration. Caddy is also modular, which means its functionality can be extended by compiling additional modules into the Caddy binary. In Caddy, middleware refers to a handler that runs during the HTTP request processing pipeline. Middleware can inspect, modify, enrich, block, or route requests before they reach the final application or upstream server.
The IP2Location Caddy middleware adds IP geolocation data to incoming requests. It detects the visitorβs IP address, looks up geolocation information using either a local IP2Location BIN database or the IP2Location.io API, and injects the result into HTTP request headers. These headers can then be used by your backend application, such as PHP, Node.js, Python, or another upstream service.
Prerequisites #
This guide uses Debian 13 and compiles a custom Caddy binary with the IP2Location middleware included.
You will need:
- A Debian 13 server.
- Root or sudo access.
- Go installed.
- xcaddy installed.
- Either:
- an IP2Location BIN database file for local lookup mode, or
- an IP2Location.io API key for remote lookup mode.
For this tutorial, the remote API mode is used first because it is easier to test. Local BIN mode is also shown later.
Step 1: Update the Debian server #
Run the following commands:
sudo apt update
sudo apt upgrade -y
Step 2: Install Go on Debian #
Debian 13 includes Go in its package repository, so you can install it using APT:
sudo apt install -y golang-go
Verify the installation:
go version
You should see output similar to:

If your Go version is too old for the latest Caddy release, install a newer Go version from the official Go website instead.
Step 3: Install xcaddy #
xcaddy is the recommended tool for building Caddy with third-party modules.
Install it using Go:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

The binary will usually be installed into ~/go/bin/xcaddy.
Add Goβs binary directory to your PATH:
echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.profile
source ~/.profile
Verify that xcaddy is available:
xcaddy version

Step 4: Compile Caddy with the IP2Location middleware #
Create a working directory:
mkdir -p ~/build-caddy-ip2location
cd ~/build-caddy-ip2location
Compile Caddy with the IP2Location middleware:
xcaddy build --with github.com/ip2location/ip2location-caddy
After the build finishes, you should have a new caddy binary in the current directory.

Check that the binary works:
./caddy version
List the compiled modules:
./caddy list-modules | grep ip2location
You should see http.handlers.ip2location.

This confirms that the IP2Location middleware has been compiled into Caddy successfully.
Step 5: Install the custom Caddy binary #
Move the compiled binary to /usr/local/bin:
sudo mv ./caddy /usr/local/bin/caddy
sudo chmod +x /usr/local/bin/caddy
Verify the installed binary:
/usr/local/bin/caddy version
/usr/local/bin/caddy list-modules | grep ip2location

Step 6: Create a simple backend server for testing #
The IP2Location middleware adds geolocation data to request headers. To test this clearly, create a small Python backend that prints all X-IP2Location headers it receives.
Create a test directory:
mkdir -p ~/ip2location-caddy-test
cd ~/ip2location-caddy-test
Create a file named echo_headers.py:
cat > echo_headers.py <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"IP2Location headers received by backend:\n\n")
found = False
for key, value in self.headers.items():
if key.lower().startswith("x-ip2location"):
found = True
self.wfile.write(f"{key}: {value}\n".encode())
if not found:
self.wfile.write(b"No X-IP2Location headers found.\n")
HTTPServer(("127.0.0.1", 9000), Handler).serve_forever()
PY
Start the backend server:
python3 echo_headers.py
Keep this terminal open.
Step 7: Configure Caddy using IP2Location.io remote mode #
Open a second terminal and create a Caddyfile:
cd ~/ip2location-caddy-test
nano Caddyfile
Add the following configuration:
{
order ip2location before reverse_proxy
}
:8080 {
ip2location {
mode remote
api_key YOUR_IP2LOCATION_IO_API_KEY
header_prefix X-IP2Location
}
reverse_proxy 127.0.0.1:9000
}
Replace YOUR_IP2LOCATION_IO_API_KEY with your actual IP2Location.io API key. If you donβt have an API key yet, you can sign up for a free IP2Location.io API key.

The order ip2location before reverse_proxy line is important. It tells Caddy to run the IP2Location middleware before forwarding the request to the backend server. This allows the backend to receive the geolocation headers.
Step 8: Validate the Caddyfile #
Run:
caddy validate --config ./Caddyfile --adapter caddyfile
If the configuration is valid, Caddy should report that the config is valid.

Step 9: Run the custom Caddy binary #
Start Caddy:
sudo caddy run --config ./Caddyfile --adapter caddyfile
Keep this terminal open.
Step 10: Test the IP2Location headers #
Open a third terminal and send a request to Caddy:
curl -i http://127.0.0.1:8080/
Because this request comes from localhost, the detected IP may be 127.0.0.1, which usually does not return useful geolocation data.

For a better test, send a simulated client IP using X-Forwarded-For:
curl -i http://127.0.0.1:8080/ -H "X-Forwarded-For: 8.8.8.8"
You should see output similar to this:
IP2Location headers received by backend:
X-Ip2location-Address-Type: Anycast
X-Ip2location-Area-Code: 650
X-Ip2location-As: Google LLC
X-Ip2location-As-Cidr: 8.8.8.0/24
X-Ip2location-As-Domain: google.com
X-Ip2location-As-Usage-Type: DCH
X-Ip2location-Asn: 15169
X-Ip2location-Category: IAB19-11
X-Ip2location-City: Mountain View
X-Ip2location-Country-Code: US
X-Ip2location-Country-Name: United States of America
X-Ip2location-District: Santa Clara County
X-Ip2location-Domain: google.com
X-Ip2location-Elevation: 32
X-Ip2location-Idd-Code: 1
X-Ip2location-Isp: Google LLC
X-Ip2location-Latitude: 37.386050
X-Ip2location-Longitude: -122.083850
X-Ip2location-Mcc: -
X-Ip2location-Mnc: -
X-Ip2location-Mobile-Brand: -
X-Ip2location-Net-Speed: T1
X-Ip2location-Region: California
X-Ip2location-Time-Zone: -07:00
X-Ip2location-Usage-Type: DCH
X-Ip2location-Weather-Station-Code: USCA0746
X-Ip2location-Weather-Station-Name: Mountain View
X-Ip2location-Zip-Code: 94035

The exact result may vary depending on the IP address and database/API response.
Step 11: Configure local BIN database mode #
Remote mode uses the IP2Location.io API. If you prefer to perform lookups locally, download an IP2Location BIN database and upload it to your server. You can register for the free LITE BIN database or subscribe to the commercial BIN database for better accuracy. Upon login to the user dashboard, go to the Download section. Then, download the zipped file containing the BIN file that you wish to use and save it to your home folder. Extract the BIN file into the same folder. We are using the DB26 IPv6 BIN.
Move the BIN file into the shared folder:
sudo mkdir -p /usr/local/share/ip2location
sudo mv IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN /usr/local/share/ip2location/
sudo chmod 644 /usr/local/share/ip2location/IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN
Then update your Caddyfile:
{
order ip2location before reverse_proxy
}
:8080 {
ip2location {
mode local
bin_path /usr/local/share/ip2location/IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN
header_prefix X-IP2Location
}
reverse_proxy 127.0.0.1:9000
}
Validate the configuration again (using sudo due to the BIN file permission):
sudo caddy validate --config ./Caddyfile --adapter caddyfile

Run Caddy:
sudo caddy run --config ./Caddyfile --adapter caddyfile
Test again:
curl -i http://127.0.0.1:8080/ -H "X-Forwarded-For: 8.8.8.8"
You should get the same results as the API.
Conclusion #
By compiling Caddy with the IP2Location middleware, you can enrich incoming HTTP requests with geolocation headers before they reach your backend application. This is useful for analytics, fraud detection, localization, access control, logging, and region-specific business logic. The custom binary can use either the IP2Location.io API for remote lookups or a local IP2Location BIN database for offline lookups.
