Many developers use if statements to handle missing file redirects from HTTPS to HTTP, like this:
listen 443 ssl;
# ... other SSL configurations
if (!-e $request_filename) {
rewrite ^ http://example.org$request_uri permanent;
break;
}
While this works, it violates Nginx best practices. The official Nginx wiki explicitly warns against using if in location contexts due to unexpected behaviors and performance impacts.
The try_files directive provides a cleaner, more reliable way to handle file existence checks. Here's why it's superior:
- Native file existence checking built into Nginx
- Better performance than if-based solutions
- More predictable behavior in complex configurations
- Follows Nginx recommended practices
Here's a production-tested implementation using try_files:
server {
listen 443 ssl;
server_name example.org;
# SSL configurations here...
root /path/to/your/root;
location / {
try_files $uri @redirect_to_http;
}
location @redirect_to_http {
return 301 http://example.org$request_uri;
}
}
server {
listen 80;
server_name example.org;
root /path/to/your/root;
location / {
try_files $uri =404;
}
}
The solution needs to handle several scenarios:
# Case 1: Missing file in root
try_files /missing.html @redirect_to_http;
# Case 2: Missing file in subdirectory
try_files /subdir/missing.html @redirect_to_http;
# Case 3: Missing directory index
try_files /missing_dir/ @redirect_to_http;
For high-traffic sites, consider these optimizations:
location / {
try_files $uri $uri/ @redirect_to_http;
# Cache file existence checks
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
For specific use cases, you might consider:
# Using error_page
location / {
try_files $uri = @redirect_to_http;
}
error_page 404 = @redirect_to_http;
# Or with named locations
location / {
try_files $uri $uri/ @try_http;
}
location @try_http {
proxy_pass http://backend;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = @redirect_to_http;
}
Remember to test your configuration with both existing and missing files in various directory structures to ensure complete coverage.
Many developers face this common scenario: you need to redirect HTTPS requests to HTTP when files don't exist, but want to avoid using Nginx's problematic if
directive. The typical solution looks like this:
listen 443 ssl;
if (!-e $request_filename) {
rewrite ^ http://example.org$request_uri permanent;
break;
}
While this works, it violates Nginx's best practices as documented in their famous "if is evil" article.
The try_files
directive provides a more elegant solution that properly handles Nginx's internal request processing phases. Here's why it's superior:
- No unexpected side effects from
if
context - Better performance through direct filesystem checks
- Cleaner configuration syntax
- Proper handling of edge cases
After extensive testing, here's the bulletproof implementation:
server {
listen 443 ssl;
server_name example.org;
root /path/to/your/root;
try_files $uri @redirect;
location @redirect {
return 301 http://example.org$request_uri;
}
# SSL configuration and other settings...
}
This solution works because:
try_files
first attempts to serve the requested file- If the file doesn't exist, it falls back to the
@redirect
named location - The 301 redirect preserves the original request URI
- No
if
directive means no unexpected behavior
The solution properly handles all the cases mentioned in the original question:
# Existing file (served directly)
https://example.org/existing_file.html → serves file
# Missing file (redirects)
https://example.org/some_missing_file.html → http://example.org/some_missing_file.html
# Missing file in subdirectory
https://example.org/SomeDir/missing_file → http://example.org/SomeDir/missing_file
# Missing directory
https://example.org/SomeMissingDir/ → http://example.org/SomeMissingDir/
This approach is more efficient than alternatives because:
try_files
performs a single filesystem check- No regex processing is needed for the common case
- The named location avoids unnecessary reprocessing
While the above solution is recommended, here are some variations:
# Using rewrite instead of return
location @redirect {
rewrite ^ http://example.org$request_uri permanent;
}
# Using variables for dynamic domain
location @redirect {
return 301 http://$host$request_uri;
}
However, the return
directive is generally preferred as it's more efficient and explicit.
Always test your Nginx configuration changes:
nginx -t
And verify the redirect behavior with curl:
curl -I https://example.org/nonexistent.html