Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Lebod's avatar

Removing /index.php completely

So we have a Forge server set up with "Pretty URLs" set up in Nginx:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

The thing is, is that a person can still type in URL/index.php and visit the site. Additionally, we get visitors from Google into URL/index.php which means that Google is also indexing an index.php version of the site which could hurt SEO. Do you guys have a way to tackle this problem?

Thank you

0 likes
22 replies
Snapey's avatar

Why is that a problem? You only have the one duplicate / or /index.php

All other internal pages will be from your own links which won't have /index.php

Lebod's avatar

Having Google crawl two versions of your site is a problem whether we have our own internal links or not.

Imagine having two versions of the same product page indexed;

URL/index.php/PRODUCT and URL/PRODUCT

Not good from an SEO perspective.

4 likes
StormShadow's avatar

@Lebod my coworker also asked this question and I'm also interested in this. If you get an answer please post here.

Lebod's avatar

Lastly, it seems like once a person gets on the site using /index.php, every other link within the app uses index.php.

This also happens in Laracasts which I'm assuming is using Laravel. Just put in /index.php/discusss etc. and continue browsing the site. You'll see that index.php is used from then on.

4 likes
Lebod's avatar

I've been doing research on this for hours. The only thing that partially addresses it were a couple of posts on StackOverflow: http://stackoverflow.com/questions/21687288/nginx-redirect-loop-remove-index-php-from-url

Adding


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

Does redirect a URL/index.php to URL BUT it doesn't redirect other pages for me at least. So it won't redirect URL/index.php/SOMEPAGE to URL/SOMEPAGE.

Maybe somebody else could chime in and help us to get this fixed.

Lebod's avatar

A comment made on the same thread seems to fix it for me!

Here you guys go

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

The only downside that I'm seeing is that it leaves an extra forward slash in between the URLS For ex. URL/index.php/SOMEURL changes to URL//SOMEURL with the above change.

Any idea how to fix this ?

Update: It seems that the IF command is really frowned upon with NginX. The try_files command might be better but still trying to figure that out.

Lebod's avatar

Final answer for me was just to add:

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

All index.php URIs are now redirected to their respective pages. No additional slashes. No IF commands. Seems to be the perfect solution. What do you guys think?

Update: Arrhh, after testing this gets rid of /index.php on all pages except where there is no match for /index.php/ which would be the home directory; HOMEPAGE/index.php stays HOMEPAGE/index.php but HOMEPAGE/index.php/ goes to HOMEPAGE. The match is for exactly "/index.php/" with the last forward slash. Any index.php that doesn't end with a forward slash doesn't get re-written. It works. We could just put a directive on robots.txt to not crawl HOMEPAGE/index.php. All other pages will not be duplicated like before.

1 like
Snapey's avatar

None of my pages are duplicated because I don't use the Routes helper

eg, my menu has links like /about and /account etc

Lebod's avatar

Would a kind soul help and show how to remove /index.php (without the last forward slash) from the homepage? All instances of /index.php/ are redirected but not /index.php

For example; HOMEPAGE/index.php is not removed by this rule:

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

Maybe another rule can be added to redirect HOMEPAGE/index.php to HOMEPAGE ?

cbj4074's avatar

Ancient, I know, but still as relevant as ever.

@Lebod (or anyone else), you should simply be able to remove the anchor, ^, on the left-hand side of the expression:

rewrite /index.php/(.*) /  permanent;
MikeHopley's avatar

Update: Arrhh, after testing ...

You see, this is why I hate putting this logic in the server config (e.g. .htaccess). Testing it is a pain in the bum, and the syntax is tricky once you start combining multiple rules.

I put this logic in PHP, in a ResolveLinks middleware that handles redirects, etc. That way I can write the logic in PHP, and I can test it easily.

I know this is unconventional, and I know it's not quite as good for performance; so I'll just shut up now. ;-) But if the idea appeals to you, let me know and I'll post some code.

2 likes
innbativel's avatar

