Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › HTML

How to Remove URL File Extensions in Nginx (HTML and PHP)

By Freedom251 Team · · 11 min read

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 if redirect: matches an explicitly requested path ending in .html and 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 /about to /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

  1. Confirm the document root and place the files in the expected locations. For /about to resolve, verify that /var/www/site/about.html exists.
  2. Add the extension redirect and the try_files list inside the correct server block. If another server block handles the same hostname, edit the one selected for that host and scheme.
  3. 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.
  4. Run nginx -t and correct every syntax or file-path error before reloading.
  5. 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

  1. A visitor requests /contact.
  2. The prefix location tests /contact, /contact/, /contact.php, and /contact/index.php in that order.
  3. If /contact.php exists, Nginx internally selects the PHP regular-expression location.
  4. The FastCGI include supplies the standard parameters, and fastcgi_pass sends the script to PHP-FPM.
  5. 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Why loops happen

  • The redirect tests the rewritten URI: an internal /about to /about.php lookup 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 /images only 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.

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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Safe deployment procedure

  1. Inventory current URLs. Identify extension-bearing links, directory URLs, assets, forms, feeds, API endpoints, and scripts that may be called directly.
  2. 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.
  3. 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.
  4. Add the narrow redirect. Match only explicitly requested legacy extensions. Keep host and scheme canonicalization in their own server blocks.
  5. Add ordered try_files candidates. Put existing files and directories before extension candidates so assets and real directories are not hijacked.
  6. Validate syntax. Run nginx -t. Do not reload a configuration that reports an error.
  7. 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.
  8. 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Request /about and confirm the expected HTML or PHP response.
  • Request /about.html and confirm exactly one 301 to /about.
  • Request /about.php when PHP is enabled and confirm exactly one 301 to /about.
  • Request /search.php?q=test and confirm the final URL retains the query string.
  • Request a missing path and confirm the configured 404 response 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 $uri and $uri/ before extension candidates to protect assets and directories.
  • Document whether a collision between page.html and page.php is 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.

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.

HTMLNGINXPHPPHP-FPMSEOserver configurationURL rewritingweb servers

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.