
Intro #
Login pages are common targets for credential stuffing, password spraying, automated account takeover attempts, and other abusive activities. Attackers may use VPNs, Tor, residential proxies, data center servers, or other anonymizing networks to hide their original IP addresses and distribute login attempts across different networks.
However, blocking every visitor who connects through a proxy is not always the best approach. Legitimate users may also rely on VPNs, privacy services, or corporate networks.
Instead, proxy information can be treated as an additional risk signal when evaluating login attempts.
In this tutorial, we will use the IP2Proxy Caddy Middleware together with the commercial IP2Proxy PX12 BIN database to inspect an incoming connection before it reaches a PHP login page.
The Caddy middleware performs the IP2Proxy lookup and passes the resulting proxy intelligence to PHP through HTTP request headers. Based on this information, the PHP application can decide whether to allow normal authentication, require an additional verification step, or reject a high-risk login attempt.
The overall flow is:

Instead of using IP2Proxy as a simple proxy blocker, this tutorial demonstrates how proxy intelligence can become part of an adaptive authentication workflow.
The IP2Proxy Caddy Middleware supports local IP2Proxy BIN databases and remote queries through the IP2Location.io API. In this tutorial, we will use a local commercial PX12 BIN database, so lookups are performed directly on the server.
Why use the commercial IP2Proxy PX12 database? #
The commercial PX12 database is suitable for this demonstration because it provides broad proxy coverage together with the additional fields needed for risk-based decisions.
PX12 can identify network types such as VPN, Tor, public proxies, web proxies, data center hosting, residential proxies, consumer privacy networks and enterprise private networks.
It also provides information including country, region, city, ISP, domain, usage type, ASN, last seen, threat, provider and Fraud Score.
The Fraud Score ranges from 0 to 99, giving the application an additional signal when evaluating login risk.
For this article, the application will evaluate several IP2Proxy fields rather than relying only on whether the IP is marked as a proxy.
Prerequisites #
This tutorial uses Debian 13, Caddy, Go, xcaddy, PHP 8.4 with PHP-FPM, the IP2Proxy Caddy Middleware, and a commercial IP2Proxy PX12 BIN database.
Step 1: Update Debian 13 #
Update the package repository & upgrade installed packages:
sudo apt update
sudo apt upgrade -y
Step 2: Install Caddy, Go, PHP and required packages #
Install the required packages:
sudo apt install -y \
caddy \
golang-go \
php-fpm \
php-cli \
git \
unzip \
curl \
ca-certificates
Verify Go:
go version
Verify PHP:
php -v
Debian 13 uses PHP 8.4 as its default PHP version.

Check PHP-FPM:
sudo systemctl status php8.4-fpm
If PHP-FPM is not running, enable and start it:
sudo systemctl enable --now php8.4-fpm
Check the PHP-FPM Unix socket:
ls -l /run/php/php8.4-fpm.sock

We will use this socket later.
Step 3: Install xcaddy #
Caddy supports third-party modules, but custom modules need to be compiled into the Caddy binary.
Install xcaddy:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
Add the Go binary directory to your PATH:
echo 'export PATH="$PATH:$HOME/go/bin"' >> ~/.profile
Reload your shell profile:
source ~/.profile
Verify the installation:
xcaddy version

Caddy recommends xcaddy for building custom Caddy binaries.
Step 4: Build Caddy with the IP2Proxy middleware #
Create a build directory:
mkdir -p ~/build-caddy-ip2proxy
Enter the directory:
cd ~/build-caddy-ip2proxy
Build Caddy with the IP2Proxy middleware:
xcaddy build --with github.com/ip2location/ip2proxy-caddy
Check the resulting binary:
./caddy version
Verify that the IP2Proxy module is included:
./caddy list-modules | grep ip2proxy

Step 5: Install the custom Caddy binary #
The Debian Caddy package provides the systemd service and configuration directory, but its default binary does not contain the IP2Proxy module.
Use dpkg-divert so the package-managed binary is preserved:
sudo dpkg-divert \
--divert /usr/bin/caddy.default \
--rename /usr/bin/caddy
Move your custom Caddy binary:
sudo mv ./caddy /usr/bin/caddy.custom
Set executable permissions:
sudo chmod 755 /usr/bin/caddy.custom
Register the original Caddy binary:
sudo update-alternatives \
--install /usr/bin/caddy caddy /usr/bin/caddy.default 10
Register the custom build:
sudo update-alternatives \
--install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
Check which binary is active:
readlink -f /usr/bin/caddy
It should return:
/usr/bin/caddy.custom
Verify again:
caddy list-modules | grep ip2proxy

