37

Is there a way to actively serve Apache's default, built-in 404 page for a number of URLs using mod_rewrite? Not a custom error document, but a rule like

RewriteCond %{REQUEST_URI} ^/dirname/pagename
RewriteRule -- serve 404 page -----

I know how to build a PHP page that sends the 404 header and have mod_rewrite redirect all the URLs there but I would prefer a solution that is based on mod_rewrite only.

I just had the idea of redirecting to a non-existent address:

RewriteCond %{REQUEST_URI} ^/dirname/pagename
RewriteRule .* /sflkadsölfkasdfölkasdflökasdf

but that would give the user the message "/sflkadsölfkasdfölkasdflökasdf does not exist" on the error page, which looks a bit unprofessional.

David Moles
  • 48,006
  • 27
  • 136
  • 235
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • 1
    Voter to close: mod_rewrite questions are officially all right on SO. http://meta.stackexchange.com/questions/39063/mod-rewrite-questions-getting-migrated-to-sf – Pekka Mar 15 '10 at 12:44

4 Answers4

59

You can use the R flag on the RewriteRule to force a redirect with a given status code:

While this is typically used for redirects, any valid status code can be given here. If the status code is outside the redirect range (300-399), then the Substitution string is dropped and rewriting is stopped as if the L flag was used.

So this:

RewriteRule ^/?page\.html$ - [R=404]

would return the default 404 page for /page.html. Since this is a regexp, remember the escaping \. and anchoring $.

- is ignored (i.e. "the Substitution string is dropped"), but there still needs to be something there to keep the rule well-formed.

tuomassalo
  • 8,717
  • 6
  • 48
  • 50
mercator
  • 28,290
  • 8
  • 63
  • 72
  • Mmm not sure whether that would trigger the default error page, would it? Because I would have to put some value to `whatever` and it would redirect there wouldn't it? I may be wrong though, I'll try it out and report. – Pekka Mar 15 '10 at 13:23
  • No, as the quote says, it ignores the Substitution string (i.e. the "whatever" in my example). – mercator Mar 15 '10 at 13:25
  • @mercator works like a charm, only the `/` in `RewriteRule ^/page.html` needs to be removed. – Pekka Mar 18 '10 at 23:46
  • @Pekka, excellent! And fixed. I kinda neglected everything other than the `[R=404]`. The rest works the same as usual anyway. – mercator Mar 19 '10 at 00:16
  • 9
    The presence or absence of a leading "/" depends on whether the rewrite rule is in the global configuration (where it exists) or an .htaccess (where it's stripped by the [filename-to-URL mapping](http://httpd.apache.org/docs/current/rewrite/rewrite_tech.html)). Use `/?` for a subpattern that will work in either context. – outis Feb 04 '11 at 21:06
  • This is not "the best" way to do it. Best way is to carefully think where your `public` directory is located. E.g. `/var/www/some_site/public/` and `/var/www/some_site/secret_system_dir/` would be good design – although another, equally good option is to direct your traffic through `index.php`, and then exclude certain subfolders or files from an exclude-list that you feed to your **router**. Don't forget that things can also be done on the app-level, and performance overhead is negligible in most cases anyway, especially since the number of such 404's is very low compared to legit traffic. – Jacob Aug 04 '23 at 08:29
13

The best way to do that is to set the R flag with the status code 404:

RewriteCond %{REQUEST_URI} ^/dirname/pagename
RewriteRule ^ - [L,R=404]

But this is only available since Apache 2.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
4

I confirm that

RewriteRule ^ - [L,R=404]

or

RewriteRule ^ - [L,redirect=404]

won't work. Here is the explanation from the official Apache website:

However, if a status code is outside the redirect range (300-399) then the substitution string is dropped entirely, and rewriting is stopped as if the L were used.

So the best solution is to redirect to a 404.php file with the 404 header as explained later.

Max
  • 343
  • 3
  • 9
  • The Apache httpd docs for both 2.2 and 2.4 specify that *any* (emphasis theirs) valid status code may be returned, and does not have to be a 3xx code, in the sentence immediately before the one you quoted. The one you quoted only indicates that the substitution string is not used, which is what `-` would have done anyway. Both the examples you provided should work. – Bob Jul 12 '22 at 02:50
3

This should work:

RewriteEngine on
RewriteRule ^/page.html /error404.html [L]

That won't give the 404 header though. You could try chaning to flag to [L,R=404] but I doubt that will work (it's supposed to be for redirects only).

Your idea of doing so in PHP would work. If all your "error pages" pages are server-side (i.e. PHP) then you could simply use this code:

<?php
header( 'HTTP/1.0 404 Not Found' );
include 'error404.html';
DisgruntledGoat
  • 70,219
  • 68
  • 205
  • 290
  • Yup, this is probably what I'm going to end up with. I'm curious though whether there is a way to trigger Apache's built-in error page (without having to create one myself.) Something like a rewrite rule "trigger internal 404 handling if this URL matches." – Pekka Mar 15 '10 at 13:13
  • This solution is what I used more or less, except I think I simply redirected to a non-existent page handled by `index.php`– now I would recommend instead doing it on the app-level, as it allows for far greater flexibility and control over user access to static files as well as dynamic resources. You could just feed a list of directories and files to your **router** that you want to 404 out. Depending on your CMS, if not supported, you may need to do this from a *decorator* or modify the routing code yourself. Keep in mind, performance is usually negligible in this scenario of 404's. – Jacob Aug 04 '23 at 08:35