Search code examples
sslnginxhttpsdjango-rest-framework

Cannot activate HTTPS links with Django and Nginx


I have a Django rest framework app running on a secured Nginx server. When I navigate on the DRF API, the protocol in the navigation bar is well redirected to https. My problem is that all the generated URLs are in http instead of https. I watched in the code, the different URLs are build with this method:

def build_absolute_uri(self, location=None):
        """
        Build an absolute URI from the location and the variables available in
        this request. If no ``location`` is specified, build the absolute URI
        using request.get_full_path(). If the location is absolute, convert it
        to an RFC 3987 compliant URI and return it. If location is relative or
        is scheme-relative (i.e., ``//example.com/``), urljoin() it to a base
        URL constructed from the request variables.
        """
        if location is None:
            # Make it an absolute URL (but schemeless and domainless) for the
            # edge case that the path starts with '//'.
            location = '//%s' % self.get_full_path()
        bits = urlsplit(location)
        if not (bits.scheme and bits.netloc):
            current_uri = '{scheme}://{host}{path}'.format(scheme=self.scheme,
                                                           host=self.get_host(),
                                                           path=self.path)
            # Join the constructed URL with the provided location, which will
            # allow the provided ``location`` to apply query strings to the
            # base path as well as override the host, if it begins with //
            location = urljoin(current_uri, location)
        return iri_to_uri(location)

In my case, this method always returns unsecured URLs (HTTP instead of HTTPS). I have edited my settings like it's described in this post: https://security.stackexchange.com/questions/8964/trying-to-make-a-django-based-site-use-https-only-not-sure-if-its-secure But the generated URL are still in HTTP. Here is my Nginx configuration:

server {
        listen 80;
        listen [::]:80;

        server_name backend.domaine.na.me www.backend.domaine.na.me server_ip:8800;

        return 301 https://$server_name$request_uri;
}

server {
        # SSL configuration

        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        include snippets/ssl-backend.domaine.na.me.conf;
        include snippets/ssl-params.conf;

        location / {
                proxy_pass http://server_ip:8800/;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto https;
                proxy_set_header Host $http_host;
                proxy_redirect off;
        }
}


server {
        listen 8800;
        server_name server_ip;
        location = /favicon.ico { access_log off; log_not_found off; }

        location /static/ {
                root /projects/my_django_app;
        }

        location /media/ {
                root /projects/my_django_app;
        }

        location / {
                include proxy_params;
                proxy_pass http://unix:/projects/my_django_app.sock;
        }

        location ~ /.well-known {
                allow all;
        }
}

And my Django configuration:

In wsgi.py:

os.environ['HTTPS'] = "on"
os.environ['wsgi.url_scheme'] = "https"

In settings.py(Note: I've tried both first two lines):

#SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_SCHEME', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

I really need to fix this problem because my DRF API returns some image URL which must be in HTTPS, else the site cannot be considered 100% secure.

What is wrong with my configuration? Am I missing some options?


Solution

  • The indirection isn't needed; you can merge the settings from the SSL block and the :8800 block to give something like this:

    server {
            listen 80;
            listen [::]:80;
    
            server_name backend.domaine.na.me www.backend.domaine.na.me;
    
            return 301 https://$server_name$request_uri;
    }
    
    server {
            # SSL configuration
    
            listen 443 ssl http2;
            listen [::]:443 ssl http2;
            include snippets/ssl-backend.domaine.na.me.conf;
            include snippets/ssl-params.conf;
    
            location = /favicon.ico { access_log off; log_not_found off; }
    
            location /static/ {
                    root /projects/my_django_app;
            }
    
            location /media/ {
                    root /projects/my_django_app;
            }
    
            location ~ /.well-known {
                    allow all;
            }
    
            location / {
                    proxy_pass http://unix:/projects/my_django_app.sock;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto https;
                    proxy_set_header Host $http_host;
                    proxy_redirect off;
            }
    }
    

    Then, set SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') and it should work.

    I can't find anything about include proxy_params, and I suspect that the headers that you set in the first proxy block aren't forwarded to the Django app at all.