Step 6: Download the commercial PX12 BIN database #
Download the commercial IP2Proxy PX12 BIN database from your IP2Location account.
Upload the downloaded ZIP file to your home folder like below:
~/ip2proxy-px12.zip
Create an extraction directory:
mkdir -p ~/ip2proxy-px12
Extract the database:
unzip ~/ip2proxy-px12.zip -d ~/ip2proxy-px12
Check the extracted files:
find ~/ip2proxy-px12 -type f
You should find a commercial PX12 BIN database similar to:
IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER-FRAUDSCORE.BIN

Step 7: Install the PX12 database #
Create a directory for the database:
sudo mkdir -p /var/lib/ip2proxy
Move the BIN database and give it a shorter filename:
sudo mv \
~/ip2proxy-px12/IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER-FRAUDSCORE.BIN \
/var/lib/ip2proxy/IP2PROXY-PX12.BIN
Set ownership:
sudo chown -R root:caddy /var/lib/ip2proxy
Set database permissions:
sudo chmod 640 /var/lib/ip2proxy/IP2PROXY-PX12.BIN
Protect the directory:
sudo chmod 750 /var/lib/ip2proxy
Verify:
sudo ls -lh /var/lib/ip2proxy/
Check that Caddy can read the database:
sudo -u caddy test -r /var/lib/ip2proxy/IP2PROXY-PX12.BIN && echo "Caddy can read PX12"
Expected result:
Caddy can read PX12

