Force SSL/https using .htaccess and mod_rewrite

301,135

Solution 1

For Apache, you can use mod_ssl to force SSL with the SSLRequireSSL Directive:

This directive forbids access unless HTTP over SSL (i.e. HTTPS) is enabled for the current connection. This is very handy inside the SSL-enabled virtual host or directories for defending against configuration errors that expose stuff that should be protected. When this directive is present all requests are denied which are not using SSL.

This will not do a redirect to https though. To redirect, try the following with mod_rewrite in your .htaccess file

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

or any of the various approaches given at

You can also solve this from within PHP in case your provider has disabled .htaccess (which is unlikely since you asked for it, but anyway)

if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
    if(!headers_sent()) {
        header("Status: 301 Moved Permanently");
        header(sprintf(
            'Location: https://%s%s',
            $_SERVER['HTTP_HOST'],
            $_SERVER['REQUEST_URI']
        ));
        exit();
    }
}

Solution 2

I found a mod_rewrite solution that works well for both proxied and unproxied servers.

If you are using CloudFlare, AWS Elastic Load Balancing, Heroku, OpenShift or any other Cloud/PaaS solution and you are experiencing redirect loops with normal HTTPS redirects, try the following snippet instead.

RewriteEngine On

# If we receive a forwarded http request from a proxy...
RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR]

# ...or just a plain old http request directly from the client
RewriteCond %{HTTP:X-Forwarded-Proto} =""
RewriteCond %{HTTPS} !=on

# Redirect to https version
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Solution 3

PHP Solution

Borrowing directly from Gordon's very comprehensive answer, I note that your question mentions being page-specific in forcing HTTPS/SSL connections.

function forceHTTPS(){
  $httpsURL = 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
  if( count( $_POST )>0 )
    die( 'Page should be accessed with HTTPS, but a POST Submission has been sent here. Adjust the form to point to '.$httpsURL );
  if( !isset( $_SERVER['HTTPS'] ) || $_SERVER['HTTPS']!=='on' ){
    if( !headers_sent() ){
      header( "Status: 301 Moved Permanently" );
      header( "Location: $httpsURL" );
      exit();
    }else{
      die( '<script type="javascript">document.location.href="'.$httpsURL.'";</script>' );
    }
  }
}

Then, as close to the top of these pages which you want to force to connect via PHP, you can require() a centralised file containing this (and any other) custom functions, and then simply run the forceHTTPS() function.

HTACCESS / mod_rewrite Solution

