在 PHP 中判斷目前請求是否為 HTTPS,看似簡單卻藏著許多陷阱。單純檢查 $_SERVER['HTTPS'] 在反向代理、負載平衡器、CDN 等架構下常失效。本文詳細說明各種環境下的正確判斷方法,並提供一個通用的輔助函數。
| 方法 | 程式碼 | 適用環境 | 缺點 |
|---|---|---|---|
| 標準 HTTPS 變數 | $_SERVER['HTTPS'] === 'on' |
Apache + mod_ssl 直連、Nginx + php-fpm 直連(正確設定 fastcgi_param HTTPS on) | 反向代理/SSL 卸載後失效 |
| 端口號判斷 | $_SERVER['SERVER_PORT'] === '443' |
直連環境備用 | 非標準端口、代理環境失效 |
| HTTP_X_FORWARDED_PROTO | $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' |
AWS ELB、Cloudflare、Nginx 反向代理(已設定 proxy_set_header X-Forwarded-Proto $scheme) | Header 可被偽造,需信任代理層 |
| HTTP_FRONT_END_HTTPS | $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on' |
Microsoft Azure、IIS ARR 代理 | 特定平台專用 |
| HTTP_X_FORWARDED_SSL | $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on' |
部分舊版代理/負載平衡器 | 非標準、較少見 |
<?php
/**
* 判斷目前請求是否為 HTTPS
* 支援直連、單層/多層反向代理、CDN、雲端負載平衡器
*
* @param array $trustedProxies 信任的代理 IP 範圍 (CIDR),為空則信任所有 REMOTE_ADDR(不建議生產環境)
* @return bool
*/
function isHttps(array $trustedProxies = []): bool
{
// 1. 直接連線環境
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
return true;
}
if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === '443') {
return true;
}
// 2. 反向代理環境:檢查 X-Forwarded-Proto(標準)、其他常見 Header
$forwardedProto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
if ($forwardedProto) {
// 可能包含多個值:"https, https, http",取第一個(客戶端原始協定)
$firstProto = trim(explode(',', $forwardedProto)[0]);
if (strtolower($firstProto) === 'https') {
return true;
}
}
// 3. 其他代理 Header
$proxyHeaders = [
'HTTP_FRONT_END_HTTPS' => 'on',
'HTTP_X_FORWARDED_SSL' => 'on',
'HTTP_X_FORWARDED_PROTOCOL' => 'https',
'HTTP_X_URL_SCHEME' => 'https',
];
foreach ($proxyHeaders as $header => $expected) {
if (isset($_SERVER[$header]) && strtolower($_SERVER[$header]) === $expected) {
return true;
}
}
return false;
}
// 使用範例
if (isHttps()) {
echo '安全連線 (HTTPS)';
} else {
echo '非加密連線 (HTTP)';
}
若架構已知(如固定的 Nginx 代理 IP、AWS ELB IP 範圍),應驗證 REMOTE_ADDR 來自信任代理,才信任 X-Forwarded-* Header:
<?php
function isHttpsSecure(array $trustedProxyCidrs = []): bool
{
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
// 若設定白名單,檢查來源 IP 是否在信任範圍內
if ($trustedProxyCidrs) {
$trusted = false;
foreach ($trustedProxyCidrs as $cidr) {
if (ipInCidr($remoteAddr, $cidr)) {
$trusted = true;
break;
}
}
if (!$trusted) {
// 來源非信任代理,僅信任直連 HTTPS 變數
return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on')
|| (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === '443');
}
}
// 信任代理或無白名單設定時,使用標準判斷邏輯
return isHttps();
}
/** 簡易 CIDR 比對(IPv4) */
function ipInCidr(string $ip, string $cidr): bool
{
[$subnet, $mask] = explode('/', $cidr);
$mask = (int)$mask;
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
$maskLong = -1 << (32 - $mask);
return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
}
// 使用範例:信任 10.0.0.0/8 (內網) 和 172.31.0.0/16 (AWS VPC)
$trusted = ['10.0.0.0/8', '172.31.0.0/16'];
var_dump(isHttpsSecure($trusted));
App\Http\Middleware\TrustProxies 中介層,填入信任代理 IP,框架自動處理 Request::secure()。framework.trusted_proxies 與 framework.trusted_headers。Config\App::$proxyIPs 設定信任代理。isHttpsSecure() 並定義為常數 define('IS_HTTPS', isHttpsSecure([...]));。if/else 區塊,請移除重複。$_SERVER Key 為大寫且加前綴 HTTP_(如 HTTP_X_FORWARDED_PROTO),勿直接用 X-Forwarded-Proto。proxy_set_header X-Forwarded-Proto $scheme; 已加入 location 區塊。CF-Visitor Header。判斷 HTTPS 的關鍵在於理解流量進入路徑:直連看 HTTPS/SERVER_PORT,經代理看 X-Forwarded-Proto。生產環境務必配合信任代理 IP 白名單,防止 Header 偽造導致安全判斷繞過。將判斷邏輯封裝為可複用函數並整合至框架請求生命週期,才能確保全站一致性與安全性。