Removing the extension requires two separate operations: when a user or search engine visits the `.php` URL, an external 301 redirect is issued to the clean URL; the clean URL request is then internally rewritten to the actual `.php` file. If these two steps are not separated, infinite redirects and duplicate URLs may occur.
/product.php -> /product
Internal rewrite: /product -> /product.php
ERR_TOO_MANY_REDIRECTS
404 Not FoundRemoving the extension requires two separate operations: when a user or search engine visits the `.php` URL, an external 301 redirect is issued to the clean URL; the clean URL request is then internally rewritten to the actual `.php` file. If these two steps are not separated, infinite redirects and duplicate URLs may occur.
Do not expect a `.htaccess` result without reserving the Apache, LiteSpeed, Plesk proxy or NGINX-only structure.
Back up the current file with the date; Find the directive and line in the Apache/LiteSpeed error log.
Do not change redirect, rewrite, header and access rules all at the same time.
Measure the status, Location, Content-Type and redirect number with curl and check the cache layers separately.
Do not apply a broad, unconditional rewrite to all URLs to remove extensions.
Meaning: Internal rewrite enters external redirect condition again.
Possible cause: No differentiation is made using REQUEST_URI.
Meaning: The corresponding `.php` file is not being checked.
Possible cause: Wrong file path or subdirectory.
Meaning: Relative asset paths are resolved according to the new URL depth.
Possible cause: Base path/relative URL.
Meaning: The rewrite/redirect query behavior is incorrect.
Possible cause: QSA/QSD or `?` in target.
Meaning: No directory exception.
Possible cause: The `!-d` condition is missing.
Meaning: Being rewritten to the root path.
Possible cause: RewriteBase/relative substitution.
Meaning: The file extension rule conflicts with the front controller.
Possible cause: Rule order.
Meaning: The clean URL should go to the router, not a physical `.php` file.
Possible cause: Front-controller architecture.
No records matching this expression were found.
for u in https://example.com/product.php https://example.com/product; do curl -sS -o /dev/null -w "$u -> %{http_code} %{url_effective} redirects:%{num_redirects}\n" -L "$u"; doneCompares the results of URLs with and without extensions.
curl -sIL --max-redirs 10 'https://example.com/product.php?id=42' | grep -iE '^HTTP|^location:'Shows the redirect chain with the query string.
find . -maxdepth 2 -type f -name '*.php' -printf '%P\n' 2>/dev/null | sort | head -n 100Lists PHP files that may be subject to rewrite.
grep -nE 'THE_REQUEST|REQUEST_FILENAME|RewriteRule|RewriteCond' .htaccessShows the extension and front-controller rules with their order numbers.
curl -sL https://example.com/product | grep -ioE '(src|href)=["'"'][^"'"']+' | head -n 40Shows the asset paths on the clean URL page.
curl -sL https://example.com/product | grep -ioE '<link[^>]+rel=["'"']canonical["'"'][^>]*>' | head -n 1Checks if the canonical URL shows an extensionless target.
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+)$ $1.php [L]RewriteCond %{THE_REQUEST} \s/+(.+?)\.php(?:[\s?]) [NC]
RewriteRule ^(.+?)\.php$ /$1 [R=301,L,NE]
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+?)/?$ $1.php [END]RewriteRule ^(.+)$ $1.php [L]RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+?)/?$ $1.php [END]RewriteRule ^(.+)\.html$ $1 [L]RewriteCond %{THE_REQUEST} \s/+(.+?)\.html(?:[\s?]) [NC]
RewriteRule ^(.+?)\.html$ /$1 [R=301,L,NE]
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+?)/?$ $1.html [END]RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+)$ $1.php [L]RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L,QSA]Each clean URL can be mapped to a physical `.php` file.
In a front-controller architecture, use the router instead of removing physical extensions.
WordPress already generates clean URLs; custom file rules must come before the managed block.
No. NGINX does not read `.htaccess` files. The rule must be translated to a `server` or `location` configuration.
Usually not; Apache evaluates the `.htaccess` file on each request. However, a reload or restart is required for VirtualHost, module, or AllowOverride changes.
A dated backup of the existing file should be taken, the active document root verified, and the change tested on staging or during low-traffic periods.
In cPanel it is usually inside `public_html`, in Plesk inside `httpdocs`; addon domains and subdomains may have a different document root.
LiteSpeed supports most rules through Apache compatibility; some module and handler behaviors may differ.
Full directive and line record in Apache/LiteSpeed error log. The file should be restored according to the log instead of being deleted randomly.
Domain should not be added directly without verifying domain, document root, proxy, WordPress, and server structure. Examples should be adapted and tested.
We examine redirection, rewrite, CORS, access and 500 errors with logs in cPanel, Plesk, LiteSpeed, WordPress, custom PHP and WISECP structures.