Free tools Windows power users keep installed
One-click scans. No signup required.
The reliable way to remove .html or .php from Nginx URLs is a two-layer configuration: issue a public 301 redirect when a visitor explicitly requests the old extension, then use try_files to resolve the clean URL internally to the real file or PHP script. For example, /about.html redirects to /about, while a request for /about is served from about.html without exposing the filename.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
Do not use one broad rewrite for both jobs. Separating the external canonical redirect from internal file resolution prevents redirect loops, preserves a predictable status code, and lets you handle directories, query strings, PHP-FPM, and missing files deliberately.
As an Amazon Associate I earn from qualifying purchases.
How Nginx normally maps a URL to a file
Nginx starts with the root directive. It maps the request URI onto that directory on disk. With root /var/www/site;, a request for /about.html normally checks /var/www/site/about.html. The URI and filename are therefore identical unless another directive changes the process.
The index directive supplies a default file when a directory is requested. If /docs/ maps to a real directory and index index.html; is configured, Nginx performs additional internal processing to serve the index file. That is different from a public redirect: the browser can remain on /docs/ while Nginx serves /docs/index.html.
#1 Best Overall
The try_files directive checks candidate paths in order. It uses the first existing file or directory, internally redirects to that result, or ends with a fallback such as =404. The F5 NGINX Documentation describes its behavior this way: “The try_files directive can be used to check whether the specified file or directory exists; NGINX makes an internal redirect if it does, or returns a specified status code if it doesn’t.”
Why an internal lookup is not the same as a redirect
A redirect changes the URL displayed by the browser and sends a second request. An internal redirect changes Nginx’s processing route while the browser keeps the original URL. Clean extensionless URLs require both behaviors: old links need a public canonical redirect, while new links need an internal lookup.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11The canonical two-layer design
Layer 1: redirect only explicit legacy URLs
The redirect must run only when the client actually requested a filename ending in .html or .php. It should not react to an internally generated filename. Using the original request URI for this test is the important safeguard: an internal lookup of /about to /about.php must not trigger another public redirect.
Layer 2: resolve the clean URL internally
After the browser requests /about, try_files checks the clean path and then the extension-bearing candidates. The browser remains at /about, but Nginx can serve /about.html or pass /about.php to PHP-FPM.
Rank #2
Static HTML configuration
Use this server block when the site consists of static HTML files and directories:
server {
root /var/www/site;
index index.html;
# Redirect only explicit legacy extension requests.
if ($request_uri ~ ^(.+).html$) {
return 301 $1;
}
location / {
try_files $uri $uri/ $uri.html $uri/index.html =404;
}
}
The core form documented in the official Nginx static-content example is try_files $uri $uri/ $uri.html =404;. The additional $uri/index.html candidate is useful when extensionless directory-style routes should resolve to an index file.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →What each directive does
root /var/www/site;: sets the filesystem base. Replace it with the actual document root; a wrong root produces false 404 responses even when the files exist elsewhere.index index.html;: controls the default file for a real directory. Keep this separate from extensionless page lookup.- The
ifredirect: matches an explicitly requested path ending in.htmland returns one permanent redirect to the captured extensionless path. $uri: allows existing files such as stylesheets, images, robots.txt, and already-clean filenames to be served normally.$uri/: allows a real directory to continue through normal slash and index handling.$uri.html: maps/aboutto/about.html.$uri/index.html: maps a route to an index file when the directory-style arrangement requires it.=404: returns a not-found response instead of falling through to an unintended handler.
Implementation steps
- Confirm the document root and place the files in the expected locations. For
/aboutto resolve, verify that/var/www/site/about.htmlexists. - Add the extension redirect and the
try_fileslist inside the correctserverblock. If another server block handles the same hostname, edit the one selected for that host and scheme. - Choose a slash policy for directories. Keep
/images/as the canonical directory URL, or define a separate policy that redirects to a slashless form; do not let both forms become indexable. - Run
nginx -tand correct every syntax or file-path error before reloading. - Reload Nginx using the service manager used by your system, for example
systemctl reload nginx. A reload applies the new configuration without unnecessarily dropping active connections.
PHP configuration with PHP-FPM
For a PHP-backed site, the clean path must resolve to a PHP script and then be handed to the installed PHP-FPM socket or upstream. Adapt the socket, include file, and script naming to the PHP version and operating system:
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;
}
}
How the PHP request flows
- A visitor requests
/contact. - The prefix location tests
/contact,/contact/,/contact.php, and/contact/index.phpin that order. - If
/contact.phpexists, Nginx internally selects the PHP regular-expression location. - The FastCGI include supplies the standard parameters, and
fastcgi_passsends the script to PHP-FPM. - The browser continues to display
/contact.
The socket in the example is only an example for a PHP 8.3 installation. Check the socket actually created by your PHP-FPM package; common installations use a different PHP version, path, or TCP upstream. A wrong socket produces gateway errors even when URL resolution is correct.
Rank #3
Whether to block direct PHP URLs
After migration, you may want /contact.php to be inaccessible from the public internet. One option is to make the PHP location internal and ensure every legitimate PHP request arrives through an internal rewrite. An internal location rejects direct external requests, so test every application entry point before enabling it. If the application, health check, webhook, or administrative route still calls a PHP filename directly, that request will fail and must be changed first.
Preserving query strings and avoiding redirect loops
Query strings are part of the URL contract. A request such as /search.php?q=test should end at /search?q=test, not lose q=test. Verify this behavior in your deployment because redirect construction, existing rewrite rules, and application-specific configuration can affect the result.
The simple examples use the original request URI and a captured path. If your environment does not retain arguments automatically, make them explicit in the redirect target with Nginx variables:
if ($request_uri ~ ^([^?]+).(php|html)(?:?.*)?$) {
return 301 $1$is_args$args;
}
Use one redirect rule, not several competing rules for the same extension. Test both a URL with no arguments and one with multiple arguments. Do not append a literal question mark unless you intentionally want to discard the original arguments.
Rank #4
Why loops happen
- The redirect tests the rewritten URI: an internal
/aboutto/about.phplookup is seen as a new extension request and redirected repeatedly. Test the original client request instead. - The redirect and fallback are combined: a rewrite intended to serve a file is made public, causing the browser to alternate between clean and extension URLs.
- Slash rules conflict: one rule adds a slash while another removes it, creating a two-URL cycle.
- Another server block canonicalizes the host or scheme: HTTP-to-HTTPS or host redirects should be defined in separate, simple server blocks with
return 301, as recommended in nginx.org request-processing guidance.
Location precedence and regex ordering
Nginx does not simply read locations from top to bottom. It selects the most specific prefix location, then evaluates regular-expression locations in configuration order. An internal redirect starts location selection again. This matters because the PHP handler is a regular-expression location and can intercept a URI after try_files internally resolves it to a filename ending in .php.
Keep the general location / fallback and the PHP handler logically separate. Check every other regular-expression location for assets, security files, or application routes. The first matching regular expression wins after prefix selection, so moving a regex block can change behavior without changing the URL itself.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Directories, index files, and trailing slashes
Directories are the main reason a seemingly correct extension-removal rule behaves unexpectedly. In try_files $uri $uri/ $uri.html ..., the $uri/ candidate can succeed before the extension candidate. Nginx then applies directory and index processing, which may generate another internal redirect.
Choose one directory policy
- Slashful directories: keep
/images/and let Nginx serve its index or deny listing. This is the conventional policy and avoids relative-link problems. - File-like routes: use extensionless files such as
/imagesonly when they are actual page files, not directories with assets. - Separate application routes: reserve a prefix such as
/app/for a front controller and keep static directories outside that prefix.
Request a real directory during testing and inspect whether Nginx adds or removes a slash. Do not assume that the page test also validates directory behavior.
Best Value
Static HTML versus PHP: which pattern fits?
| Decision point | Static HTML pattern | PHP pattern |
|---|---|---|
| Clean URL lookup | $uri $uri/ $uri.html $uri/index.html |
$uri $uri/ $uri.php $uri/index.php |
| Execution layer | Nginx serves a file directly | Nginx passes the resolved script to PHP-FPM |
| Index setting | index index.html; |
index index.php index.html; |
| Extension redirect | Explicit .html requests redirect to the clean path |
Explicit .php and .html requests redirect to the clean path |
| Direct extension endpoint | Usually harmless if the redirect is canonical | Can be blocked with an internal PHP location, after complete testing |
| Primary failure mode | Wrong root, candidate order, or directory handling | Wrong candidate order, regex precedence, or PHP-FPM socket |
If both page.html and page.php exist, the candidate order decides which one wins. Decide explicitly whether the static or dynamic version is canonical, remove the collision, or use separate locations. Never let a deployment change the response merely because both files happen to be present.
Safe deployment procedure
- Inventory current URLs. Identify extension-bearing links, directory URLs, assets, forms, feeds, API endpoints, and scripts that may be called directly.
- Set the canonical policy. Decide whether the public form is
/page, whether directories retain a trailing slash, and whether old extensions receive a permanent 301. - Map representative files. For each important route, write down the expected filesystem path and confirm permissions allow Nginx to read it or PHP-FPM to execute it.
- Add the narrow redirect. Match only explicitly requested legacy extensions. Keep host and scheme canonicalization in their own server blocks.
- Add ordered
try_filescandidates. Put existing files and directories before extension candidates so assets and real directories are not hijacked. - Validate syntax. Run
nginx -t. Do not reload a configuration that reports an error. - Reload and inspect logs. Watch the Nginx error log and PHP-FPM log while requesting a clean page, an old URL, a missing path, and a directory.
- Check application-generated links. Update templates, sitemaps, canonical tags, navigation, and redirects to emit clean URLs directly. The 301 is a migration safety net, not a reason to keep generating old links.
Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
/about returns 404 while /about.html works |
The root is wrong, the $uri.html candidate is missing, or another location handles the request first. |
Confirm the absolute file path, inspect the selected server block, and place the extension candidate in the active prefix location. |
/about.html redirects repeatedly |
The rule is matching an internally generated URI or another redirect removes and re-adds the extension. | Test the original request URI only, remove overlapping rewrites, and follow the chain with a small redirect limit. |
The browser URL changes to /about.html |
The fallback was implemented as a public rewrite or redirect instead of an internal try_files lookup. |
Keep the extension lookup in try_files; reserve return 301 for explicit legacy requests. |
/search.php?q=test loses q=test |
The redirect target did not retain the original arguments. | Test the query-aware redirect form and inspect the Location header; ensure no rule intentionally clears arguments. |
/images behaves differently from /images/ |
A real directory is being selected by $uri/, invoking index or slash processing. |
Choose a single trailing-slash policy and test directory requests separately from page requests. |
| PHP pages return 404 or 502 | The script candidate is absent, the regex location is not selected, or the PHP-FPM socket is wrong or unavailable. | Verify the resolved script path, regex ordering, PHP-FPM service status, and socket path. |
| Static files are sent to PHP | A broad regex location or an overly broad fallback catches asset paths. | Review location precedence, narrow the PHP regex, and ensure existing files are tested before script candidates. |
| Only some hosts redirect | The rule was added to a server block that is not selected for every hostname or scheme. | Inspect the host-specific server blocks and use simple separate canonical redirects for host or scheme changes. |
Verification checklist
Run these checks against the deployed environment, not only against a configuration file:
- Request
/aboutand confirm the expected HTML or PHP response. - Request
/about.htmland confirm exactly one301to/about. - Request
/about.phpwhen PHP is enabled and confirm exactly one301to/about. - Request
/search.php?q=testand confirm the final URL retains the query string. - Request a missing path and confirm the configured
404response rather than an accidental index page. - Request a real directory such as
/images/and confirm the selected slash and index behavior. - Request a static asset such as a stylesheet or image and confirm it is not routed through the page fallback or PHP-FPM.
- Follow redirects with a small maximum, for example with a command-line HTTP client, and confirm there is no loop.
- Inspect response headers and logs for unexpected second redirects, upstream errors, or permission failures.
Practical rules for a clean migration
- Use permanent redirects only for the old public URLs; use internal resolution for the new URLs.
- Keep the redirect condition tied to the original request so internal filenames are not redirected.
- Put
$uriand$uri/before extension candidates to protect assets and directories. - Document whether a collision between
page.htmlandpage.phpis resolved in favor of static or dynamic content. - Test query strings, directories, missing files, host and scheme variants, and direct extension requests independently.
- Make the application generate extensionless links so users and crawlers do not repeatedly encounter migration redirects.
For a static site, the short configuration is a narrow .html redirect followed by try_files that checks $uri.html. For PHP, add the PHP candidate and a correctly ordered FastCGI location. The details that determine whether the migration is safe are not the removal of the suffix itself, but the separation of public redirects from internal resolution, the handling of directories and arguments, and verification that Nginx selects the intended location on every request.
Quick Recap
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.

