Just to make things clear, this is NOT a duplicate question like this one as core of question is different. What I'm trying to do is redirect users who try to access a site using a language code (typically en or cz) to index.php with language variable set. The site itself can recognise only English and Czech languages settings, every other is redirected based on visitor's IP geolocation (using an external online JSON API). But the problem is, that browser keeps redirecting on and on and it returns an error that too many redirects occurred. What's wrong? Tried to seek for an error, but couldn't see any.

This is complete .htaccess file:

Options -Indexes

RewriteEngine on
RewriteRule ^[a-z]{2,3}/$ /index.php?lang=$1
RewriteRule ^blog/?(.*)$ //blog.czghost.czlh/$1 [R=301]
RewriteRule ^ftp/?(.*)$ //ftp.czghost.czlh/$1 [R=301]

Also, if user writes in index.php?lang=$1, I need to redirect him to the nice URL variant instead. I've searched online, but it seems too complicated to me :(

I'm testing sites in my WAMP server on my Windows machine, before it's ready to deploy online.

  • Please give an example of the URL(s) you want to redirect from/to. There are several errors with the directives you have posted, so none of those can be working as intended. – MrWhite Apr 1 '18 at 16:13
  • 1
    czghost.czlh/cz/ should rewrite to czghost.czlh/index.php?lang=cz (but the actual page should redirect to the /cz/ variant). However czghost.czlh/blog/<anything> should redirect to blog.czghost.czlh/<anything> and similarily the czghost.czlh/ftp/<anything> should redirect to ftp.czghost.czlh/<anything>. Isn't it too complicated? – Polda18 Apr 1 '18 at 16:38

You've not actually stated the URL(s) you are requesting that trigger the redirect loop?

However, from the directives you have posted, it would seem that any request of the form /blog<anything> or /ftp<anything> would result in a redirect loop.

RewriteRule ^blog/?(.*)$ //blog.czghost.czlh/$1 [R=301]
RewriteRule ^ftp/?(.*)$ //ftp.czghost.czlh/$1 [R=301]

Apache mod_rewrite does not support protocol-relative URLs in the RewriteRule substitution - they are simply seen as root-relative (ie. a URL-path relative to the document root). You presumably see the result of this redirect loop in the address bar of your browser? It will go something like this:

  • Initial request: http://example.com/blog/xyz
  • Redirects to http://example.com//blog.czghost.czlh/xyz
  • Redirects to http://example.com//blog.czghost.czlh/.czghost.czlh/xyz
  • Redirects to http://example.com//blog.czghost.czlh/.czghost.czlh/.czghost.czlh/xyz
  • etc.

You need to include the protocol. For example:

RewriteRule ^blog/?(.*)$ https://blog.czghost.czlh/$1 [R=301,L]

I've also included the L (last) flag, since you want the redirect to trigger immediately, not run the risk of being further rewritten. This should really come before the internal rewrite (that rewrites to index.php).

You will need to make sure you have cleared the browser cache, as the erroneous 301 (permanent) redirect will have been cached by the browser. It can be easier to test with 302 (temporary) redirects for this reason.

Your blog and ftp rules can be handled by a single directive, using alternation. For example:

RewriteRule ^(blog|ftp)/?(.*)$ https://$1.czghost.czlh/$2 [R=301,L]

RewriteRule ^[a-z]{2,3}/$ /index.php?lang=$1

You don't have a capturing group (ie. parenthesised subpattern) in the RewriteRule pattern, so the $1 backreference will always be empty. This should be rewritten as:

RewriteRule ^([a-z]{2,3})/$ /index.php?lang=$1 [L]

Again, I've included the L flag, in order to prevent further rewriting. Note that this expects there to be a slash suffix on the end of the URL path. So, only URLs of the form /blog/en/ (with a slash suffix) would be successfully rewritten (although maybe this is unrelated).

So, in summary, this should be rewritten something like:

