iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
To remove .html or .php from public Nginx URLs, configure two separate behaviors: send a permanent redirect when a visitor explicitly requests the old extension URL, then internally resolve the extensionless path to the real file or PHP script. For example, /about.html should return one 301 redirect to /about, while a request for /about should be served internally from about.html.
This separation preserves search-engine canonical URLs, avoids redirect loops, keeps query strings testable, and lets Nginx handle directories and PHP safely. The exact configuration depends on whether the site is static HTML or PHP-backed.
What “removing the extension” actually changes
Nginx does not rename files when you remove an extension from a URL. It changes the relationship between the public request URI and the file that Nginx serves from the document root.
- Public URL: visitors use
/about. - Internal resolution: Nginx looks for
/var/www/site/about.html(or a PHP script such asabout.php). - Legacy URL redirect: a direct request for
/about.htmlor/about.phpreceives a single301response to/about.
The redirect must apply only to an extension that the client actually requested. If an internal fallback changes /about into /about.php and the redirect rule sees that internal URI, Nginx can redirect repeatedly. Using the original request URI for the redirect and keeping it separate from try_files prevents that loop.
#1 Best Overall
How Nginx maps a clean URL to a file
root builds the filesystem path
The root directive supplies the base directory. With root /var/www/site;, a request for /about initially corresponds to /var/www/site/about. Nginx does not automatically append .html or .php; you must define the candidates to check.
index handles directories
When a request maps to a directory, the index directive tells Nginx which file to look for, such as index.html or index.php. Directory processing can cause another internal redirect, so directory behavior should be chosen deliberately rather than left to an accidental interaction with extensionless rewrites.
Recommended Free Tools
try_files checks candidates in order
The try_files directive tests files and directories from left to right. It internally redirects to the first match, or returns the final status when none exists. The official Nginx static-content form is:
try_files $uri $uri/ $uri.html =404;
Adding $uri/index.html supports routes where a clean path should resolve directly to an index file. For PHP, the candidate list can include $uri.php and $uri/index.php before the final =404.
Rank #2
Choose the canonical URL policy first
Decide these rules before editing the server block:
- Extension policy: whether both
.htmland.phpare redirected, or only one type. - Trailing slash policy: whether a section is
/docsor/docs/. Keep the choice consistent; a directory may naturally require a slash. - Directory policy: whether
/imagesredirects to/images/, whether an index file is served, and whether directory listings are disabled. - Collision policy: what happens if
about.htmlandabout.phpboth exist. The first matchingtry_filescandidate wins, so the order is your policy. - Legacy accessibility: whether direct extension URLs always redirect, or whether some scripts remain directly reachable for APIs or administrative tools.
A clean migration normally makes the extensionless URL canonical, redirects old extension URLs with 301, and leaves query parameters unchanged.
Free tools Windows power users keep installed
One-click scans. No signup required.
Static HTML configuration
Basic server block
server {
root /var/www/site;
index index.html;
# Redirect only explicitly requested legacy extension URLs.
if ($request_uri ~ ^(.+).html$) {
return 301 $1;
}
location / {
try_files $uri $uri/ $uri.html $uri/index.html =404;
}
}
In this example, /about is tested as a real file first, then as a directory, then as about.html, and finally as about/index.html. If none exists, Nginx returns 404. A direct request for /about.html is redirected before the normal location resolves it.
Preserving query strings
Query strings are not part of the filesystem name, but they are part of the redirect contract. Test URLs such as /search.html?q=test and confirm that the destination is /search?q=test. A regular expression that expects the request URI to end immediately after .html may not match a URI carrying arguments, depending on the exact expression and variables used.
Rank #3
For a path-and-query-aware redirect, capture the path while allowing an optional query string, then append the current arguments explicitly:
if ($request_uri ~ ^(?<clean_path>.+).html(?:?.*)?$) {
return 301 $clean_path$is_args$args;
}
Use one redirect form consistently in your deployment and verify it with a real request. Do not append the arguments twice. The fragment part of a URL is never sent to Nginx, so it cannot be preserved server-side.
When to include $uri/index.html
Keep $uri/index.html when both of these are true: a request such as /guides should serve /guides/index.html, and you want that behavior even when the directory check does not produce the desired canonical slash redirect. If you want directory requests to canonicalize to /guides/, let directory handling and your slash policy perform that redirect, and test both forms.
PHP configuration
Extensionless PHP routes
server {
root /var/www/site;
index index.php index.html;
# Redirect explicitly requested extension URLs to the clean path.
if ($request_uri ~ ^(.+).(php|html)$) {
return 301 $1;
}
location / {
try_files $uri $uri/ $uri.php $uri/index.php =404;
}
location ~ .php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
The socket path, include file, and PHP-FPM version must match the installation. The order in try_files determines whether an existing file, a directory, a same-name PHP script, or an index script is selected first. If the site uses query parameters, use the query-aware redirect form shown above and test it explicitly.
Rank #4
Blocking direct PHP endpoints after migration
Some teams want /about.php to be unreachable from the public internet after the migration. Nginx can make the PHP location internal, but an internal location rejects direct external requests. Every legitimate PHP request must then arrive through an internal rewrite or try_files resolution.
location ~ .php$ {
internal;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
Apply this only after mapping every required route. A login handler, webhook, health endpoint, or other script that is still called directly will fail once the location becomes internal. Confirm the behavior in a staging environment before changing production access.
Why location precedence matters
Nginx first selects the most specific matching prefix location, then evaluates regular-expression locations in configuration order. The first matching regular expression wins. An internal redirect caused by try_files, index, or another directive starts location selection again.
This has several consequences:
- A broad regular-expression PHP location can intercept a request that you expected a prefix location to handle.
- An internal redirect to a generated
.phpURI can enter the PHP handler on the second location-selection pass. - Putting an unrelated regex location before the PHP handler can change which block receives the request.
- Broad rewrite rules can catch static assets, real directories, or API paths that should not be extensionless pages.
Keep the legacy redirect logically separate from try_files, and inspect every regex location when troubleshooting unexpected redirects or 404 responses.
Best Value
Deploy the change safely
- Inventory existing paths. List current
.htmland.phpfiles, directories, assets, API endpoints, and any routes that already rely on a trailing slash. - Choose candidate order. Decide whether an existing file, directory,
.html,.php, or index file wins when more than one candidate exists. - Add the redirect layer. Match only explicitly requested legacy extensions. Use the original request URI rather than a URI created by an internal fallback.
- Add the internal resolution layer. Put the appropriate
try_fileslist in the main location and finish with=404unless another fallback is intentional. - Check the PHP handler. Confirm the FastCGI include and Unix socket or TCP endpoint match the installed PHP-FPM service.
- Validate syntax. Run the local Nginx configuration test before reloading:
sudo nginx -t
Reload only after the test reports valid syntax:
sudo systemctl reload nginx
- Test without a browser cache. Use command-line requests so you can see status codes and the exact
Locationheader.
Verification commands and expected results
Run these against the host and paths used by your deployment. Replace https://example.test with your own host.
curl -I https://example.test/about
curl -I https://example.test/about.html
curl -I https://example.test/about.php
curl -I 'https://example.test/search.php?q=test'
curl -I https://example.test/missing-page
curl -I https://example.test/images/
curl -I -L --max-redirs 5 https://example.test/about.html
/aboutshould return the expected HTML or PHP response without an external redirect./about.htmland/about.phpshould each produce exactly one301to/about, when those legacy forms are enabled.- The search request should retain
?q=testafter the redirect. - A missing path should return your configured 404 response, not the homepage with a successful status.
- A real directory such as
/images/should follow your chosen slash and index behavior. - The limited redirect-following request should finish without a loop. A repeated chain of 301 responses indicates that the redirect layer is seeing an internally generated extension.
Common failure modes and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
/about returns 404 |
The try_files list does not include the real extension or the document root is wrong. |
Confirm root, file permissions, and the candidate order; add $uri.html or $uri.php as appropriate. |
/about.html redirects repeatedly |
The redirect rule is matching an internally generated URI. | Match the original request URI and keep the redirect separate from the internal fallback. |
| Query parameters disappear | The redirect replacement does not carry the original arguments, or arguments are appended twice. | Use a query-aware expression and $is_args$args, then test a URL containing a query string. |
| A directory becomes a file route | $uri.html or $uri.php appears before directory handling, or a regex location intercepts the request. |
Put $uri/ in the intended position and review location precedence. |
| Every unknown URL serves the homepage | A fallback such as /index.php or a named application route is being used instead of =404. |
Use an explicit application fallback only when the application is designed to handle arbitrary paths; otherwise end with =404. |
| PHP downloads as text or returns 404 | The PHP regex location is missing, the FastCGI socket is wrong, or the script was never internally resolved. | Check the PHP-FPM service, socket path, include file, and try_files candidates. |
| Direct PHP requests fail after hardening | The PHP location is marked internal while a legitimate endpoint still depends on external access. |
Map that endpoint through an internal route or remove internal for the specific supported case. |
| Slash redirects differ between pages | Directories and file-like routes are using different canonical policies. | Document whether the canonical form is slashless or slash-terminated, then test files, directories, and index pages separately. |
Handling both HTML and PHP during a migration
A mixed site often needs a temporary transition period. You can redirect both extensions while resolving only the type that should serve the clean route. For example, an older page may exist as contact.html while a newer route is account.php. If both contact.html and contact.php exist, the first matching candidate in try_files is the effective winner.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Make that choice explicit in the configuration and migration notes. Do not assume Nginx will merge the two responses or choose the newer file by timestamp. After the migration, remove obsolete files or narrow the redirect rule so that an unrelated PHP endpoint is not redirected as if it were a document page.
Host and scheme canonicalization
Extension removal is separate from forcing HTTPS or one hostname. Combining host, scheme, slash, and extension rewrites into a single broad rule makes loops and debugging harder. For host or scheme canonicalization, Nginx’s documented approach is to use separate server blocks with a simple return 301 to the canonical host and scheme. Then let the destination server handle extension redirects and internal file resolution.
Quick Recap
Static versus PHP implementations
| Decision point | Static HTML | PHP-backed routing |
|---|---|---|
| Internal candidates | $uri, $uri/, $uri.html, optionally $uri/index.html |
$uri, $uri/, $uri.php, optionally $uri/index.php |
| Execution layer | Nginx serves the matched file directly. | A regex PHP location passes the matched script to PHP-FPM. |
| Main operational risk | Directory and trailing-slash behavior can be inconsistent. | Wrong socket, regex precedence, or an overly broad PHP handler can break requests. |
| Direct extension policy | Usually redirect explicit .html requests. |
Redirect .php page URLs, but assess APIs and scripts that may need direct access. |
| Hardening option | End unmatched paths with =404. |
Use an internal PHP location only after every legitimate entry path is mapped. |
Practical final checklist
- The extensionless URL is the only canonical public page URL.
- Explicit legacy extension requests receive one 301 redirect.
- Internal resolution uses
try_filesand ends with an intentional fallback, normally=404for a file-oriented site. - Query strings survive the redirect exactly once.
- Real directories follow a documented trailing-slash and index policy.
- Candidate order resolves collisions between files, directories, HTML, and PHP predictably.
- Regex locations are ordered and narrow enough that assets and special endpoints are not intercepted.
- The PHP-FPM include and socket match the installed version.
nginx -tpasses before reload.- Requests for existing pages, old extension URLs, query strings, directories, missing paths, and redirect chains have all been checked.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

