When serving static websites, having .html
extensions in URLs creates several issues:
- Breaks URL consistency when migrating from other web servers
- Makes URLs look less clean and professional
- Complicates URL sharing as users might manually remove extensions
Instead of creating multiple location blocks, we can handle this with a single rewrite rule in the server configuration:
server {
listen 80;
server_name foo.com;
root /srv/www/foo/public_html;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
# Handle requests that explicitly end with .html
location ~ \.html$ {
rewrite ^/(.*)\.html$ /$1 permanent;
}
}
The magic happens in two parts:
try_files
checks for the URI in this order:- Exact file match
- Same path with
.html
appended - Directory index
- 404 if none found
- The rewrite rule permanently redirects any
.html
URLs to their extension-less versions
For more complex scenarios, consider these additions:
# Ensure no directory listing
location ~ /$ {
try_files $uri $uri.html =404;
}
# Handle legacy URLs with .htm extension
location ~ \.htm$ {
rewrite ^/(.*)\.htm$ /$1 permanent;
}
# Proper content-type for HTML files
location ~* \.html?$ {
add_header Content-Type text/html;
}
Always verify your changes:
sudo nginx -t
(tests configuration syntax)sudo systemctl reload nginx
- Test with curl:
curl -I http://foo.com/bar
When serving static websites through Nginx, having .html
extensions in URLs creates several issues:
- Breaks consistent URL patterns when mixing dynamic and static content
- Exposes implementation details to end users
- Makes URLs longer and less memorable
The current approach using location
blocks has significant limitations:
# Bad approach - requires manual maintenance
location /bar1 {
alias /srv/www/foo/public_html/;
index bar1.html;
}
This becomes unmanageable with:
- Hundreds of HTML files
- Frequent content updates
- Team collaboration needs
Here's the proper Nginx configuration to handle this automatically:
server {
listen 80;
server_name foo.com;
root /srv/www/foo/public_html;
# Core rewrite rule
location / {
try_files $uri $uri.html $uri/ =404;
}
# Handle requests that end with .html
if ($request_uri ~ ^/(.*)\.html$) {
return 301 /$1;
}
# Prevent direct access to .html files
location ~ \.html$ {
internal;
}
}
The configuration implements several key features:
- Extensionless URLs:
try_files
checks for files with .html extension - Redirects: Any .html requests get 301 redirected
- Security: Direct access to .html files is blocked
For more complex scenarios, consider these additions:
# Handle index files properly
index index.html;
# Cache control for static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires 30d;
add_header Cache-Control "public";
}
# Custom error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
Always validate your Nginx changes:
# Test configuration syntax
sudo nginx -t
# Reload configuration
sudo systemctl reload nginx
Watch out for these potential problems:
- 403 Forbidden errors - check directory permissions
- Infinite redirects - ensure your rewrite logic is sound
- MIME type issues - verify your Content-Type headers