RewriteRule ^(blog|ftp)/?(.*)$ https://$1.czghost.czlh/$2 [R=301,L]
RewriteRule ^([a-z]{2,3})/$ /index.php?lang=$1 [L]

Also, if user writes in index.php?lang=$1, I need to redirect him to the nice URL variant instead.

You can do this with an additional redirect before the above directives. For example:

RewriteCond %{QUERY_STRING} ^lang=([a-z]{2,3})$
RewriteRule ^(index\.php)?$ /%1/? [R=301,L]

You need to use a RewriteCond directive to check the query string against the QUERY_STRING server variable (this can't be done in the RewriteRule directive). The preceding check against the REDIRECT_STATUS environment variable is to make sure we only process initial requests and not rewritten requests by the later directive that rewrites the URL to /index.php?lang=xyz. The REDIRECT_STATUS env var is empty on the initial request, but gets set to "200" (as in 200 OK - HTTP status) after the first successful rewrite.

This will match either index.php, or an empty URL path (eg. /?lang=xyz). %1 is a backreference to the last matched CondPattern (as opposed to $N that is a backreference to the RewriteRule pattern). The ? on the end of the RewriteRule substitution is necessary in order to remove the query string from the redirected URL (alternatively you can use the QSD flag on Apache 2.4+).

Again, best to test first with a 302 (temporary) redirect that won't be cached by the browser.

(From comments:) The only thing that works properly, is the /ftp/ redirect. /blog/ redirect stays with unchanged URL in a blank page

You should make sure that there is no physical directory on the filesystem with the same name, ie. /blog. And that this subdirectory doesn't have an additional .htaccess file that could be overriding this. Also, make sure that there isn't a file that shares the same basename blog. eg. blog.html or blog.php etc. If there is then you will need to make sure that MultiViews (part of mod_negotiation) is not enabled. ie. Options -Indexes -MultiViews. If MultiViews is enabled then Apache will make an internal subrequest for /blog.php (for example) before mod_rewrite is able to rewrite the URL.


Options -Indexes -MultiViews

RewriteEngine on

# Redirect direct requests for "index.php?lang=xyz" to "/xyz/"
RewriteCond %{QUERY_STRING} ^lang=([a-z]{2,3})$
RewriteRule ^(index\.php)?$ /%1/? [R=301,L]

# Redirect "blog" or "ftp/<anything>" to subdomain
RewriteRule ^(blog|ftp)/?(.*)$ https://$1.czghost.czlh/$2 [R=301,L]

# Internally rewrite "/xyz/" to "/index.php?lang=xyz"
RewriteRule ^([a-z]{2,3})/$ /index.php?lang=$1 [L]
| improve this answer | |
  • Now I understand. However, after updating .htaccess, clearing browser cookies, cache and everything, it still doesn't work. The only thing that works properly, is the /ftp/ redirect. /blog/ redirect stays with unchanged URL in a blank page and /xyz/ rewrite still loops the internal redirect. Any idea on what could cause that? – Polda18 Apr 1 '18 at 18:18
  • "/blog/ redirect stays with unchanged URL" - Is there a physical directory on the filesystem with the same name? (Is there an additional .htaccess file in this subdirectory?) "and /xyz/ rewrite still loops the internal redirect." - Ah yes, sorry, I overlooked that - I've updated my answer to address the redirect loop when requesting /xyz/. – MrWhite Apr 1 '18 at 20:11
  • Okay, that makes sense. Will try out tomorrow, now I'm going to sleep. And no, double checked, there is no /blog/ directory, nor additional .htaccess file then. – Polda18 Apr 1 '18 at 20:12
  • Is there a file that shares the same basename blog? eg. blog.html or blog.php, etc. If there is then you will need to make sure that MultiViews is disabled. ie. Options -Indexes -MultiViews. – MrWhite Apr 1 '18 at 23:56
  • Ah, blog.php blocked my redirect :D I prepared it as a gateway to two different blogs, but then decided to merge two blogs into one, and forgot to delete it :D – Polda18 Apr 2 '18 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.