I am having exactly the same issue... Any update on this?

lcf8381595's avatar

i think i figure it out: ' if ($request_uri ~* "^(./)index.php/?(.)$") { return 301 $1$2; } '

TiBian's avatar

Here is what I use and is working perfect.

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

location / {
    try_files $uri $uri/ /index.php?$query_string;

    # Remove from everywhere index.php
    if ($request_uri ~* "^(.*/)index\.php(/?)(.*)") {
        return 301 $1$3;
    }
}


# Remove trailing slash.
if (!-d $request_filename) {
    rewrite ^/(.+)/$ /$1 permanent;
}

# Clean Double Slashes
if ($request_uri ~* "\/\/") {
  rewrite ^/(.*) /$1 permanent;
}

Another one problem is, when you visit one url for example https://laracasts.com/lessons/index.php you receive a nice message from nginx No input file specified.

To resolve this problem, on the section location ~ \.php$ add on the first line try_files $uri = 404;

and after the error_log or before the location ~ \.php$ add error_page 404 /index.php;

15 likes
cshelswell's avatar

@TiBian thanks that solution worked really well for me. Google webmaster tools often had links with index.php in which were causing issues.

amcardwell's avatar

I just want to also thank @TiBian for his response. This has been an issue for me for a while and this fixed it!

For all the ubuntu newbies out there like myself, make sure you add this to your nginx config file for your site. My file was at /etc/nginx/sites-available/mysite.com

If you are using Forge, this is as easy as editing your Nginx Configuration file by opening up your "site", clicking the "Edit Files" button at the bottom, and clicking "Edit Nginx Configuration". I added the code from @TiBian towards the bottom of the file. Here is my complete configuration file:

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/default/before/*;

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name default;
    root /home/forge/default/public;

    # FORGE SSL (DO NOT REMOVE!)
    # ssl_certificate;
    # ssl_certificate_key;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers '';
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DOT NOT REMOVE!)
    include forge-conf/default/server/*;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/default-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
    
    # Remove index.php$
    if ($request_uri ~* "^(.*/)index\.php$") {
        return 301 $1;
    }
    
    location \ {
        try_files $uri $uri/ /index.php?$query_string;
    
        # Remove from everywhere index.php
        if ($request_uri ~* "^(.*/)index\.php(/?)(.*)") {
            return 301 $1$3;
        }
    }
    
    
    # Remove trailing slash.
    if (!-d $request_filename) {
        rewrite ^/(.+)/$ /$1 permanent;
    }
    
    # Clean Double Slashes
    if ($request_uri ~* "\/\/") {
      rewrite ^/(.*) /$1 permanent;
    }

    location ~ /\.ht {
        deny all;
    }
}

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/default/after/*;

For me, the only change I had to make was to change the forward slash to a backslash to the right of "location":

 location \ {
        try_files $uri $uri/ /index.php?$query_string;

Anyway, I hope this helps.

TiBian's avatar

Thank @amcardwell,

@amcardwell follow again this post please.

NGiNX configurations for Forge and Homestead

Forge Configuration

The configurations begins after charset utf-8;

Before that don't change nothing where SITENAME you need to change with your domain name

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/SITENAME/before/*;

server {
    charset utf-8;

    # FORGE CONFIG (DOT NOT REMOVE!)
    include forge-conf/SITENAME/server/*;

    # Error Page
    error_page 404 /index.php;
    error_page 403 /index.php;

    # Cache everything for better performance 
    location ~*  \.(jpg|jpeg|png|gif|ico|svg|woff|woff2|css|js)$ {
        expires 365d;
    }
    
    # Remove index.php$
    if ($request_uri ~* "^(.*/)index\.php$") {
        return 301 $1;
    }
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;

        # Remove from everywhere index.php
        if ($request_uri ~* "^(.*/)index\.php(/?)(.*)") {
            return 301 $1$3;
        }
    }
    
    # Remove trailing slash.
    if (!-d $request_filename) {
        rewrite ^/(.+)/$ /$1 permanent;
    }

    # Clean Double Slashes
    if ($request_uri ~* "\/\/") {
        rewrite ^/(.*) /$1 permanent;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/SITENAME-error.log error;

    location ~ \.php$ {
        try_files  $uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/SITENAME/after/*;

Homestead Laravel Configuration

Edit scripts/serve-laravel.sh and simple run homestead provision to provision all your homestead sites

#!/usr/bin/env bash

mkdir /etc/nginx/ssl 2>/dev/null

PATH_SSL="/etc/nginx/ssl"
PATH_KEY="${PATH_SSL}/${1}.key"
PATH_CSR="${PATH_SSL}/${1}.csr"
PATH_CRT="${PATH_SSL}/${1}.crt"

if [ ! -f $PATH_KEY ] || [ ! -f $PATH_CSR ] || [ ! -f $PATH_CRT ]
then
  openssl genrsa -out "$PATH_KEY" 2048 2>/dev/null
  openssl req -new -key "$PATH_KEY" -out "$PATH_CSR" -subj "/CN=$1/O=Vagrant/C=UK" 2>/dev/null
  openssl x509 -req -days 365 -in "$PATH_CSR" -signkey "$PATH_KEY" -out "$PATH_CRT" 2>/dev/null
fi

block="server {
    listen ${3:-80};
    listen ${4:-443} ssl;
    server_name $1;
    root \"$2\";

    index index.html index.htm index.php;

    charset utf-8;

    # Error Page
    error_page 404 /index.php;
    error_page 403 /index.php;

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

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;

        # Remove from everywhere index.php
        if (\$request_uri ~* \"^(.*/)index\.php(/?)(.*)\") {
            return 301 \$1\$3;
        }
    }

    # Remove trailing slash.
    if (!-d \$request_filename) {
        rewrite ^/(.+)/$ /\$1 permanent;
    }

    # Clean Double Slashes
    if (\$request_uri ~* \"\/\/\") {
        rewrite ^/(.*) /\$1 permanent;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/$1-error.log error;

    sendfile off;

    client_max_body_size 100m;

    location ~ \.php$ {
        try_files  \$uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;

        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }

    location ~ /\.ht {
       deny all;
    }

    ssl_certificate     /etc/nginx/ssl/$1.crt;
    ssl_certificate_key /etc/nginx/ssl/$1.key;
}
"

