How to Fix Redirecting Correctly HTTP to HTTPS Using Django

If you’ve ever needed to exclude certain URLs from an HTTP-to-HTTPS redirect in Nginx especially when those URLs are dynamically generated by a backend like Django you’ve likely encountered confusing errors like 404 Not Found or 504 Gateway Timeout. Let’s dissect the problem and implement a clean solution while enhancing your Nginx configuration with practical improvements.

Understanding the Problem

Your goal is to allow two dynamically generated URLs (/this/endpoint1.txt and /this/endpoint2.txt) to be accessible over HTTP while redirecting all other traffic to HTTPS. The challenge arises because:

  1. Dynamic Content: These endpoints aren’t static files; they’re generated by Django via Gunicorn.
  2. Redirect Conflicts: Nginx’s return 301 directive overrides proxy rules if not structured properly.

Your initial attempts failed because:

  • Using root assumes the URLs map to physical files (they don’t).
  • proxy_pass http://127.0.0.1:80 creates a loop (Nginx redirects to itself).

Targeted location Blocks

To exclude specific URLs from the redirect:

  1. Define location blocks for the excluded URLs before the global redirect.
  2. Proxy requests for those URLs directly to Gunicorn (bypassing the HTTPS redirect).

Here’s the revised configuration:

HTTP Server Block (Port 80)

server {  
    listen 80;  
    listen [::]:80;  
    server_name mydomain.org;  

    # Handle excluded endpoints  
    location /this/endpoint1.txt {  
        proxy_pass http://unix:/run/gunicorn.sock;  # Match Gunicorn socket  
        proxy_set_header Host $http_host;  
        proxy_set_header X-Forwarded-Proto $scheme;  
        proxy_set_header X-Forwarded-For $remote_addr;  
        proxy_redirect off;  
    }  

    location /this/endpoint2.txt {  
        proxy_pass http://unix:/run/gunicorn.sock;  
        proxy_set_header Host $http_host;  
        proxy_set_header X-Forwarded-Proto $scheme;  
        proxy_set_header X-Forwarded-For $remote_addr;  
        proxy_redirect off;  
    }  

    # Redirect all other HTTP traffic to HTTPS  
    location / {  
        return 301 https://$server_name$request_uri;  
    }  
}  

HTTPS Server Block (Port 443)

server {  
    listen 443 ssl;  
    listen [::]:443 ssl;  
    server_name mydomain.org;  

    # SSL and other settings (keep your existing config)  
    ssl_certificate ...;  
    ssl_certificate_key ...;  

    # Serve Django via Gunicorn for all HTTPS requests  
    location / {  
        proxy_pass http://unix:/run/gunicorn.sock;  
        proxy_set_header Host $http_host;  
        proxy_set_header X-Forwarded-Proto $scheme;  
        proxy_set_header X-Forwarded-For $remote_addr;  
        proxy_redirect off;  
    }  

    # Static and media paths (keep your existing config)  
    location /static/ { ... }  
}  

Key Fixes Explained

  1. Order Matters:
    Nginx processes location blocks in order of specificity. By defining /this/endpoint1.txt and /this/endpoint2.txt before the global redirect, they’re excluded from the 301 rule.
  2. Proxy to Gunicorn Directly:
    Using proxy_pass http://unix:/run/gunicorn.sock (or http://0.0.0.0:8000 if bound to a port) ensures requests for the excluded URLs are sent directly to Django, avoiding infinite loops.
  3. Headers Consistency:
    Replicating proxy_set_header directives ensures Django receives the same metadata for both HTTP and HTTPS requests.

Enhancing Functionality

Let’s add features to improve security and flexibility:

Rate Limiting for Excluded Endpoints

http {  
    limit_req_zone $binary_remote_addr zone=excluded_endpoints:10m rate=5r/s;  

    server {  
        ...  
        location /this/endpoint1.txt {  
            limit_req zone=excluded_endpoints burst=10 nodelay;  
            proxy_pass ...;  
        }  
        location /this/endpoint2.txt {  
            limit_req zone=excluded_endpoints burst=10 nodelay;  
            proxy_pass ...;  
        }  
    }  
}  

Caching Dynamic Content

Cache responses for 5 minutes to reduce backend load:

location /this/endpoint1.txt {  
    proxy_cache my_cache;  
    proxy_cache_valid 200 5m;  
    proxy_pass ...;  
}  

Content-Type Enforcement

Force text/plain for the endpoints:

location ~ \.txt$ {  
    add_header Content-Type text/plain;  
    proxy_pass ...;  
}  

Final Thoughts

Final Thought
Excluding URLs from Nginx redirects hinges on prioritizing specificity in location blocks and ensuring requests bypass the HTTPS rule by proxying directly to your backend (like Gunicorn). Always test with tools like curl to confirm behavior, and enhance security with rate limiting or caching for high-traffic endpoints. By balancing precise routing with performance optimizations, you maintain a secure, efficient setup without compromising dynamic content needs.

Related blog posts