142

I've got just one page that I want to force to be accessed as an HTTPS page (PHP on Apache). How do I do this without making the whole directory require HTTPS? Or, if you submit a form to an HTTPS page from an HTTP page, does it send it by HTTPS instead of HTTP?

Here is my example:

http://www.example.com/some-page.php

I want it to only be accessed through:

https://www.example.com/some-page.php

Sure, I can put all of the links to this page pointed at the HTTPS version, but that doesn't stop some fool from accessing it through HTTP on purpose...

One thing I thought was putting a redirect in the header of the PHP file to check to be sure that they are accessing the HTTPS version:

if($_SERVER["SCRIPT_URI"] == "http://www.example.com/some-page.php"){
  header('Location: https://www.example.com/some-page.php');
}

But that can't be the right way, can it?

Will Chen
  • 482
  • 4
  • 12

22 Answers22

184

The way I've done it before is basically like what you wrote, but doesn't have any hardcoded values:

if($_SERVER["HTTPS"] != "on")
{
    header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);
    exit();
}
Xeoncross
  • 55,620
  • 80
  • 262
  • 364
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • 16
    You forgot to call exit() to ensure the script quits after the redirect. I usually wrap that in a function called requireSSL(). I can this call this at the top of any page I want to be encrypted. – Jesse Weigert Jan 13 '09 at 11:20
  • 31
    Change the if to be `(empty($_SERVER["HTTPS"]) || $_SERVER["HTTPS"] !== "on")` to fix PHP notices. – dave1010 Jan 30 '13 at 14:57
  • Aren't `$_SERVER[]` variables changeable/vulnerable to users intervention? – Arian Faurtosh May 07 '14 at 06:48
  • @ArianFaurtosh source? – John Mar 08 '16 at 07:47
  • 3
    @ArianFaurtosh some are extracted from client headers, like `HTTP_X_FORWARDED`, and can be manipulated, but others like `HTTPS` or `SERVER_PORT` are set directly from the web server and should *usually* be safe. – Mahn Jun 23 '16 at 03:36
  • I combined this with temuraru's answer from http://stackoverflow.com/a/16076965/1040123 to check for https on servers behind a load balancer. – odaa Apr 04 '17 at 18:32
46

You could do it with a directive and mod_rewrite on Apache:

<Location /buyCrap.php>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</Location>

You could make the Location smarter over time using regular expressions if you want.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
thebigjc
  • 569
  • 3
  • 8
  • 6
    Where would you put this? .htaccess file? –  Sep 17 '08 at 18:16
  • 1
    Doesn't REQUEST_URI not include the "query string" (like ?page=1&id=41 etc.)? That's what the apache documentation says... So if I try to access http://site.com/index.php?page=1&id=12 I will be redirected https://site.com/index.php – Rolf Jul 08 '13 at 13:00
  • 2
    From the apache Documentation: REQUEST_URI The path component of the requested URI, such as "/index.html". This notably excludes the query string which is available as as its own variable named QUERY_STRING. So you'd need to add QUERY_STRING after REQUEST_URI – Rolf Jul 08 '13 at 13:03
  • 2
    You also need to add the [R] flage after that to make it redirect – Rolf Jul 08 '13 at 13:17
44

You should force the client to request HTTPS always with HTTP Strict Transport Security (HSTS) headers:

// Use HTTP Strict Transport Security to force client to use secure connections only
$use_sts = true;