I have not implemented this kind of solution personally (I have tended to use the PHP solution, like the one above, for it's simplicity), but the following may be, at least, a good start.

RewriteEngine on

# Check for POST Submission
RewriteCond %{REQUEST_METHOD} !^POST$

# Forcing HTTPS
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{SERVER_PORT} 80
# Pages to Apply
RewriteCond %{REQUEST_URI} ^something_secure [OR]
RewriteCond %{REQUEST_URI} ^something_else_secure
RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

# Forcing HTTP
RewriteCond %{HTTPS} =on [OR]
RewriteCond %{SERVER_PORT} 443
# Pages to Apply
RewriteCond %{REQUEST_URI} ^something_public [OR]
RewriteCond %{REQUEST_URI} ^something_else_public
RewriteRule .* http://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

Solution 4

Mod-rewrite based solution :

Using the following code in htaccess automatically forwards all http requests to https.

RewriteEngine on

RewriteCond %{HTTPS}::%{HTTP_HOST} ^off::(?:www\.)?(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [NE,L,R]

This will redirect your non-www and www http requests to www version of https.

Another solution (Apache 2.4*)

RewriteEngine on

RewriteCond %{REQUEST_SCHEME}::%{HTTP_HOST} ^http::(?:www\.)?(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [NE,L,R]

This doesn't work on lower versions of apache as %{REQUEST_SCHEME} variable was added to mod-rewrite since 2.4.

Solution 5

I'd just like to point out that Apache has the worst inheritance rules when using multiple .htaccess files across directory depths. Two key pitfalls:

  • Only the rules contained in the deepest .htaccess file will be performed by default. You must specify the RewriteOptions InheritDownBefore directive (or similar) to change this. (see question)
  • The pattern is applied to the file path relative to the subdirectory and not the upper directory containing the .htaccess file with the given rule. (see discussion)

This means the suggested global solution on the Apache Wiki does not work if you use any other .htaccess files in subdirectories. I wrote a modified version that does:

RewriteEngine On
# This will enable the Rewrite capabilities

RewriteOptions InheritDownBefore
# This prevents the rule from being overrided by .htaccess files in subdirectories.

RewriteCond %{HTTPS} !=on
# This checks to make sure the connection is not already HTTPS

RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [QSA,R,L]
# This rule will redirect users from their original location, to the same location but using HTTPS.
# i.e.  http://www.example.com/foo/ to https://www.example.com/foo/
Share:
301,135

Related videos on Youtube

Sanjay Shah
Author by

Sanjay Shah

Updated on July 03, 2020

Comments

  • Sanjay Shah
    Sanjay Shah almost 4 years

    How can I force to SSL/https using .htaccess and mod_rewrite page specific in PHP.

  • depoulo
    depoulo almost 11 years
    out of curiosity, why the RewriteCond %{REQUEST_METHOD} !^POST$ ?
  • Luke Stevenson
    Luke Stevenson almost 11 years
    Because POST parameters aren't retained on a redirect. You can omit that line if you want to make sure that all POST submissions are secure (any unsecured POST submissions will be ignored).
  • StackOverflowNewbie
    StackOverflowNewbie almost 10 years
    @Lucanos - How to write a RewriteCond that doesn't force a redirect to either http or https when POST? My .htaccess forces HTTPS on certain specific pages, and then forces HTTP on the rest. However, on the HTTPS pages, there are forms that submit to my web root. The form specifies the action url as HTTPS. However, since the web root isn't one of those pages that are specified to force HTTPS, my .htaccess then forces a redirect -- which means the POST variables are lost. How do I prevent redirects on POST?
  • Luke Stevenson
    Luke Stevenson almost 10 years
    @StackOverflowNewbie: The line RewriteCond %{REQUEST_METHOD} !^POST$ should prevent POST submissions from being affected by these redirections.
  • Michael J. Calkins
    Michael J. Calkins over 9 years
    This is great in our situation because we currently have a mixture of http and https traffic. For our admin area we just popped in the .htaccess script while keeping the rest of the site http.
  • Czechnology
    Czechnology over 9 years
    When I use the mod_rewrite method, I get sent to https but with a "The page isn't redirecting properly" error.
  • Czechnology
    Czechnology over 9 years
    Followup: If you're having similar trouble, check your server and HTTP variables. If your server uses proxies, you might want to use %{HTTP:X-Forwarded-Proto} or %{HTTP:X-Real-Port} variables to check whether SSL is turned on.
  • cnlevy
    cnlevy almost 9 years
    Don't forget to set AllowOverride all in your apache httpd.conf; otherwise the rewriting rules in your .htaccess will be ignored
  • raphinesse
    raphinesse over 8 years
    If you experience redirect loops on servers running behind proxies (CloudFlare, Openshift), see this answer for a solution that works for that case too.
  • TheStoryCoder
    TheStoryCoder about 8 years
    I like this PHP version. It is much better as it considers a POST and handles better if headers have already been sent.
  • TheStoryCoder
    TheStoryCoder about 8 years
    To be even more complete you should also check for raw POST requests in the PHP code (eg. a JSON string). So the line where you count the POST values should be written like this instead: if (count( $_POST) > 0 || file_get_contents('php://input') )
  • GTodorov
    GTodorov almost 8 years
    It should be "RewriteRule ^https"... NOT "RewriteRule ^ https". Remove the space, otherwise you may get redirect issue.
  • Ryan
    Ryan almost 8 years
    Didn't work for me. I got "too many redirects" error. Then I followed instructions at support.cloudways.com/what-can-i-do-with-an-htaccess-file and it worked.
  • Sphinxxx
    Sphinxxx almost 8 years
    @GTodorov - Removing the space broke the rewriterule for me. From my (limited) knowledge of rewriterules, the syntax is RewriteRule <input-pattern> <output-url>. Thus, the space needs to be there, and the single ^ just says "match all input URLs".
  • GTodorov
    GTodorov almost 8 years
    @Sphinxxx Sorry about that, I don't know what I was thinking at that time. You're right!
  • Urgotto
    Urgotto over 6 years
    This is brilliant thx! But maybe it is require to add post condition? RewriteCond %{REQUEST_METHOD} !^POST$
  • raphinesse
    raphinesse about 6 years
    @Aleksej True, this breaks unencrypted POST requests. But I think this is a good thing. This way, people will notice they are doing something wrong. I guess the best thing would be to redirect them to a page that informs them on how to properly use your service.
  • RRN
    RRN over 5 years
    @raphinesse Do you know the answer of this question: stackoverflow.com/questions/51951082/…
  • Amir Savand
    Amir Savand over 5 years
    NO! The site URL needs to be dynamic.
  • ThN
    ThN over 5 years
    This is pretty much what people all over the Internet telling you to do for forcing https. However, every time I do that, my whole website becomes FORBIDDEN or You have no permission to view. something like that... What am I doing wrong? I just want to know if you have any idea before I post a question on this.
  • Ray
    Ray about 5 years
    For some reason when I did this, it made $_POST always empty
  • Risteard
    Risteard almost 5 years
    I had a similar problem and I had to apply the rules to the https.conf file. See my answer below.
  • William Entriken
    William Entriken over 4 years
    Can also add this line in case you ever run this site locally for testing. RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$
  • Alexufo
    Alexufo almost 2 years
    why you want to delete it?