.htaccess rewrite rule for removing .php extension with exception of wp login and wp-admin

The old site was based on php and there are lot of pages which are indexed, since we are keeping same urls in new wp sites but without .php extension at the end…

Assuming your “old site” .php documents no longer exist (they shouldn’t) then your current rule block to remove the .php extension would seem to be far more complex than it needs to be (and/or incorrect). (See below.)

the above mention code also remove .php from admin pages too.

Ok, something a bit odd here… Your existing rule block already excludes any URL that contains /wp-admin/ – so this should already exclude your admin pages. To also exclude wp-login.php you could simply modify this same condition, although this would seem to be unnecessary (see below). wp-login.php is not the only exception you need to make and adding many exceptions in this way quickly becomes impractical.

A quick assessment of your current rule block…

1. RewriteCond %{REQUEST_URI} !/wp-(content|admin|includes)/ [NC]
2. RewriteCond %{REQUEST_FILENAME} !-d
3. RewriteCond %{REQUEST_FILENAME}\.php !-f
4. RewriteCond %{THE_REQUEST} ^(.+)\.php([#?][^\ ]*)?\ HTTP/
5. RewriteRule ^(.+)\.php$ $1 [R=301,L]
  1. As noted above, this already excludes any URL that contains /wp-content/, /wp-admin/ or /wp-includes/. However, assuming none of your old site URLs start with wp- then why not simply exclude any URL that starts /wp- – which naturally includes wp-login.php, /wp-admin, etc. There are potentially more than just wp-login and wp-admin URLs that need to be excluded here.

  2. Why do you need to check that the request does not map to a filesystem directory? Do you have directories that also end in .php?! Filesystem checks are relatively expensive and this condition looks superfluous.

  3. This check is not doing what (I think) you think it’s doing. The preceding comment states “the file does not exist” – that’s not what this condition does. If it did then you probably would not be asking your question. No part of this code checks that “the file does not exist”, which is really your main problem. If you request /foo.php then this condition checks that /foo.php.php does not exist (which I’m sure it doesn’t), or if you request /foo/bar/baz.php (where /foo/bar does not exist as a physical directory) then it checks that /foo.php does not exist – probably not the intention.

  4. The purpose of the check against THE_REQUEST server variable is to avoid a redirect loop – however, it can be simplified (the fragment identifier is never passed to the server, so # is never matched). If your old URLs never contain a query string then it can be simplified further. But note that if your old URLs did/do contain a query string, then this will currently be passed onto the new target URL – is this the intention?

  5. This redirect only works because of the RewriteBase directive that is set later in the # BEGIN WordPress section. Ideally, you would include the slash prefix on the substitution or include the canonical protocol + hostname (an absolute URL) and avoid a potential second redirect later.

Solution

Bringing the above points together, try something like the following instead (there is no need for the <IfModule mod_rewrite.c> wrapper):

RewriteCond %{REQUEST_URI} !^/wp-
RewriteCond %{THE_REQUEST} "^GET /.+\.php(\?\S*)? HTTP/"
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(?!index\.php$)(.+)\.php$ /$1 [R=301,L]

Surrounding the CondPattern in quotes (ie. "..") avoids the need to escape spaces in the regex.

\S is a shorthand negated character class that matches any non-whitespace character. Similar to [^\ ] as you had initially.

(?!index\.php$) – This negative lookahead assertion in the RewriteRule pattern ensures we don’t try to process the request after it has been rewritten to index.php by the WP front-controller.

The filesystem check (really just a failsafe) is the last condition since this is the most “expensive”, so it’s only processed when the other conditions (and RewriteRule pattern) are successful.

You will need to clear your browser cache before testing. Preferably test with 302 (temporary) redirects to avoid caching issues.

Leave a Comment