在現代 Web 架構中,用戶請求常經過 CDN (Cloudflare、Akamai)、反向代理 (Nginx、HAProxy)、負載均衡器 (ALB、SLB) 才抵達 PHP 應用伺服器。$_SERVER['REMOTE_ADDR'] 僅能見到直接上游的 IP,無法識別真實客戶端。本文提供穿透多層代理獲取真實 IP 的完整、安全方案。
Client (真實 IP: 1.2.3.4) │ ▼ CDN / WAF (加上 X-Forwarded-For: 1.2.3.4) │ ▼ 負載均衡器 (追加 X-Forwarded-For: 1.2.3.4, 5.6.7.8) │ ▼ Nginx 反向代理 (設定 proxy_set_header X-Real-IP $remote_addr;) │ ▼ PHP-FPM (看到 REMOTE_ADDR = Nginx IP)
| 標頭 | 來源 | 格式 | 可信度 |
|---|---|---|---|
X-Forwarded-For (XFF) | 代理鏈自動追加 | client, proxy1, proxy2 (左到右:客戶端 → 最後代理) | 需驗證信任代理清單 |
X-Real-IP | Nginx/HAProxy 設定 | 單一 IP | 較高 (由直連代理設定) |
CF-Connecting-IP | Cloudflare | 單一真實 IP | 極高 (Cloudflare 保證) |
True-Client-IP | Akamai / Cloudflare Enterprise | 單一真實 IP | 極高 |
X-Client-IP | 部分代理 | 單一 IP | 中 |
<?php
/**
* 獲取客戶端真實 IP
*
* @param array $trusted_proxies 信任的代理 IP/CIDR 陣列 (如 ['10.0.0.0/8', '192.168.1.10'])
* @return string IPv4/IPv6 字串
*/
function get_client_ip(array $trusted_proxies = []): string {
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_TRUE_CLIENT_IP', // Akamai/Cloudflare Ent
'HTTP_X_CLIENT_IP', // 部分代理
'HTTP_X_REAL_IP', // Nginx proxy_set_header
'HTTP_X_FORWARDED_FOR', // 標準代理鏈
'REMOTE_ADDR', // 最後兜底
];
foreach ($headers as $header) {
if (empty($_SERVER[$header])) continue;
// X-Forwarded-For 可能包含多個 IP,需解析
$ips = array_map('trim', explode(',', $_SERVER[$header]));
// 從右向左找第一個「不在信任清單」的 IP
for ($i = count($ips) - 1; $i >= 0; $i--) {
$ip = $ips[$i];
if (!filter_var($ip, FILTER_VALIDATE_IP)) continue;
if (is_trusted_proxy($ip, $trusted_proxies)) {
continue; // 此 IP 為信任代理,繼續往左找
}
return $ip; // 找到第一個非信任 IP = 真實客戶端
}
}
// 兜底:直接返回 REMOTE_ADDR
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 判斷 IP 是否在信任代理清單中 (支援 CIDR)
*/
function is_trusted_proxy(string $ip, array $trusted_proxies): bool {
foreach ($trusted_proxies as $proxy) {
if (strpos($proxy, '/') !== false) {
// CIDR 表示法
[$range_ip, $mask] = explode('/', $proxy);
if (ip_in_cidr($ip, $range_ip, (int)$mask)) return true;
} elseif ($ip === $proxy) {
return true;
}
}
return false;
}
/**
* CIDR 範圍檢查
*/
function ip_in_cidr(string $ip, string $range_ip, int $mask): bool {
$ip_long = ip2long($ip);
$range_long = ip2long($range_ip);
if ($ip_long === false || $range_long === false) return false; // IPv6 需用 inet_pton
$wildcard = ~((1 << (32 - $mask)) - 1);
return (($ip_long & $wildcard) === ($range_long & $wildcard));
}
// === 使用範例 ===
// 信任內網 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 及特定 LB IP
$trusted = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '100.64.0.0/10'];
$real_ip = get_client_ip($trusted);
echo "真實 IP: $real_ip";
?>
# 只在「直接上游是信任代理」時設定 set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; real_ip_header X-Forwarded-For; real_ip_recursive on; # 遞迴向左找第一個非信任 IP # 傳遞給 PHP-FPM fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for; fastcgi_param HTTP_X_REAL_IP $remote_addr;
LoadModule remoteip_module modules/mod_remoteip.so RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 10.0.0.0/8 172.16.0.0/12 RemoteIPInternalProxy 192.168.0.0/16
若僅使用 Cloudflare,可直接信任 CF-Connecting-IP:
<?php
function get_cf_ip(): string {
return $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
?>
但仍建議在 Nginx 層用 set_real_ip_from 限制 Cloudflare IP 範圍,防止偽造標頭。
<?php
function ip_in_cidr_v6(string $ip, string $range_ip, int $mask): bool {
$ip_bin = inet_pton($ip);
$range_bin = inet_pton($range_ip);
if ($ip_bin === false || $range_bin === false) return false;
$mask_bin = str_repeat("\xFF", $mask >> 3);
if ($mask % 8) {
$mask_bin .= chr(0xFF << (8 - ($mask % 8)));
}
$mask_bin = str_pad($mask_bin, 16, "\x00");
return (($ip_bin & $mask_bin) === ($range_bin & $mask_bin));
}
// 綜合版:自動偵測 IPv4/IPv6
function is_trusted_proxy_v6(string $ip, array $trusted_proxies): bool {
foreach ($trusted_proxies as $proxy) {
if (strpos($proxy, '/') !== false) {
[$range, $mask] = explode('/', $proxy);
$mask = (int)$mask;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
if (ip_in_cidr($ip, $range, $mask)) return true;
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
if (ip_in_cidr_v6($ip, $range, $mask)) return true;
}
} elseif ($ip === $proxy) {
return true;
}
}
return false;
}
?>
X-Forwarded-For 第一個 IP:攻擊者可偽造 X-Forwarded-For: 1.2.3.4,必須結合信任代理清單從右向左驗證。real_ip/mod_remoteip 清洗。https://www.cloudflare.com/ips/)。
4. 忽略 IPv6:現代雲環境大量使用 IPv6,CIDR 檢查必須支援 inet_pton。
5. 將 REMOTE_ADDR 當真實 IP:在容器化、K8s、Serverless 架構下,REMOTE_ADDR 通常是 Pod IP 或內部 Gateway IP。
獲取真實 IP 的核心公式:信任代理清單 + 從右向左掃描 X-Forwarded-For + 標準化標頭 (CF-Connecting-IP, X-Real-IP) + Web 伺服器層清洗。將上述 get_client_ip() 封裝為專案共用工具類,並在部署時注入當前環境的信任 CIDR 清單,即可在任何代理架構下穩定獲取用戶真實 IP。