Is there a way to check if the current page was opened with SSL? For example, I want my login page (login.php) to check if it was accessed using SSL (https://mywebserver.com/login.php). If not, redirect them to the SSL version of the page.
Pretty much, I want to enfore that the user uses the page securely.
You should be able to check that $_SERVER['HTTPS'] is set, e.g.:
if (empty($_SERVER['HTTPS'])) {
header('Location: https://mywebserver.com/login.php');
exit;
}
Be careful. On my IIS server, $_SERVER['HTTPS'] is not empty but has the value 'off'.
So i had to do
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') {
// no SSL request
}
You'll find this may not work if you are working over forwarded protocols. For example, Amazon's ELB can handle SSL negotiation and interact with your app servers over port 80.
This block handles that:
public function isSSL()
{
if( !empty( $_SERVER['https'] ) )
return true;
if( !empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' )
return true;
return false;
}
Well, Here is another chunk of code. The code will return full url with https/http.
<?php
/**
* Check whether URL is HTTPS/HTTP
* #return boolean [description]
*/
function isSecure()
{
if (
( ! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
|| ( ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
|| (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)
|| (isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT'] == 443)
|| (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https')
) {
return true;
} else {
return false;
}
}
/**
* Example Use
*/
define('APP_URL', (isSecure() ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']));
echo APP_URL;
/**
* +++++++++++++++++++++++++
* OR - One line Code
* +++++++++++++++++++++++++
*/
define('APP_URL', ((( ! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') || ( ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) || (isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT'] == 443) || (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https') ) ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']));
echo APP_URL;
?>
<?php
if ( !empty( $_SERVER['HTTPS'] ) ) {
//do secure stuff
}else{
//warn or redirect or whatever
}
?>
http://php.net/manual/en/reserved.variables.server.php
Another method is to check for the existence of HTTPS cookies. First your server needs to send the browser a cookie with the secure flag:
Set-Cookie:some_key=some_value;secure
After your server has sent the browser the cookie, whenever the browser requests a page from your server, it will send along the secure cookie some_key=some_value only if it is requesting a HTTPS page. This means that if you see the existence of the cookie some_key=some_value you know that the browser is requesting a HTTPS page. Voila!
Browser support is very good, as this is fundamental to security. Browsers without support for HTTPS cookies are Firesheepable when users request pages from non-HSTSed domains.
For more info, see:
https://httpsnow.org/help/securecookies
https://www.owasp.org/index.php/SecureFlag#Overview
https://stackoverflow.com/a/13730187/632951
https://security.stackexchange.com/q/100/2379
Just to add that in case of nginx, the way to check for https is:
if (isset($_SERVER['SERVER_PORT']) &&
($_SERVER['SERVER_PORT'] === '443')) {
return 'https';
}
To use PHP to check if the page was accessed without SSL you can check the port number.
// Most encrypted web sites use port 443
if ($_SERVER['SERVER_PORT']==443) {
// Tell browser to always use HTTPS
header('strict-transport-security: max-age=126230400');
}
elseif (isset($_SERVER['SERVER_PORT'])) {
// Redirect current page to https with 301 Moved Permanently response
header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], true, 301);
exit;
}
This assumes your server is configured with the SERVER_PORT environment variable and that the encrypted version of your web site is hosted on port 443. It also assumes your server is not behind a load balancer. If your server is behind a load balancer, you might need a more advanced solution such as this one that does not rely on custom HTTP headers which can vary from one load balancer to the next:
// Set secure cookie to detect HTTPS as cookie will not exist otherwise.
header('set-cookie: __Secure-https=1; expires='.substr(gmdate('r', ($_SERVER['REQUEST_TIME']?: time())+126230400), 0, -5).'GMT; path=/; secure', false);
// Tell browser to always use HTTPS
header('strict-transport-security: max-age=126230400');
if (!isset($_COOKIE['__Secure-https']) && !isset($_GET['https'])) {
// Redirect to secure version of site and add https=1 GET variable in case cookies are blocked
header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].(strpos($_SERVER['REQUEST_URI'], '?')===false? '?': '&').'https=1', true, 307);
exit;
}
If the above solution is problematic because it adds ?https=1 to your URL then you can always use JavaScript. Add this to the top of your page right after <head>:
<script>
// This will redirect all requests from http to https
if (location.protocol=='http:') {
location.replace('https://'+location.host+location.pathname+location.search)
document.write('<noscript>');// hack to stop page from displaying
}
</script>
Then add the following to your PHP script if you want browsers to remember to always use HTTPS when accessing your site:
header('strict-transport-security: max-age=126230400');
or if you want browsers to have your preferences preloaded use:
header('strict-transport-security: max-age=126230400; preload');// HTTPS will always be used!
If you use the preload feature you will need to submit your web site to be included in Chrome's HSTS preload list so that browsers come preloaded with your web site preferences. If you use preload, it's also advisable to host your site on the naked domain without the www. This is because it's usually easier for most people to type in your domain without the www, and with preload your web site loads without the need of a tedious redirect since https is already the default.
Detect server side SLL with some additions:
function detectSSL(): ?bool {
// check HTTPS protocol
if( isset($_SERVER['HTTPS']) ) {
if( 'off' !== strtolower($_SERVER['HTTPS']) ) {
return true;
}
if( 1 === (int)$_SERVER['HTTPS'] ) {
return true;
}
}
if( isset($_SERVER['HTTP_X_FORWARDED_SSL']) ) {
if( 'on' === $_SERVER['HTTP_X_FORWARDED_SSL'] ) {
return true;
}
}
if( isset($_SERVER['HTTP_X_FORWARDED_PORT']) ) {
if( 443 === (int)$_SERVER['HTTP_X_FORWARDED_PORT'] ) {
return true;
}
}
if( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) ) {
if( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' ) {
return true;
}
}
if( isset($_SERVER['REQUEST_SCHEME']) ) {
if( strtolower($_SERVER['REQUEST_SCHEME'] === 'https') ) {
return true;
}
}
// check server port
if( isset($_SERVER['SERVER_PORT']) ) {
if( 443 === (int)$_SERVER['SERVER_PORT'] ) {
return true;
}
}
// non-SSL
return null;
}
// Set URI prefix
define('uri_prefix', detectSSL() ? 'https://' : 'http://');
define('site_host', strtolower(uri_prefix . $_SERVER['HTTP_HOST']));
And addition for .htaccess:
# SSL schema off
RewriteCond %{HTTPS} off
RewriteRule .* - [E=REQUEST_SCHEME:http]
# SSL schema
RewriteCond %{HTTPS} on
RewriteRule .* - [E=REQUEST_SCHEME:https]
I've seen many tutorials online that says you need to check $_SERVER['HTTPS'] if the server is connection is secured with HTTPS. My problem is that on some of the servers I use, $_SERVER['HTTPS'] is an undefined variable that results in an error. Is there another variable I can check that should always be defined?
Just to be clear, I am currently using this code to resolve if it is an HTTPS connection:
if(isset($_SERVER['HTTPS'])) {
if ($_SERVER['HTTPS'] == "on") {
$secure_connection = true;
}
}
This should always work even when $_SERVER['HTTPS'] is undefined:
function isSecure() {
return
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| $_SERVER['SERVER_PORT'] == 443;
}
The code is compatible with IIS.
From the PHP.net documentation and user comments :
Set to a non-empty value if the script was queried through the HTTPS protocol.
Note that when using ISAPI with IIS, the value will be "off" if the request was not made through the HTTPS protocol. (Same behaviour has been reported for IIS7 running PHP as a Fast-CGI application).
Also, Apache 1.x servers (and broken installations) might not have $_SERVER['HTTPS'] defined even if connecting securely. Although not guaranteed, connections on port 443 are, by convention, likely using secure sockets, hence the additional port check.
Additional note: if there is a load balancer between the client and your server, this code doesn't test the connection between the client and the load balancer, but the connection between the load balancer and your server. To test the former connection, you would have to test using the HTTP_X_FORWARDED_PROTO header, but it's much more complex to do; see latest comments below this answer.
My solution (because the standard conditions [$_SERVER['HTTPS'] == 'on'] do not work on servers behind a load balancer) is:
$isSecure = false;
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$isSecure = true;
}
elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
$isSecure = true;
}
$REQUEST_PROTOCOL = $isSecure ? 'https' : 'http';
HTTP_X_FORWARDED_PROTO: a de facto standard for identifying the originating protocol of an HTTP request, since a reverse proxy (load balancer) may communicate with a web server using HTTP even if the request to the reverse proxy is HTTPS
http://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Common_non-standard_request_headers
Chacha, per the PHP documentation: "Set to a non-empty value if the script was queried through the HTTPS protocol." So your if statement there will return false in many cases where HTTPS is indeed on. You'll want to verify that $_SERVER['HTTPS'] exists and is non-empty. In cases where HTTPS is not set correctly for a given server, you can try checking if $_SERVER['SERVER_PORT'] == 443.
But note that some servers will also set $_SERVER['HTTPS'] to a non-empty value, so be sure to check this variable also.
Reference: Documentation for $_SERVER and $HTTP_SERVER_VARS [deprecated]
This also works when $_SERVER['HTTPS'] is undefined
if( (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443 ){
//enable secure connection
}
Making my own function from reading all previous posts:
public static function isHttps()
{
if (array_key_exists("HTTPS", $_SERVER) && 'on' === $_SERVER["HTTPS"]) {
return true;
}
if (array_key_exists("SERVER_PORT", $_SERVER) && 443 === (int)$_SERVER["SERVER_PORT"]) {
return true;
}
if (array_key_exists("HTTP_X_FORWARDED_SSL", $_SERVER) && 'on' === $_SERVER["HTTP_X_FORWARDED_SSL"]) {
return true;
}
if (array_key_exists("HTTP_X_FORWARDED_PROTO", $_SERVER) && 'https' === $_SERVER["HTTP_X_FORWARDED_PROTO"]) {
return true;
}
return false;
}
I have just had an issue where I was running the server using Apache mod_ssl, yet a phpinfo() and a var_dump( $_SERVER ) showed that PHP still thinks I'm on port 80.
Here is my workaround for anyone with the same issue....
<VirtualHost *:443>
SetEnv HTTPS on
DocumentRoot /var/www/vhost/scratch/content
ServerName scratch.example.com
</VirtualHost>
The line worth noting is the SetEnv line. With this in place and after a restart, you should have the HTTPS environment variable you always dreamt of
If your are using Apache you may always count on
$_SERVER["REQUEST_SCHEME"]
to verify the scheme of the URL requested. But, as mentioned in other answers, it is prudent to verify other parameters before assuming SSL is really being used.
The REAL answer: ready for copy-paste into a [config] script
/* configuration settings; X=edit may 10th '11 */
$pv_sslport=443; /* for it might be different, as also Gabriel Sosa stated */
$pv_serverport=80; /* X */
$pv_servername="mysite.com"; /* X */
/* X appended after correction by Michael Kopinsky */
if(!isset($_SERVER["SERVER_NAME"]) || !$_SERVER["SERVER_NAME"]) {
if(!isset($_ENV["SERVER_NAME"])) {
getenv("SERVER_NAME");
// Set to env server_name
$_SERVER["SERVER_NAME"]=$_ENV["SERVER_NAME"];
}
}
if(!$_SERVER["SERVER_NAME"]) (
/* X server name still empty? ... you might set $_SERVER["SERVER_NAME"]=$pv_servername; */
}
if(!isset($_SERVER["SERVER_PORT"]) || !$_SERVER["SERVER_PORT"]) {
if(!isset($_ENV["SERVER_PORT"])) {
getenv("SERVER_PORT");
$_SERVER["SERVER_PORT"]=$_ENV["SERVER_PORT"];
}
}
if(!$_SERVER["SERVER_PORT"]) (
/* X server port still empty? ... you might set $_SERVER["SERVER_PORT"]=$pv_serverport; */
}
$pv_URIprotocol = isset($_SERVER["HTTPS"]) ? (($_SERVER["HTTPS"]==="on" || $_SERVER["HTTPS"]===1 || $_SERVER["SERVER_PORT"]===$pv_sslport) ? "https://" : "http://") : (($_SERVER["SERVER_PORT"]===$pv_sslport) ? "https://" : "http://");
$pv_URIprotocol is now correct and ready to be used; example $site=$pv_URIprotocol.$_SERVER["SERVER_NAME"]. Naturally, the string could be replaced with TRUE and FALSE also. PV stands for PortalPress Variable as it is a direct copy-paste which will always work. This piece can be used in a production script.
I know this answer is late, but I combined a bunch of answers and made a simple function that works for all use cases.
Try this:
function is_ssl(){
if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']=="https"){ return true; }
elseif(isset($_SERVER['HTTPS'])){ return true; }
elseif($_SERVER['SERVER_PORT'] == 443){ return true; }
else{ return false; }
}
Then just use if, for example:
if(is_ssl()){
// WHAT TO DO IF IT IS SSL / HTTPS
}else{
// WHAT TO DO IF IT IS NOT SSL / HTTPS
}
This code works with Cloudflare, shared hosting providers, etc.
Enjoy.
I don't think that adding a port is good idea - specially when you got many servers with different builds. that just adds one more thing to remember to change. looking at doc's I think the last line of kaisers is quite good, so that:
if(!empty($_SERVER["HTTPS"]))
if($_SERVER["HTTPS"]!=="off")
return 1; //https
else
return 0; //http
else
return 0; //http
seems like perfectly enough.
The only reliable method is the one described by Igor M.
$pv_URIprotocol = isset($_SERVER["HTTPS"]) ? (($_SERVER["HTTPS"]==="on" || $_SERVER["HTTPS"]===1 || $_SERVER["SERVER_PORT"]===$pv_sslport) ? "https://" : "http://") : (($_SERVER["SERVER_PORT"]===$pv_sslport) ? "https://" : "http://");
Consider following:
You are using nginx with fastcgi, by default(debian, ubuntu) fastgi_params contain directive:
fastcgi_param HTTPS $https;
if you are NOT using SSL, it gets translated as empty value, not 'off', not 0
and you are doomed.
http://unpec.blogspot.cz/2013/01/nette-nginx-php-fpm-redirect.html
I find these params acceptable as well and more then likely don't have false positives when switching web servers.
$_SERVER['HTTPS_KEYSIZE']
$_SERVER['HTTPS_SECRETKEYSIZE']
$_SERVER['HTTPS_SERVER_ISSUER']
$_SERVER['HTTPS_SERVER_SUBJECT']
if($_SERVER['HTTPS_KEYSIZE'] != NULL){/*do foobar*/}
Shortest way I am using:
$secure_connection = !empty($_SERVER['HTTPS']);
If if https is used, then $secure_connection is true.
You could check $_SERVER['SERVER_PORT'] as SSL normally runs on port 443, but this is not foolproof.
What do you think of this?
if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
$scheme = 'https';
else
$scheme = 'http';
On my server (Ubuntu 14.10, Apache 2.4, php 5.5) variable $_SERVER['HTTPS'] is not set when php script is loaded via https. I don't know what is wrong. But following lines in .htaccess file fix this problem:
RewriteEngine on
RewriteCond %{HTTPS} =on [NC]
RewriteRule .* - [E=HTTPS:on,NE]
Here is a re-usable function that I have been using for a while. HTH.
Note: The value of HTTPS_PORT (which is a custom constant in my code) may vary on your envrionment, for example it may be 443 or 81.
/**
* Determine if this is a secure HTTPS connection
*
* #return bool True if it is a secure HTTPS connection, otherwise false.
*/
function isSSL()
{
if (isset($_SERVER['HTTPS'])) {
if ($_SERVER['HTTPS'] == 1) {
return true;
} elseif ($_SERVER['HTTPS'] == 'on') {
return true;
}
} elseif ($_SERVER['SERVER_PORT'] == HTTPS_PORT) {
return true;
}
return false;
}
just for interest, chrome canary at the moment sends
HTTPS : 1
to the server, and depending on how the server is configured can mean that you get back the following
HTTPS : 1, on
This broke our application because we were testing if on, which it obviously isn't.
At the moment, only chrome canary seems to do this, but its worth noting that things from canary generally land in "normal" chrome a short while later.
If You use nginx as loadbalancing system check $_SERVER['HTTP_HTTPS'] == 1 other checks will be fail for ssl.
$secure_connection = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || (!empty($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] != 'off') || $_SERVER['REQUEST_SCHEME'] == 'https' || $_SERVER['SERVER_PORT'] == 443) ? true : false;
Code is checking anything possible and works also on IIS web server. Chrome since v44 do not set header HTTP: 1 so checking HTTP_HTTPS is OK. If this code does not match https it means your webserver or proxy server is poorly configured. Apache itself sets HTTPS flag correctly but there can be problem when you use proxy (e.g. nginx). You must set some header in nginx https virtual host
proxy_set_header X-HTTPS 1;
and use some Apache module to set HTTPS flag correctly by looking for X-HTTPS from proxy. Search for mod_fakessl, mod_rpaf, etc.
I have occasion to go a step further and determine if the site I'm connecting to is SSL capable (one project asks the user for their URL and we need to verify they have installed our API pack on a http or https site).
Here's the function I use - basically, just call the URL via cURL to see if https works!
function hasSSL($url)
{
// take the URL down to the domain name
$domain = parse_url($url, PHP_URL_HOST);
$ch = curl_init('https://' . $domain);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD'); //its a HEAD
curl_setopt($ch, CURLOPT_NOBODY, true); // no body
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // in case of redirects
curl_setopt($ch, CURLOPT_VERBOSE, 0); //turn on if debugging
curl_setopt($ch, CURLOPT_HEADER, 1); //head only wanted
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // we dont want to wait forever
curl_exec($ch);
$header = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($header === 200) {
return true;
}
return false;
}
This is the most reliable way I have found to not only find out IF you are using https (as the question asks), but if you COULD (or even SHOULD) be using https.
NOTE: it is possible (though not really likely...) that a site could have different http and https pages (so if you are told to use http, maybe you don't need to change..) The vast majority of sites are the same, and probably should reroute you themselves, but this additional check has its use (certainly as I said, in the project where the user inputs their site info and you want to make sure from the server side)
If you are using Incapsula's load balancer you'll need to use an IRule to generate a custom header for your server. I created an HTTP_X_FORWARDED_PROTO header that is equal to either "http" if the port is set to 80 and "https" if it is equal to 443.
I would add a global filter to ensure everything I am checking is correct;
function isSSL() {
$https = filter_input(INPUT_SERVER, 'HTTPS');
$port = filter_input(INPUT_SERVER, 'SERVER_PORT');
if ($https) {
if ($https == 1) {
return true;
} elseif ($https == 'on') {
return true;
}
} elseif ($port == '443') {
return true;
}
return false;
}
This is how i find solve this
$https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0 ||
!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
return ($https) ? 'https://' : 'http://';
I used the main suggestion here and got annoyed at the "PHP Notice" in the logs when HTTPS was not set. You can avoid it by using the null-coalescing operator "??":
if( ($_SERVER['HTTPS'] ?? 'off') == 'off' ) {
// redirect
}
(Note: not available prior to php v7)
If you don't have control of the web server & don't know which variables have been set, upload this php to find out:
<?php
echo "<br>1 ".$_SERVER["HTTPS"];
echo "<br>2 ".$_SERVER["SERVER_PORT"];
echo "<br>3 ".$_SERVER["HTTP_X_FORWARDED_PROTO"];
echo "<br>4 ".$_SERVER["HTTP_X_FORWARDED_SSL"];
echo "<br>5 ".$_SERVER["HTTP_HTTPS"];
echo "<br>6 ".$_SERVER["REQUEST_SCHEME"];
?>
<html>
<body>
<br>
Just cruising
</body>
</html>
I use cloudflare for my systems. I had to access the $_SERVER['HTTP_CF_VISITOR'] value.
$isSsl = false;
if (isset($_SERVER['HTTP_CF_VISITOR'])) {
$cfDecode = json_decode($_SERVER['HTTP_CF_VISITOR']);
if (!empty($cfDecode) && !empty($cfDecode->scheme) && $cfDecode->scheme == 'https') {
$isSsl = true;
}
}
var_dump($isSsl);
As per hobodave's post: "Set to a non-empty value if the script was queried through the HTTPS protocol."
if (!empty($_SERVER['HTTPS']))
{
$secure_connection = true;
}