1

I want to do three things on my website:

  • Redirect users with a 301 to the HTTPS version of the site when they access the HTTP version
  • Redirect users with a 301 to the "no-www" version of the site when they put a www in the url
  • Silently (or internally) redirect /* to /index.php?p=* (as I am using a framework). (Or even, redirect "visually" /index.php?p=* to /*, then internally redirect /* to /index.php?p=* ...)

Here is my current .htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule (.*) https://%1/$1 [R=301]

RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.+) index.php?p=$1 [QSA]
</IfModule>

However when I am running this configuration, my 301 redirects are not working – more precisely, a 301 code is actually sent to the client, but no Location: header is sent:

$ curl http://mywebsite.com/path -I
HTTP/1.1 301 Moved Permanently
Date: Thu, 23 Apr 2020 19:03:57 GMT
Server: Apache/2.4.38 (Debian)
Set-Cookie: ci_session=q24j0enjrskagec2fggi256bpg5fsvs6; expires=Thu, 23-Apr-2020 20:39:57 GMT; Max-Age=7200; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=UTF-8

If I try to put the block about index.php before the two other ones, the HTTPS and www redirection work, but the Location: header has a weird behavior when dealing with pages:

λ curl http://mywebsite.com/fr/salon -I
HTTP/1.1 301 Moved Permanently
Date: Thu, 23 Apr 2020 19:09:03 GMT
Server: Apache/2.4.38 (Debian)
Location: https://mywebsite.com/fr/salon?p=fr/salon
Content-Type: text/html; charset=iso-8859-1

(You can also see the charset changed, for some weird reason)

What are the changes I must make to my rules to make my three redirections work?
Thank you!

Ailothaen
  • 23
  • 4

2 Answers2

1
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule (.*) https://%1/$1 [R=301]

RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]

You are missing the L (last) flag on both of your redirects. Without the L flag processing continues and is getting caught by the internal rewrite that follows.

For example:

RewriteRule (.*) https://%1/$1 [R=301,L]

UPDATE: You should strictly include the L flag on your later rewrite as well. Although if this is the last RewriteRule anyway then it doesn't strictly matter. However, if you later add more rules then it could be important.

Or even, redirect "visually" /index.php?p=* to /*

This part is only strictly required if you are changing an existing URL structure where the old index.php?p= URL is indexed by search engines or linked to by third parties.

The important point here is to avoid a potential redirect loop by preventing the rewritten URL being redirected. We can do this by checking against the REDIRECT_STATUS environment variable (which is empty on the initial request and set to "200", as in 200 OK HTTP status after the the first successful rewrite).

For example, before your existing redirects, immediately after the RewriteBase directive, you could do something like the following to externally redirect /index.php?p=* to /*:

RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{QUERY_STRING} ^p=([^&]*)
RewriteRule ^index\.php$ https://example.com/%1 [QSD,R=302,L]

The QSD (Query String Discard) flag (Apache 2.4+) is necessary in order to remove the query string (ie p=* URL parameter) from the request. This does assume you have no other URL parameters that need to be passed through - they are all discarded.

This also assumes that the p URL param always occurs at the start of the query string (as per your rewrite).

By including the absolute URL in the substitution string we avoid any secondary redirect associated with requesting HTTP or www subdomain.

Note that this is currently a 302 (temporary) redirect. It is preferable to first test with a 302, before changing to a 301 (permanent) redirect to avoid potential caching issues.

MrWhite
  • 12,647
  • 4
  • 29
  • 41
  • The thing is, if I put a `L` flag on both redirects and if I need *both* of them (in the case of a `www` in the URL AND a non-HTTPS connection), will the second be applied? Well, in this case it seems the first manages the HTTPS redirect as well, but I am curious to know what I should do if I need to apply two redirects. And I also need the internal rewrite - so wouldn't putting `L` affect that as well? – Ailothaen Apr 28 '20 at 08:19
  • You never need _both_ redirects for the same initial request - as you say, the first rule (www to non-www) redirects to HTTPS as well. There is only, at most, one redirect here. The internal rewrite only occurs when the redirects don't - that is correct. The purpose of the redirects are to canonicalize the request - this must be done and finished before the internal rewrite occurs. (An external redirect triggers an entirely separate request - hence the need for the `L` flag.) – MrWhite Apr 28 '20 at 10:32
  • I've updated my answer addressing the missing `L` flag on your rewrite and your additional "requirement"(?) to canonicalise/redirect a request of the form `/index.php?p=*` to `/*`. Without the `L` flags, your directives are essentially _conflicting_ with each other, as processing continues through the file and the next directive is triggered. – MrWhite Apr 28 '20 at 11:21
  • Indeed, it seems to work correctly with adding L flags now. (The part about "visual" redirection was not a requirement, it was more a bonus, but I guess this will be useful for me at some point). Thank you for the help. However, it seems like the `L` flag only applies to visible redirects? I am surprised the internal redirect still works, even though the URL is already redirected by a rule with the `L` flag in it. – Ailothaen Apr 28 '20 at 16:46
0

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

  • Does it handle the `index.php?p=` part? It does not look like to. – Ailothaen Apr 27 '20 at 22:15
  • Code only answers are not a good fit on StackExchange - please provide some explanation. This is really no different to the code/redirects given in the question, except for the addition of the - all important - `L` flag. @Ailothaen The internal rewrite to `index.php?p=` (as you already have) is still required following the redirect directives. – MrWhite Apr 28 '20 at 11:01