31

I want any requests like http://example.com/whatever/index.php, to do a 301 redirect to http://example.com/whatever/.

I tried adding:

rewrite ^(.*/)index.php$ $1 permanent;

location / {
    index  index.php;
}

The problem here, this rewrite gets run on the root url, which causes a infinite redirect loop.

Edit:

I need a general solution

http://example.com/ should serve the file webroot/index.php

http://example.com/index.php, should 301 redirect to http://example.com/

http://example.com/a/index.php should 301 redirect to http://example.com/a/

http://example.com/a/ should serve the index.php script at webroot/a/index.php

Basically, I never want to show "index.php" in the address bar. I have old backlinks that I need to redirect to the canonical url.

Paulo Boaventura
  • 1,365
  • 1
  • 9
  • 29
jcampbell1
  • 4,159
  • 3
  • 33
  • 35
  • well a redirect loop is def a logical thing to happen, because both URL's are the same, the `whatever/` will call `index.php` because you most probably have an `index index.php` line above – Mohammad AbuShady Feb 10 '14 at 22:02
  • I want to 301 redirect the external url, but internally serve the file index.php. How do I do that? – jcampbell1 Feb 11 '14 at 00:26

4 Answers4

74

Great question, with the solution similar to another one I've answered on ServerFault recently, although it's much simpler here, and you know exactly what you need.

What you want here is to only perform the redirect when the user explicitly requests /index.php, but never redirect any of the internal requests that end up being served by the actual index.php script, as defined through the index directive.

This should do just that, avoiding the loops:

server {
    index index.php;

    if ($request_uri ~* "^(.*/)index\.php$") {
        return 301 $1;
    }

    location / {

        # ...
    }
}
Community
  • 1
  • 1
cnst
  • 25,870
  • 6
  • 90
  • 122
  • This is the correct answer to the question. Note that the usage of `if` as @cnst is using it is **not** evil. – Fleshgrinder Feb 16 '14 at 18:13
  • Thanks, this works great. It was the last piece of the puzzle to move from Apache to Nginx. – jcampbell1 Feb 19 '14 at 23:07
  • 5
    Great answer. I wonder how it could be done without the `if() {}` syntax and instead as a one liner? Anyway, for now, I modified it a bit to preserve the query string if any: `if ($request_uri ~* "^(.*/)index\.php(?:.*)$") {` `return 301 $1$is_args$args;` `}` – J W May 17 '14 at 20:25
  • 5
    I think passing the query parameters should be allowed in it. Changing it to: if ($request_uri ~* "^(.*/)index\.php(.*)") { return 301 $1$2; } – Neo Sep 08 '14 at 12:46
  • 5
    Nice one @Neo To avoid getting double slash // I changed a little # Strip index.php to avoid duplicate content `if ($request_uri ~* "^(.*/)index\.php(/?)(.*)") { return 301 $1$3; }` – Jerem Jan 21 '16 at 16:50
  • And if you want this to work for `index.php`, `index.html`, and `index.htm` (the default nginx index stack), try: `if ($request_uri ~* "^(.*/)index\.(html?|php)\/?(.*)$") { return 301 $1$3; }` – Jacob Ford May 22 '16 at 03:24
  • I've made a number of changes to the provided answer, to more closely match the request. First, `$request_uri` already contains `$args`, which means a) you MUST account for them, b) you don't need to add them, given (a) is satisfied. Second, the `#fragment` MAY be passed to the request, and you MUST account for it as well. Third, you most likely want to capture FIRST `index.php`. Thus, the matcher I set for the leading part of URI is a non-greedy capture of anything that isn't a request delimiter. – AnrDaemon Aug 26 '16 at 11:29
  • This still leaves unresolved the issue of request delimiters passed in clear text when a full URL passed as request, including scheme, host and auth data. But such applications are rare and I've intentionally left it out for clarity. – AnrDaemon Aug 26 '16 at 11:30
  • @AnrDaemon, good points! However, can you clarify when `#fragment` may be passed to the request? I was not aware of that being a possibility. – cnst Aug 26 '16 at 15:40
  • nginx is a generic server, and you'd be surprised, how "generic" someone can be, since HTTP specification does not explicitly forbid passing a fragment to the server. Also the fragment may appear in the URI as the result of redirection, internal or external. In either case, using `#` as the separator is safe from protocol POV, as, under normal circumstances, it SHOULD NOT appear in the request unencoded. – AnrDaemon Aug 26 '16 at 16:05
  • Ok. Dear KarmaOverflow users, can someone point to me, where in the http://stackoverflow.com/review/suggested-edits/13471659 I've deviated from the original poster's intent?… Which line, character or whitespace is a, ekhm, "deviation"? – AnrDaemon Aug 26 '16 at 16:43
  • Thanks @Jerem , your suggestion was the most useful and worked wonders for me. – Dragunov Nov 05 '16 at 08:51
  • Don't we need to add a slash to cover it? `if ($request_uri ~* "^(.*/)index\.php/?$") {` – Gaurav May 29 '18 at 07:59
  • 1
    @Jacob Ford How do I need to modify the expression to match index.html and index without html (as extension)? –  Dec 18 '19 at 16:31
  • 1
    @AndrewK — `if ($request_uri ~* "^(.*/)index(\.(php|html))?/?$") { return 301 $1; }` – cnst Dec 18 '19 at 20:10
  • @AndrewK also see https://stackoverflow.com/a/59399739/1122270. – cnst Dec 18 '19 at 20:50
  • Nice answer, except, I don't want a redirect, I just want to quietly redirect (rewrite) `http://host.com/a/index.php/b/c?d=e&f=g` to `http://host.com/a/b/c?d=e&f=g` – AaA Oct 20 '20 at 03:18
2

Try that

location ~ /*/index.php {
    rewrite ^/(.*)/(.*) http://www.votre_domaine.com/$1 permanent;
}
location /index.php {
    return 301 http://www.example.com/;
}
dev691
  • 1,034
  • 1
  • 9
  • 17
2

If you already have first line mentioned below in your Nginx configuration file you don't have rewrite it again.

index index.php index.html index.htm;

rewrite ^(/.).html(?.)?$ $1$2 permanent;

rewrite ^/(.*)/$ /$1 permanent;

try_files $uri/index.html $uri.html $uri/ $uri =404;

This will remove .html from the URL and additionally will also remove "index" from home page or index page. For example - https://www.example.com/index will be changed to https://www.example.com

jyotisankar
  • 134
  • 1
  • 4
-3

Try

location = /whatever/index.php {
    return 301 $scheme://www.example.com/whatever/;
}

Another benefit from doing it this way is that nginx does a return faster than a rewrite.

abradley
  • 5
  • 3