Nginx HTTPS to HTTP Redirect for Missing Files: A try_files Implementation Guide


3 views

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:

  1. try_files first attempts to serve the requested file
  2. If the file doesn't exist, it falls back to the @redirect named location
  3. The 301 redirect preserves the original request URI
  4. 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