HTTPS访问时$_SERVER不显示SSL启用、SERVER_PORT为80的原因排查
这两个问题几乎都是同一个场景导致的:你的PHP应用部署在了反向代理、负载均衡或者CDN之后。实际的SSL加密/解密(也就是SSL终止)是在前端服务器(比如Nginx、Apache反向代理、Cloudflare这类服务)完成的,而你的PHP后端收到的其实是普通的HTTP请求,所以$_SERVER里的SSL相关标识和端口信息都是后端服务器的,不是用户实际访问的状态。
问题1:HTTPS访问时$_SERVER未显示SSL已启用
原因
当SSL终止在前端代理时,后端PHP服务器接收到的请求是HTTP协议的,所以PHP默认不会设置$_SERVER['HTTPS']为on,也不会生成SSL_PROTOCOL、SSL_CIPHER这类SSL相关的环境变量。
解决办法
配置前端代理传递HTTPS标识头
- 如果用Nginx做反向代理,在你的代理location块里添加这两行:
这样Nginx会把用户实际访问的协议(http/https)通过proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr;X-Forwarded-Proto头传递给后端。 - 如果是Apache反向代理,在SSL虚拟主机的配置里添加:
RequestHeader set X-Forwarded-Proto "https"
- 如果用Nginx做反向代理,在你的代理location块里添加这两行:
让PHP识别并设置SSL变量
在你的PHP应用入口文件(比如index.php)最开头添加这段代码,手动修正$_SERVER变量:if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; // 如果需要其他SSL相关变量,可以根据前端传递的信息补充,比如SSL_PROTOCOL }要是你是用Apache直接处理SSL(没有代理),那要确保
mod_ssl模块已经启用,并且在SSL虚拟主机配置里加上:SetEnvIf HTTPS on HTTPS=on这样就能保证
$_SERVER['HTTPS']被正确设置为on。
问题2:HTTPS访问时SERVER_PORT的值等于80
原因
前端服务器监听443端口处理用户的HTTPS请求,然后把请求转发到后端PHP服务器的80端口(因为后端通常用HTTP协议接收代理请求),所以PHP拿到的$_SERVER['SERVER_PORT']是后端的80,而非用户实际访问的443。
解决办法
配置前端代理传递真实端口
- Nginx的话,在代理配置里添加:
proxy_set_header X-Forwarded-Port $server_port; - Apache的话,在SSL虚拟主机里添加:
RequestHeader set X-Forwarded-Port "443"
- Nginx的话,在代理配置里添加:
在PHP里修正SERVER_PORT
同样在入口文件里添加代码:if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { $_SERVER['SERVER_PORT'] = $_SERVER['HTTP_X_FORWARDED_PORT']; }
额外提示
如果你用的是Laravel、Symfony这类框架,它们大多自带了处理反向代理的机制。比如Laravel可以在app/Http/Middleware/TrustProxies.php里配置信任的代理IP列表,框架会自动识别X-Forwarded-Proto和X-Forwarded-Port这类头,不需要你手动写代码处理。
内容的提问来源于stack exchange,提问作者Skrudox