echo "$block" > "/etc/nginx/sites-available/$1"
ln -fs "/etc/nginx/sites-available/$1" "/etc/nginx/sites-enabled/$1"

If someone use the follow configuration, Commented #.

    if ( $request_uri ~ "/index.(php|html?)" ) {
       rewrite ^ /$1 permanent;
    }

I hope this help you :)

4 likes
aurawindsurfing's avatar

HI Guys!

Thanks a lot for sharing it. I find it however bit disappointing that default forge configuration does not address such a basic SEO issue.

I actually went with middleware solution as I have no intention to remember this each time I will spin up a new forge server.

I'll ask Mohamed or Taylor and see what their response will be.

Thanks!

aurawindsurfing's avatar

So Mohamad replied and said it was part of original nginx configurations, but It was removed due to causing problems for some of the users.

I would argue however that this makes every site deployed with forge not really SEO friendly as it displays 2 url's for each resource on the site. I really do not understand why it was removed.

robjbrain's avatar

I know this is old, but I found this via Google and copied a year or so ago and it was a bad decision.

To anyone else finding this, DO NOT copy this part:

# Clean Double Slashes
    if ($request_uri ~* \"\/\/\") {
        rewrite ^/(.*) /$1 permanent;
    }

This will cause an infinite redirect if your URL includes a URL e.g. with Google Oauth your URL will look something like this:

domain.com/auth/social/return/youtube?state=XXXX&code=XXXX&scope=https://www.googleapis.com/auth/youtube.readonly

And the slashes in the "scope" part will cause an infinite redirect.

Please or to participate in this conversation.