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
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
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
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
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.
@Lebod my coworker also asked this question and I'm also interested in this. If you get an answer please post here.
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.
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.
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.
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.
None of my pages are duplicated because I don't use the Routes helper
eg, my menu has links like /about and /account etc
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 ?
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;
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.
I am having exactly the same issue... Any update on this?
@MikeHopley I am having exactly the same issue... Any update on this?
i think i figure it out: ' if ($request_uri ~* "^(./)index.php/?(.)$") { return 301 $1$2; } '
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 linetry_files $uri = 404;
and after the error_log or before the location ~ \.php$ add error_page 404 /index.php;
@TiBian thanks that solution worked really well for me. Google webmaster tools often had links with index.php in which were causing issues.
You are welcome @cshelswell
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.
Thank @amcardwell,
@amcardwell follow again this post please.
Forge and HomesteadForge 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.shand simple runhomestead provisionto 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 :)
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!
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.
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.