Step 8: Create the PHP login application #
Create the web directory:
sudo mkdir -p /var/www/ip2proxy-login
Create the PHP application:
sudo nano /var/www/ip2proxy-login/index.php
Paste the following code:
<?php
session_start();
/*
* IP2Proxy Adaptive Login Demo
*
* Fixed credentials and verification codes are used only
* to make this demonstration self-contained.
*
* Do not use hard-coded authentication credentials in
* a production application.
*/
$demoUsername = 'demo';
$demoPassword = 'DemoPass123!';
$demoVerificationCode = '246810';
function getIP2ProxyHeader(string $name, string $default = '-'): string
{
$key = 'HTTP_X_IP2PROXY_' .
strtoupper(str_replace('-', '_', $name));
if (!isset($_SERVER[$key])) {
return $default;
}
return trim((string) $_SERVER[$key]);
}
function escape(string $value): string
{
return htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
}
/*
* Read the IP2Proxy information inserted by Caddy.
*/
$signalAvailable =
isset($_SERVER['HTTP_X_IP2PROXY_IS_PROXY']);
$isProxyRaw = getIP2ProxyHeader('IS_PROXY', 'Unknown');
$proxyType =
strtoupper(getIP2ProxyHeader('PROXY_TYPE'));
$countryCode =
getIP2ProxyHeader('COUNTRY_CODE');
$countryName =
getIP2ProxyHeader('COUNTRY_NAME');
$region =
getIP2ProxyHeader('REGION');
$city =
getIP2ProxyHeader('CITY');
$isp =
getIP2ProxyHeader('ISP');
$domain =
getIP2ProxyHeader('DOMAIN');
$usageType =
getIP2ProxyHeader('USAGE_TYPE');
$asn =
getIP2ProxyHeader('ASN');
$asName =
getIP2ProxyHeader('AS');
$lastSeen =
getIP2ProxyHeader('LAST_SEEN');
$threat =
getIP2ProxyHeader('THREAT');
$provider =
getIP2ProxyHeader('PROVIDER');
$fraudScoreRaw =
getIP2ProxyHeader('FRAUD_SCORE', '0');
$isProxy =
strcasecmp($isProxyRaw, 'True') === 0;
$fraudScore =
is_numeric($fraudScoreRaw)
? (int) $fraudScoreRaw
: 0;
/*
* Determine whether a threat classification is present.
*/
$normalizedThreat =
strtoupper(trim($threat));
$hasThreat =
$normalizedThreat !== '' &&
$normalizedThreat !== '-';
/*
* Some network types can still be interesting for login
* security even when Is-Proxy is False.
*
* DCH means Data Center / Web Hosting / Transit.
*/
$elevatedNetworkTypes = [
'DCH'
];
$isElevatedNetworkType =
in_array(
$proxyType,
$elevatedNetworkTypes,
true
);
/*
* Demo risk policy:
*
* HIGH
* Fraud Score >= 80
* OR a threat classification is present.
*
* ELEVATED
* Proxy detected
* OR Fraud Score >= 50
* OR Proxy Type = DCH
* OR IP2Proxy information is unavailable.
*
* LOW
* None of the above conditions apply.
*/
$shouldBlock =
$signalAvailable &&
(
$fraudScore >= 80 ||
$hasThreat
);
$requiresVerification =
!$shouldBlock &&
(
!$signalAvailable ||
$isProxy ||
$fraudScore >= 50 ||
$isElevatedNetworkType
);
if ($shouldBlock) {
$riskLevel = 'HIGH';
} elseif ($requiresVerification) {
$riskLevel = 'ELEVATED';
} else {
$riskLevel = 'LOW';
}
$state = 'login';
$message = '';
if (
isset($_SESSION['pending_login']) &&
$_SESSION['pending_login'] === true
) {
$state = 'verify';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action =
$_POST['action'] ?? '';
/*
* Process username/password login.
*/
if ($action === 'login') {
$username =
(string) ($_POST['username'] ?? '');
$password =
(string) ($_POST['password'] ?? '');
if (
!hash_equals($demoUsername, $username) ||
!hash_equals($demoPassword, $password)
) {
$message =
'Invalid username or password.';
$state =
'login';
} elseif ($shouldBlock) {
unset($_SESSION['pending_login']);
http_response_code(403);
$state =
'blocked';
} elseif ($requiresVerification) {
$_SESSION['pending_login'] =
true;
$state =
'verify';
} else {
unset($_SESSION['pending_login']);
$state =
'success';
}
/*
* Process additional verification.
*/
} elseif ($action === 'verify') {
if (
!isset($_SESSION['pending_login']) ||
$_SESSION['pending_login'] !== true
) {
$message =
'Please log in first.';
$state =
'login';
} elseif ($shouldBlock) {
unset($_SESSION['pending_login']);
http_response_code(403);
$state =
'blocked';
} else {
$verificationCode =
(string) (
$_POST['verification_code'] ?? ''
);
if (
hash_equals(
$demoVerificationCode,
$verificationCode
)
) {
unset($_SESSION['pending_login']);
$state =
'success';
} else {
$message =
'Invalid verification code.';
$state =
'verify';
}
}
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<title>IP2Proxy Adaptive Login Demo</title>
<style>
body {
margin: 0;
padding: 40px 20px;
font-family: Arial, sans-serif;
background: #f5f7fa;
color: #222;
}
.container {
max-width: 780px;
margin: auto;
}
.card {
background: #fff;
padding: 28px;
margin-bottom: 20px;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
}
h1,
h2 {
margin-top: 0;
}
input {
width: 100%;
box-sizing: border-box;
padding: 12px;
margin: 8px 0 16px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 12px 22px;
border: 0;
border-radius: 5px;
cursor: pointer;
background: #222;
color: #fff;
}
.error,
.blocked {
color: #b42318;
}
.success {
color: #067647;
}
.low {
color: #067647;
font-weight: bold;
}
.elevated {
color: #b54708;
font-weight: bold;
}
.high {
color: #b42318;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
}
td {
padding: 9px 8px;
border-bottom: 1px solid #ddd;
vertical-align: top;
word-break: break-word;
}
td:first-child {
width: 38%;
font-weight: bold;
}
.note {
color: #666;
font-size: 14px;
line-height: 1.5;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>Secure Login</h1>
<?php if ($message !== ''): ?>
<p class="error">
<?= escape($message) ?>
</p>
<?php endif; ?>
<?php if ($state === 'login'): ?>
<p>
Enter the demo credentials to continue.
</p>
<form method="post">
<input
type="hidden"
name="action"
value="login"
>
<label for="username">
Username
</label>
<input
id="username"
type="text"
name="username"
autocomplete="username"
required
>
<label for="password">
Password
</label>
<input
id="password"
type="password"
name="password"
autocomplete="current-password"
required
>
<button type="submit">
Sign in
</button>
</form>
<p class="note">
Demo username:
<strong>demo</strong>
<br>
Demo password:
<strong>DemoPass123!</strong>
</p>
<?php elseif ($state === 'verify'): ?>
<h2>
Additional verification required
</h2>
<p>
Your network connection has triggered additional
verification before the login can continue.
</p>
<form method="post">
<input
type="hidden"
name="action"
value="verify"
>
<label for="verification_code">
Verification code
</label>
<input
id="verification_code"
type="text"
name="verification_code"
inputmode="numeric"
autocomplete="one-time-code"
required
>
<button type="submit">
Verify
</button>
</form>
<p class="note">
Demo verification code:
<strong>246810</strong>
</p>
<?php elseif ($state === 'blocked'): ?>
<h2 class="blocked">
Login blocked
</h2>
<p>
This login attempt was rejected because the
network connection exceeded the configured
risk threshold.
</p>
<?php elseif ($state === 'success'): ?>
<h2 class="success">
Login successful
</h2>
<p>
Authentication has completed successfully.
</p>
<?php endif; ?>
</div>
<div class="card">
<h2>IP2Proxy risk signals</h2>
<p>
Risk level:
<span class="<?= strtolower($riskLevel) ?>">
<?= escape($riskLevel) ?>
</span>
</p>
<table>
<tr>
<td>IP2Proxy available</td>
<td><?= $signalAvailable ? 'Yes' : 'No' ?></td>
</tr>
<tr>
<td>Proxy detected</td>
<td><?= escape($isProxyRaw) ?></td>
</tr>
<tr>
<td>Proxy type</td>
<td><?= escape($proxyType) ?></td>
</tr>
<tr>
<td>Country</td>
<td>
<?= escape($countryCode) ?>
-
<?= escape($countryName) ?>
</td>
</tr>
<tr>
<td>Region</td>
<td><?= escape($region) ?></td>
</tr>
<tr>
<td>City</td>
<td><?= escape($city) ?></td>
</tr>
<tr>
<td>ISP</td>
<td><?= escape($isp) ?></td>
</tr>
<tr>
<td>Domain</td>
<td><?= escape($domain) ?></td>
</tr>
<tr>
<td>Usage type</td>
<td><?= escape($usageType) ?></td>
</tr>
<tr>
<td>ASN</td>
<td><?= escape($asn) ?></td>
</tr>
<tr>
<td>AS</td>
<td><?= escape($asName) ?></td>
</tr>
<tr>
<td>Last seen</td>
<td><?= escape($lastSeen) ?></td>
</tr>
<tr>
<td>Threat</td>
<td><?= escape($threat) ?></td>
</tr>
<tr>
<td>Provider</td>
<td><?= escape($provider) ?></td>
</tr>
<tr>
<td>Fraud score</td>
<td><?= escape($fraudScoreRaw) ?></td>
</tr>
</table>
<p class="note">
The IP2Proxy information is displayed only for this
demonstration. A production login page would normally
process these signals on the server without exposing
them to the visitor.
</p>
</div>
</div>
</body>
</html>
Save the file.
Set ownership:
sudo chown -R www-data:www-data /var/www/ip2proxy-login
Set directory permissions:
sudo find /var/www/ip2proxy-login \
-type d \
-exec chmod 755 {} \;
Set file permissions:
sudo find /var/www/ip2proxy-login \
-type f \
-exec chmod 644 {} \;
Step 9: Understand the risk policy #
For a low-risk connection, IP2Proxy data must be available, the connection must not be identified as a proxy, the Fraud Score must be below 50, no threat must be present, and the network must not be classified as DCH.
The result is:

For an elevated-risk connection, additional verification is required when a proxy is detected, Fraud Score is at least 50, the network is classified as DCH, or IP2Proxy information is unavailable.

The demo verification code is:
246810
A real implementation could replace this with TOTP, a passkey, email verification, device verification or another MFA mechanism.
For high risk, the demo blocks the login when Fraud Score is 80 or higher or a threat classification is present:

The thresholds in this tutorial are demonstration values, not universal recommended thresholds.
Step 10: Why check more than Is-Proxy? #
It may seem sufficient to check:
$isProxy =
strcasecmp(
$_SERVER['HTTP_X_IP2PROXY_IS_PROXY'],
'True'
) === 0;
However, the Caddy middleware treats certain infrastructure classifications differently when generating Is-Proxy.
For example:
DCH
represents data center or hosting infrastructure.
A result could therefore look like:
X-IP2Proxy-Is-Proxy: False
X-IP2Proxy-Proxy-Type: DCH
For login protection, this is useful. A data center connection should not necessarily be called an anonymous proxy, but it can still be considered an elevated-risk login signal.
That is why our PHP application considers:
Is-Proxy
Proxy-Type
Threat
Fraud-Score
together.
Step 11: Configure Caddy #
Open the Caddyfile:
sudo nano /etc/caddy/Caddyfile
Replace it with:
{
order ip2proxy before php_fastcgi
}
example.com {
root * /var/www/ip2proxy-login
# Remove any client-supplied IP2Proxy values.
request_header -X-IP2Proxy-*
# This tutorial assumes Caddy is directly exposed
# to the Internet.
#
# Remove forwarded client-IP headers so a client
# cannot choose which IP address IP2Proxy checks.
request_header -CF-Connecting-IP
request_header -X-Real-IP
request_header -X-Forwarded-For
request_header -Forwarded
ip2proxy {
mode local
bin_path /var/lib/ip2proxy/IP2PROXY-PX12.BIN
header_prefix X-IP2Proxy
}
php_fastcgi unix//run/php/php8.4-fpm.sock
file_server
}
The key configuration is:
ip2proxy {
mode local
bin_path /var/lib/ip2proxy/IP2PROXY-PX12.BIN
header_prefix X-IP2Proxy
}
The mode local instructs the middleware to query the local PX12 BIN database.
The following ordering is also important:
{
order ip2proxy before php_fastcgi
}
It ensures IP2Proxy processes the request before PHP receives it.
Step 12: Prevent spoofed IP2Proxy headers #
Because HTTP headers originate from the client, an attacker could attempt to send:
X-IP2Proxy-Is-Proxy: False
or:
X-IP2Proxy-Fraud-Score: 0
The Caddy configuration removes incoming IP2Proxy headers:
request_header -X-IP2Proxy-*
The intended flow is therefore:

Step 13: Prevent client-IP spoofing #
The IP2Proxy middleware can inspect common forwarded client-IP headers before falling back to the remote connection address.
That behavior is useful when Caddy is behind a CDN or load balancer, but our demonstration assumes Caddy is directly Internet-facing.
Otherwise a client could attempt something like:
X-Forwarded-For: 8.8.8.8
Our Caddy configuration therefore removes:
request_header -CF-Connecting-IP
request_header -X-Real-IP
request_header -X-Forwarded-For
request_header -Forwarded
IP2Proxy will then use the actual connection address.
If Caddy is behind Cloudflare, another reverse proxy or a load balancer, you should instead establish a proper trusted-proxy configuration. Do not blindly remove the legitimate real-client-IP header in that deployment model.
Step 14: Validate the Caddyfile #
Validate the configuration:
sudo caddy validate \
--config /etc/caddy/Caddyfile \
--adapter caddyfile

Step 15: Restart Caddy #
Restart the service:
sudo systemctl restart caddy
Check status:
sudo systemctl status caddy
If something fails, check the log:
sudo journalctl \
-u caddy \
-n 100 \
--no-pager
Step 16: Open the demo #
From your computer, visit your own domain:
E.g., https://example.com
The page should display the login form together with the IP2Proxy risk information.

Step 17: Test a normal connection #
First access the server without intentionally using a VPN, Tor or proxy.
A normal connection could show:
IP2Proxy available: Yes
Proxy detected: False
Proxy type: –
Threat: –
Provider: –
Fraud score: 0
Risk level: LOW
The actual result depends on your public IP and current PX12 data.
Use:
Username: demo
Password: DemoPass123!
A low-risk connection should immediately produce:
Login successful

Step 18: Test with a VPN #
Connect your computer to a commercial VPN and reload the page.
A detected VPN may show:
Proxy detected: True
Proxy type: VPN
Provider: Example VPN Provider
Threat: –
Fraud score: 35
Risk level: ELEVATED
Enter:
Username: demo
Password: DemoPass123!
Instead of completing authentication immediately, the site should display:
Additional verification required

Enter:
246810
The login should then succeed.

The flow has changed from:

to:

This is the main point of the demonstration: a VPN user is not automatically rejected, but the authentication requirements become stronger.
Step 19: Test the high-risk flow #
A high-risk connection might look like:
Proxy detected: True
Proxy type: PUB
Threat: SPAM/SCANNER
Fraud score: 99
Risk level: HIGH

Submitting the correct username and password should produce:
Login blocked
and PHP returns:
HTTP 403

Step 20: What Caddy sends to PHP #
The lookup happens before PHP is executed:

The middleware can provide headers such as:
X-IP2Proxy-Is-Proxy
X-IP2Proxy-Proxy-Type
X-IP2Proxy-Country-Code
X-IP2Proxy-Country-Name
X-IP2Proxy-Region
X-IP2Proxy-City
X-IP2Proxy-ISP
X-IP2Proxy-Domain
X-IP2Proxy-Usage-Type
X-IP2Proxy-ASN
X-IP2Proxy-AS
X-IP2Proxy-Last-Seen
X-IP2Proxy-Threat
X-IP2Proxy-Provider
X-IP2Proxy-Fraud-Score
PHP receives them through $_SERVER.
For example:
$_SERVER['HTTP_X_IP2PROXY_IS_PROXY'];
Proxy type:
$_SERVER['HTTP_X_IP2PROXY_PROXY_TYPE'];
Provider:
$_SERVER['HTTP_X_IP2PROXY_PROVIDER'];
Fraud Score:
$_SERVER['HTTP_X_IP2PROXY_FRAUD_SCORE'];
The PHP application therefore does not need to open the PX12 database itself.
Step 21: Handle unavailable IP2Proxy data #
The application checks whether proxy intelligence exists:
$signalAvailable =
isset($_SERVER['HTTP_X_IP2PROXY_IS_PROXY']);
If IP2Proxy data is missing, this demo requires additional verification rather than automatically considering the request safe:
$requiresVerification =
!$shouldBlock &&
(
!$signalAvailable ||
$isProxy ||
$fraudScore >= 50 ||
$isElevatedNetworkType
);
The result is:

This avoids blindly failing open.
Step 22: Keep PX12 current #
Proxy and VPN infrastructure changes constantly, so the commercial PX12 database should be updated regularly.
Keep the live database at:
/var/lib/ip2proxy/IP2PROXY-PX12.BIN
After replacing it, restore ownership:
sudo chown root:caddy /var/lib/ip2proxy/IP2PROXY-PX12.BIN
Restore permissions:
sudo chmod 640 /var/lib/ip2proxy/IP2PROXY-PX12.BIN
Then restart Caddy:
sudo systemctl restart caddy
Production considerations #
IP2Proxy should be treated as one component of a broader authentication-risk strategy. A VPN connection does not automatically mean that a visitor is malicious, and a normal residential connection does not guarantee that a login is legitimate.
For example, a real authentication system could combine IP2Proxy signals with failed-login velocity, device recognition, account history, impossible travel detection, CAPTCHA, MFA, passkeys, compromised-password detection and behavioral signals.
The difference between these two decisions is important:

versus:

The second approach makes better use of the information available from PX12.
The 50 and 80 Fraud Score thresholds in this tutorial are only examples. Production thresholds should be tuned using your own traffic and risk tolerance.
The debug table should also be removed from a real login page. Fields such as Proxy Type, Provider, Threat and Fraud Score should normally remain server-side.
Conclusion #
The IP2Proxy Caddy Middleware allows proxy intelligence to be added to an HTTP request before the application processes it.
With the commercial PX12 database, the application can consider VPNs, Tor, residential proxies, data center infrastructure, provider information, threat classifications and Fraud Score.
This same architecture can also be applied to account registration, password resets, checkout verification, API authentication, account recovery and administrative portals.
The key idea is to use IP2Proxy information as a risk signal, allowing an application to apply stronger authentication controls when required instead of automatically blocking every user who connects through a privacy or proxy network.
