Send custom 404 error without changing page

What is the best way to send a custom 404 error to the browser without
it changing the URL on the user?

For instance I currently have this:
error_page 404 /404.php;

But then the URL in their browser is http://mysite.com/404.php which can
be annoying to users because instead of just fixing a typo in the url
they now have to retype the url.

So instead what I want to do is show the content from 404.php but have
the browser be on the typo url still.

For example let’s say the real page is products.php (products, plural)
and they type http://mysite.com/product.php (product, singular).

So now I want their browser to still show http://mysite.com/product.php,
but to actually show the 404.php page in the browser.

What is the best way to do this?

This is how I have done it now, but I do not like using the -f check
because now I feel like nginx has to check the file system on every
request to see if a file exists? That seems very inefficient don’t you
think?

location ~ .*.php$ {
if (!-f $request_filename) { rewrite . /404.php; }
… other php and fastcgi stuff here
}

So again, this does work, but I am concerned about the added overhead of
the server checking to see if the file exists on each request.

Is there a better way? Ideally I would like to do something like this:

error_page 404 /404.php noredirect;

where the “noredirect” (which I just made up) would tell Nginx “hey if
you ever need to issue a 404 get the content from 404.php but don’t
redirect them to the page, instead just feed them the content”. Is this
possible?

The idea is that same except in this later case only when the server
finds a page is missing does the action occur. Whereas in the -f case
the server has to check for the file in every request, when 99.9% of the
time there will be no 404.

Please let me know your thoughts. Thank you!!

On Sat, Feb 21, 2009 at 09:16:59AM -0800, Rt Ibmer wrote:

Please let me know your thoughts. Thank you!!
This

error_page 404 /404.php;

does exactly what you have described: it does internal only redirect
invisible for client.