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

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

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.

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

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 as about.php).
  • Legacy URL redirect: a direct request for /about.html or /about.php receives a single 301 response 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.

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.

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

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.

Choose the canonical URL policy first

Decide these rules before editing the server block:

  • Extension policy: whether both .html and .php are redirected, or only one type.
  • Trailing slash policy: whether a section is /docs or /docs/. Keep the choice consistent; a directory may naturally require a slash.
  • Directory policy: whether /images redirects to /images/, whether an index file is served, and whether directory listings are disabled.
  • Collision policy: what happens if about.html and about.php both exist. The first matching try_files candidate 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.

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

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.

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.

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

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.

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.

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

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 .php URI 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.

Deploy the change safely

  1. Inventory existing paths. List current .html and .php files, directories, assets, API endpoints, and any routes that already rely on a trailing slash.
  2. Choose candidate order. Decide whether an existing file, directory, .html, .php, or index file wins when more than one candidate exists.
  3. Add the redirect layer. Match only explicitly requested legacy extensions. Use the original request URI rather than a URI created by an internal fallback.
  4. Add the internal resolution layer. Put the appropriate try_files list in the main location and finish with =404 unless another fallback is intentional.
  5. Check the PHP handler. Confirm the FastCGI include and Unix socket or TCP endpoint match the installed PHP-FPM service.
  6. 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
  1. Test without a browser cache. Use command-line requests so you can see status codes and the exact Location header.

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
  • /about should return the expected HTML or PHP response without an external redirect.
  • /about.html and /about.php should each produce exactly one 301 to /about, when those legacy forms are enabled.
  • The search request should retain ?q=test after 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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

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.

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_files and ends with an intentional fallback, normally =404 for 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 -t passes 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.