// iis sets HTTPS to 'off' for non-SSL requests
if ($use_sts && isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
    header('Strict-Transport-Security: max-age=31536000');
} elseif ($use_sts) {
    header('Location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], true, 301);
    // we are in cleartext at the moment, prevent further execution and output
    die();
}

Please note that HSTS is supported in most modern browsers, but not universal. Thus the logic above manually redirects the user regardless of support if they end up on HTTP, and then sets the HSTS header so that further client requests should be redirected by the browser if possible.

Jacob Swartwood
  • 4,075
  • 1
  • 16
  • 17
  • I am surprised non of the other answers include this header, it is quite important... any pitfall for combining the two of them ? – keisar Apr 18 '13 at 21:23
  • No... you could definitely set that header anyway if you want HTTPS always. It is just sort of redundant to set it before and after you do the redirect. I've also amended my response above to more accurately explain compatibility. – Jacob Swartwood Apr 25 '13 at 23:24
  • 1
    (Redacting original comment) I didn't notice the specific requirement of "just one page". HSTS will apply to all pages; my answer is technically incorrect. – Jacob Swartwood Apr 25 '13 at 23:32
  • 4
    FYI, the standard RFC 6797 section 7.2 says "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport." so no need to send it when the request is regular http, it should be ignored if the browser follows the standard. – Frank Forte Nov 24 '13 at 04:56
  • From a security standpoint, I think this is the best answer. However, I would not do any redirects. Simply turn on HSTS or block the page from loading. – Alex W Nov 30 '15 at 15:56
  • @AlexW Thanks for your input. Why not redirect? – Mark Feb 09 '16 at 19:29
  • This is good, but you need to add it to the preload, otherwise the initial request would still be through http. – M H Jan 16 '17 at 20:48
  • 1
    @mark if there is a MITM and your website doesn't have this header, you can allow a comprised session to go to http and people input their details and they probably won't notice and a redirect won't help (the man in the middle will connect to the site via https and present it as http). Using this header will force HTTPS on the client side. This is particularly important due to the recent WPA2 WiFi security vulnerability. – Rudiger Oct 17 '17 at 05:28
19

I just created a .htaccess file and added :

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

Simple !

MatHatrik
  • 762
  • 1
  • 6
  • 16
10

The PHP way:

$is_https=false;
if (isset($_SERVER['HTTPS'])) $is_https=$_SERVER['HTTPS'];
if ($is_https !== "on")
{
    header("Location: https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
    exit(1);
}

The Apache mod_rewrite way:

RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Jay
  • 353
  • 4
  • 8
  • I prefer the PHP way because there are instance where I wish to override the requirement for SSL. For example, local access to APIs and things like Mozilla Thunderbird's autoconfigure, etc. – Jay Oct 26 '18 at 22:51
  • The PHP method works perfectly for me, highly recommended. – Moxet Jan Jan 31 '20 at 10:57
9
// Force HTTPS for security
if($_SERVER["HTTPS"] != "on") {
    $pageURL = "Location: https://";
    if ($_SERVER["SERVER_PORT"] != "80") {
        $pageURL .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
    } else {
        $pageURL .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
    }
    header($pageURL);
}
Manoj Sharma
  • 1,467
  • 2
  • 13
  • 20
Jeff
  • 1
  • 1
  • 1
8

Had to do something like this when running behind a load balancer. Hat tip https://stackoverflow.com/a/16076965/766172

function isSecure() {
    return (
        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
     || $_SERVER['SERVER_PORT'] == 443
     || (
            (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
         || (!empty($_SERVER['HTTP_X_FORWARDED_SSL'])   && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
        )
    );
}

function requireHTTPS() {
    if (!isSecure()) {
        header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], TRUE, 301);
        exit;
    }
}
Community
  • 1
  • 1
syvex
  • 7,518
  • 9
  • 43
  • 47
5

http://www.besthostratings.com/articles/force-ssl-htaccess.html

Sometimes you may need to make sure that the user is browsing your site over securte connection. An easy to way to always redirect the user to secure connection (https://) can be accomplished with a .htaccess file containing the following lines:

RewriteEngine On 
RewriteCond %{SERVER_PORT} 80 
RewriteRule ^(.*)$ https://www.example.com/$1 [R,L]

Please, note that the .htaccess should be located in the web site main folder.

In case you wish to force HTTPS for a particular folder you can use:

RewriteEngine On 
RewriteCond %{SERVER_PORT} 80 
RewriteCond %{REQUEST_URI} somefolder 
RewriteRule ^(.*)$ https://www.domain.com/somefolder/$1 [R,L]

The .htaccess file should be placed in the folder where you need to force HTTPS.

itsazzad
  • 6,868
  • 7
  • 69
  • 89
5

Ok.. Now there is tons of stuff on this now but no one really completes the "Secure" question. For me it is rediculous to use something that is insecure.

Unless you use it as bait.

$_SERVER propagation can be changed at the will of someone who knows how.

Also as Sazzad Tushar Khan and the thebigjc stated you can also use httaccess to do this and there are a lot of answers here containing it.

Just add:

RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://example.com/$1 [R,L]

to the end of what you have in your .httaccess and thats that.

Still we are not as secure as we possibly can be with these 2 tools.

The rest is simple. If there are missing attributes ie...

if(empty($_SERVER["HTTPS"])){ // SOMETHING IS FISHY
}

if(strstr($_SERVER['HTTP_HOST'],"mywebsite.com") === FALSE){// Something is FISHY
}


Also say you have updated your httaccess file and you check:

if($_SERVER["HTTPS"] !== "on"){// Something is fishy
}

There are a lot more variables you can check ie..

HOST_URI (If there are static atributes about it to check)

HTTP_USER_AGENT (Same session different values)

So all Im saying is dont just settle for one or the other when the answer lies in a combination.

For more httaccess rewriting info see the docs-> http://httpd.apache.org/docs/2.0/misc/rewriteguide.html

Some Stacks here -> Force SSL/https using .htaccess and mod_rewrite
and
Getting the full URL of the current page (PHP)
to name a couple.

JimHawkins
  • 4,843
  • 8
  • 35
  • 55
JSG
  • 390
  • 1
  • 4
  • 13
3

If you want to use PHP to do this then this way worked really well for me:


<?php

if(!isset($_SERVER["HTTPS"]) || $_SERVER["HTTPS"] != "on") {
    header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"], true, 301);
    //Prevent the rest of the script from executing.
    exit;
}
?>

It checks the HTTPS variable in the $_SERVER superglobal array to see if it equal to “on”. If the variable is not equal to on.

3

Use $_SERVER['HTTPS'] to tell if it is SSL, and redirect to the right place if not.

And remember, the page that displays the form does not need to be fed via HTTPS, it's the post back URL that needs it most.

Edit: yes, as is pointed out below, it's best to have the entire process in HTTPS. It's much more reassuring - I was pointing out that the post is the most critical part. Also, you need to take care that any cookies are set to be secure, so they will only be sent via SSL. The mod_rewrite solution is also very nifty, I've used it to secure a lot of applications on my own website.

Jason Plank
  • 2,336
  • 5
  • 31
  • 40
DGM
  • 26,629
  • 7
  • 58
  • 79
  • It's true that the form itself doesn't need to be https, though it's a good idea for the majority of people who don't know this. If they are about to submit the form and notice that the lock icon isn't there, they might mistakenly assume that the form is insecure. – Graeme Perrow Sep 17 '08 at 18:02
  • 1
    @Graeme: additionally nobody can be shure that the form will ever be sent through https. The whole form (displayed through http) might be a fake, submitting to an unknown or http cleartext site. Https is not just about encryption, it also authenticates the server. – Olaf Kock Sep 29 '08 at 20:02
1

If you use Apache or something like LiteSpeed, which supports .htaccess files, you can do the following. If you don't already have a .htaccess file, you should create a new .htaccess file in your root directory (usually where your index.php is located). Now add these lines as the first rewrite rules in your .htaccess:

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

You only need the instruction "RewriteEngine On" once in your .htaccess for all rewrite rules, so if you already have it, just copy the second and third line.

I hope this helps.

Antonio
  • 322
  • 2
  • 11
  • This is what worked for me and what I am using in my own scripts on a Zend 2 -like Framework - I don't understand the downvote. – Antonio Nov 02 '16 at 20:25
  • Maybe you got downvoted because the answer is virtually the same as [this one](http://stackoverflow.com/a/31000510/1697459) from @MatHatrik that was posted more than a year earlier? – Wilt Jan 24 '17 at 08:19
1

Using this is NOT enough:

if($_SERVER["HTTPS"] != "on")
{
    header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);
    exit();
}

If you have any http content (like an external http image source), the browser will detect a possible threat. So be sure all your ref and src inside your code are https

bourax webmaster
  • 748
  • 7
  • 18
1

I have been through many solutions with checking the status of $_SERVER[HTTPS] but seems like it is not reliable because sometimes it does not set or set to on, off, etc. causing the script to internal loop redirect.

Here is the most reliable solution if your server supports $_SERVER[SCRIPT_URI]

if (stripos(substr($_SERVER[SCRIPT_URI], 0, 5), "https") === false) {
    header("location:https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
    echo "<meta http-equiv='refresh' content='0; url=https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]'>";
    exit;
}

Please note that depending on your installation, your server might not support $_SERVER[SCRIPT_URI] but if it does, this is the better script to use.

You can check here: Why do some PHP installations have $_SERVER['SCRIPT_URI'] and others not

Community
  • 1
  • 1
Tarik
  • 4,270
  • 38
  • 35
1

For those using IIS adding this line in the web.config will help:

<httpProtocol>
    <customHeaders>
        <add name="Strict-Transport-Security" value="max-age=31536000"/>
    </customHeaders>
</httpProtocol>
<rewrite>
    <rules>
        <rule name="HTTP to HTTPS redirect" stopProcessing="true">
              <match url="(.*)" />
              <conditions>
                 <add input="{HTTPS}" pattern="off" ignoreCase="true" />
              </conditions>
              <action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" />
         </rule>
    </rules>
</rewrite>

A full example file

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
                <add name="Strict-Transport-Security" value="max-age=31536000"/>
             </customHeaders>
        </httpProtocol>
        <rewrite>
            <rules>
                <rule name="HTTP to HTTPS redirect" stopProcessing="true">
                      <match url="(.*)" />
                      <conditions>
                         <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                      </conditions>
                      <action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" />
                 </rule>
            </rules>
       </rewrite>
   </system.webServer>
</configuration>
Tschallacka
  • 27,901
  • 14
  • 88
  • 133
  • The question specifically asks about Apache, not IIS. – Quentin Apr 06 '17 at 10:10
  • 1
    A) its not tagged apache. Apache is metioned between quotes. B) its a generic question that has nothing to do with apache in general. Its applicable to multiple php supporting webservers – Tschallacka Apr 06 '17 at 10:13
1

As an alternative, you can make use of X-Forwarded-Proto header to force a redirect to HTTPS.

add these lines in the .htaccess file

### Force HTTPS
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Sumithran
  • 6,217
  • 4
  • 40
  • 54
1
if(location.protocol!=='https:'){location.replace(`https:${location.href.substring(location.protocol.length)}`);}
MRRaja
  • 1,073
  • 12
  • 25
1

Don't mix HTTP and HTTPS on the same page. If you have a form page that is served up via HTTP, I'm going to be nervous about submitting data -- I can't see if the submit goes over HTTPS or HTTP without doing a View Source and hunting for it.

Serving up the form over HTTPS along with the submit link isn't that heavy a change for the advantage.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JBB
  • 4,543
  • 3
  • 24
  • 25
0

maybe this one can help, you, that's how I did for my website, it works like a charm :

$protocol = $_SERVER["HTTP_CF_VISITOR"];

if (!strstr($protocol, 'https')){
    header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);
    exit();
}
0

You shouldn't for security reasons. Especially if cookies are in play here. It leaves you wide open to cookie-based replay attacks.

Either way, you should use Apache control rules to tune it.

Then you can test for HTTPS being enabled and redirect as-needed where needed.

You should redirect to the pay page only using a FORM POST (no get), and accesses to the page without a POST should be directed back to the other pages. (This will catch the people just hot-jumping.)

http://joseph.randomnetworks.com/archives/2004/07/22/redirect-to-ssl-using-apaches-htaccess/

Is a good place to start, apologies for not providing more. But you really should shove everything through SSL.

It's over-protective, but at least you have less worries.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
-1

I have used this script and it works well through the site.

if(empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == "off"){
    $redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    enter code hereheader('HTTP/1.1 301 Moved Permanently');
    header('Location: ' . $redirect);
    exit();
}
-2
<?php 
// Require https
if ($_SERVER['HTTPS'] != "on") {
    $url = "https://". $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
    header("Location: $url");
    exit;
}
?>

That easy.

Spell
  • 21
  • 5