On the server, I have a file (on the filesystem) called page.html, which I want to be accessed as site.com/page So if someone goes to site.com/page.html, it should 301 redirect to site.com/page

I've seen rewrite rules that will handle rewriting /page -> /page.html internally, but forcing it to 301 redirect /page.html -> /page as well causes a redirect loop for me.

The END flag looks like it can be used to do what I want, but it is not yet supported.

I've also tried using ENV as follows:

RewriteRule ^page$ /page.html [L,E=END:1]
RewriteCond %{ENV:END} !1
RewriteRule ^page.html$ /page [R=301,L]

But that results in a redirect loop as well.

  • As it should. I do not understand what you want- to redirect a page to a directory then the directory back to the original page does not make sense to me. – closetnoc Aug 29 '14 at 4:02
  • @closetnoc The question has since been clarified a bit; the OP appears to be looking for help with his rewrite rule to remove the file extension, but the current one results in a redirect loop (which is a common problem when removing extensions). – dan Aug 29 '14 at 13:16
  • 2
    If you change your line to RewriteCond %{ENV:REDIRECT_END} !1 it should work for you. mod_rewrite renames the environment variable out from under you so you have to set it with one name, and read it with another. – Stephen Ostermiller Aug 29 '14 at 13:46

I asked this same question on StackOverflow. To get it to work properly, you have to use environment variables:

RewriteRule ^page$ /page.html [L,E=LOOP:1]
RewriteCond %{ENV:REDIRECT_LOOP} !1
RewriteRule ^page.html$ /page [R=301,L]

This is because mod_rewrite does multiple passes through your rules. During the first pass, it sets the environment variable. During the second pass, it prepends the variable with the REDIRECT_ prefix, so you have to read it as REDIRECT_LOOP.

| improve this answer | |
  • +1, this may be cleaner than my solution. – Ilmari Karonen Aug 29 '14 at 13:49

The problem is that, when you use mod_rewrite in an .htaccess file or a <Directory> section, every successful RewriteRule — even an internal one — causes the request to be restarted internally, and thus the whole rewrite ruleset to be reprocessed.

Thus, what's happening is that, when the user visits /page, your internal RewriteRule matches and rewrites the URL to /page.html. But that makes Apache restart the request processing and run your ruleset again, causing the external rewrite rule to match and trigger a 301 redirect back to /page.

A quick and dirty (but effective!) fix is to make your internal rewrite rule append a dummy parameter like redirect=no to the URL, and check for that parameter in the external rewrite rule. Here's an example based on this answer I wrote for a similar question on Stack Overflow:

RewriteEngine On
RewriteBase /

# Externally rewrite page.html -> page, unless query includes redirect=no:
RewriteCond %{QUERY_STRING} !(^|&)redirect=no(&|$) 
RewriteRule ^(page)\.html$ /$1  [NS,R=301,L] 

# Internally rewrite page -> page.html, add redirect=no to query:
RewriteRule ^(page)$ $1.html?redirect=no [NS,QSA]

(Of course, feel free to replace redirect=no with something else if it conflicts with an actual URL parameter you might be using.)

| improve this answer | |
  • Thanks for your answer! That's the workaround that I had been using in the meanwhile. – arcyqwerty Aug 29 '14 at 18:43

A common way to prevent the redirect loop is to check against THE_REQUEST server variable, which contains the initial request header, rather than the URL-path of the rewritten URL (which the RewriteRule pattern matches against), which naturally gets updated as the URL is rewritten.

THE_REQUEST value does not change as the URL is internally rewritten, and contains a string of the form:

GET /page.html HTTP/1.1

So this could be rewritten as:

# Redirect direct requests for /page.html to the canonical URL
RewriteCond %{THE_REQUEST} \.html
RewriteRule ^(page)\.html$ /$1 [R=301,L]

# Rewrite the "pretty" URL to the actual filesystem path
RewriteRule ^(page)$ $1.html [L]

In this case, THE_REQUEST condition simply makes sure that .html is present in the initially requested URL. The check for page.html is left to the RewriteRule pattern - which is more efficient (since this is checked first).

It's always preferable to have any external redirects before the internal rewrites.

| improve this answer | |

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.