51

I recently was seeking a way to properly determine protocol, under which url request was supplied to the server.

I watched through parse_url() and though $_SERVER superglobal variable, and found this:

<?php

header('Content-Type: text/plain');

print_r($_SERVER);

Output:

[REQUEST_SCHEME] => http

However, I was unable to find it on php.net or Google. Though, I was able to find this question. Q#1: If $_SERVER['REQUEST_SCHEME'] wasn't documented, then it is probably unreliable, or it can be trusted?

I'am using VC9 PHP 5.4.14 TS under windows for development. But my production is under ubuntu. Q#2: Is this property also availible under ubuntu linux too?

BlitZ
  • 12,038
  • 3
  • 49
  • 68

9 Answers9

60

The REQUEST_SCHEME environment variable is documented on the Apache mod_rewrite page. However, it didn't become available until Apache 2.4.

I only have Apache 2.2 so I created an environment variable. I added the following to the top of my .htaccess file.

RewriteEngine on

# Set REQUEST_SCHEME (standard environment variable in Apache 2.4)
RewriteCond %{HTTPS} off
RewriteRule .* - [E=REQUEST_SCHEME:http]

RewriteCond %{HTTPS} on
RewriteRule .* - [E=REQUEST_SCHEME:https]

Now I can use

  • %{ENV:REQUEST_SCHEME} in other rewrite conditions and rules
  • $_SERVER['REQUEST_SCHEME'] in my PHP code

I don't have to do extra messy conditional checks everywhere, and my PHP code is forward compatible. When Apache is upgraded, I can change my .htaccess file.

I don't know how you'd apply this to a Windows environment. This is probably not a good solution for distributed code, but it works well for my needs.

toxalot
  • 11,260
  • 6
  • 35
  • 58
36

It is hard to prove that it is reliable, but it is easy to prove that it is not reliable (if only I could provide a case which it does not work). And I can prove that it is unreliable because it does not work with IIS 7.0 + PHP 5.3

invisal
  • 11,075
  • 4
  • 33
  • 54
25

As this variable is not available in all web server versions, it is not reliable testing only it. Instead, you can change your PHP code to test two more server environment variables, which can also indicate that https is being used, as below:

if ( (! empty($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https') ||
     (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ||
     (! empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443') ) {
    $server_request_scheme = 'https';
} else {
    $server_request_scheme = 'http';
}

As remarked by toxalot, REQUEST_SCHEME is a native variable of Apache web server since its version 2.4. Apache 2.2 does not have it (see Apache 2.2 server variables) and Microsoft IIs 8.5 does not have it either (see IIS 8.5 Server Variables). Naturally, if a variable is not set by the server, PHP will not include it in its global array $_SERVER.

Fortunately, for compatibility with codes based exclusively on REQUEST_SCHEME checking, you can create this variable in Apache 2.2 editing all your host configuration files (httpd.conf, ssl.conf, 000-default.conf, vhosts.conf), adding the following lines:

# FOR HOSTS LISTENING AT PORT 80
SetEnvIf Request_Protocol ^HTTP/ REQUEST_SCHEME=http

# FOR HOSTS LISTENING AT PORT 443
SetEnvIf Request_Protocol ^HTTP/ REQUEST_SCHEME=https

The code above presume the use of one vhost for every protocol (a best practice in Apache - see this and that).

aldemarcalazans
  • 1,309
  • 13
  • 16
  • That doesn't seem a good idea to assume `$server_request_scheme = 'http'` when the `REQUEST_SCHEME` environment variable is not defined. Moreover, for the `SetEnvIf Request_Protocol`, this doesn't work on vhosts listening on both ports. – xhienne Jul 11 '18 at 13:10
  • 2
    Hi @xhienne. About the PHP code: the idea behind it is not to force the definition of a variable which exists naturally in a web server: instead, the purpose here is to fix a problem which may arise in a server where this environment variable does not exist! In this case, you have a defined request scheme, but the server does not inform it to you "explicitly" (I mean, with precise strings as "http" or "https"). The code, therefore, is merely a translator, when the information that you wants comes in another variable and in a different form. – aldemarcalazans Aug 17 '18 at 17:08
  • Hi aldemarcalazans. Sorry, I guess that, because of its length, I have missed the other conditions in your if statement and only saw a test on REQUEST_SCHEME. Actually, your PHP code looks ok. If you don't mind, you could split that long condition over multiple lines. Thanks. – xhienne Aug 17 '18 at 18:19
  • Done: condition splited. – aldemarcalazans Aug 17 '18 at 18:51
  • using Apache/2.4.25 (Debian) and php_fpm behind a reverse proxy (https happens before reaching apache) I just could not change REQUEST_SCHEME to https - seems like something else is forcing it back to http (I can see it changes the order variables are displayed in `phpinfo()` but value stays http – Antony Gibbs Nov 29 '18 at 01:38
  • Hi @Antony Gibbs. In this case, I guess you should test another variable: $_SERVER['HTTP_X_FORWARDED_PROTO']. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto – aldemarcalazans Feb 28 '19 at 15:53
  • Hi @aldemarcalazans. I would recommend to test for `$_SERVER['HTTPS'] == 'on'` that can be set via `proxy_set_header X-Forwarded-SSL $https;` (nginx) and `SetEnvIf X-Forwarded-SSL on HTTPS=on` (apache2) and never, never use totally unreliable REQUEST_SCHEME – Antony Gibbs Mar 04 '19 at 18:52
  • Does not work, it just set `REDIRECT_REQUEST_SCHEME` to https, but `REQUEST_SCHEME` is still http value. Im using nginx which sets X-HTTPS=on and in apache `SetEnvIf X-HTTPS on REQUEST_SCHEME=https` – mikep Jun 23 '23 at 08:14
11

I, too, couldn't find a reference to REQUEST_SCHEME, but if you're looking to determine whether a request was made by http: or https: then you can use $_SERVER['HTTPS'], which is set to a non-empty value if a request was made by https:. It's documented on the PHP site here

5

In new version Nginx, set by default fastcgi_param REQUEST_SCHEME $scheme.

BlitZ
  • 12,038
  • 3
  • 49
  • 68
S.A.N
  • 83
  • 1
  • 5
4

This value depends on your web-server. If you use nginx (v1.10), in file /etc/nginx/fastcgi_params you can see this following lines :

fastcgi_param  REQUEST_SCHEME     $scheme; 
fastcgi_param  HTTPS              $https if_not_empty;

Generally, this default values are sufficient. But it is possible that it does not work, you can force this values in your vhost :

include fastcgi_params;
fastcgi_param  REQUEST_SCHEME     https; 
fastcgi_param  HTTPS              On;

If you use Apache, you can take a look toxalot's answer

Arno
  • 1,309
  • 14
  • 20
3

Its interesting to see how WordPress resolves this issue with its is_ssl() function which makes use of $_SERVER variable,

function is_ssl() {
    if ( isset( $_SERVER['HTTPS'] ) ) {
        if ( 'on' == strtolower( $_SERVER['HTTPS'] ) ) {
            return true;
        }

        if ( '1' == $_SERVER['HTTPS'] ) {
            return true;
        }
    } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
        return true;
    }
    return false;
}
Aurovrata
  • 2,000
  • 27
  • 45
3

I'm using this and i think it's best way to get the current scheme

    /**
     * Is Secure?
     * Determines if the application is accessed via an encrypted
     * (HTTPS) connection.
     *
     * @return  bool
     */
    public static function isSecure()
    {
        if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') {
            return true;
        } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
            return true;
        } elseif (!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') {
            return true;
        } elseif (isset($_SERVER['SERVER_PORT']) && intval($_SERVER['SERVER_PORT']) === 443) {
            return true;
        }
        return false;
    }

Define this function and check server ssl connection to get current scheme

        $scheme = isSecure() ? 'https' : 'http';
smarteist
  • 1,331
  • 12
  • 15
  • `$_SERVER['SERVER_PROTOCOL']` contains something like `HTTP/1.1` which is unrelated to what you are comparing. – cherouvim Dec 06 '19 at 14:07
  • @cherouvim It returns the web server protocol with its version, we can detect url scheme by this way. What made you think this answer was irrelevant? – smarteist Dec 07 '19 at 10:49
  • The server protocol can be `HTTP/1.0`, `HTTP/1.1`, `HTTP 2.0` etc. How is comparing it with the string `https` relevant? – cherouvim Dec 07 '19 at 16:10
  • @cherouvim yes and could be https ones... my previous answer worked with Apache web servers, but thank you for making me update this post, now i edited answer to a production ready code , its codeigniter implementation for checking secure connections. In the above method all possibilities and web servers are considered and it looks very reliable. hope to be useful – smarteist Dec 07 '19 at 17:18
  • You totally changed the code. You previously tried to deduct the scheme from the protocol version which is not doable. – cherouvim Dec 07 '19 at 17:45
2

Enhancing toxalot's suggestion for CloudFlare users:

RewriteEngine on

RewriteCond %{HTTPS} !on [OR]
RewriteCond %{HTTP:CF-Visitor} '"scheme":"http"'
RewriteRule .* - [E=REQUEST_SCHEME:http]

RewriteCond %{HTTPS} on [OR]
RewriteCond %{HTTP:CF-Visitor} '"scheme":"https"'
RewriteRule .* - [E=REQUEST_SCHEME:https]
  • I should add that that header is JSON and technically they could add space after the colon and break the logic of that configuration, but of course I understand that Apache does not allow to parse JSON and Cloudflare staff are probably aware that people are doing string matches inside the JSON.. More about Cloudflare special headers: https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-CloudFlare-handle-HTTP-Request-headers- – joonas.fi Feb 04 '17 at 16:10