Search code examples
pythonpython-3.xdjangonginxgunicorn

Deployed Django app returning 301s and NS_ERROR_REDIRECT_LOOP


I've just setup my Django app in my VPS using gunicorn and nginx, and I cannot access it in my browser through the custom domain that I assigned it to (ipantun.com) because it keeps returning 301s and finally stopped with NS_ERROR_REDIRECT_LOOP.

I followed this tutorial, and everything works fine with running it locally and also using gunicorn. So I suspect it may have something to do with how I configure my nginx and maybe even Cloudflare which is my domain registrar.

A thing that I noticed is if I go to the URL ipantun.com, then it will redirect to www.ipantun.com and it will also query the full URL as its URI as in the final URL will be https://www.ipantun.com/www.ipantun.com.

I expected the redirect of non-www to www as I have setup a Redirect Rule on Cloudflare to do so, but the querying is something I've never encountered before so I'm lost.

Every log files of gunicorn and nginx showed nothing wrong. Everything in /etc/nginx/sites-available is also in /etc/nginx/sites-enabled.

Not sure if it's relevant, but I'm also serving another React app from the same server on the same port ie. port 80. But as you'll see in the nginx config files below, the server_name is different which I'm not really sure if it's relevant.

Here are my settings.py, /etc/nginx/sites-available/default and /etc/nginx/sites-available/ipantun files.

settings.py

"""
Django settings for ipantun project.

Generated by 'django-admin startproject' using Django 3.2.23.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path
import os
from dotenv import load_dotenv

load_dotenv()  # take environment variables from .env.

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'django-secret-key')

# SECURITY WARNING: don't run with debug turned on in production!
# Security.W018
# DEBUG = True
# DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'
DEBUG = False

# Security.W020
ALLOWED_HOSTS = [
        '.localhost',
        '127.0.0.1',
        '[::1]',
        'ipantun.com',
        'www.ipantun.com',
        'my-server-ip-address'
        ]


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app.apps.AppConfig',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'ipantun.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'ipantun.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'pantunis',
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASS'),
        'HOST': 'localhost',
        'PORT': '',
    }
}

# Update database configuration from $DATABASE_URL environment variable (if defined)
import dj_database_url

if 'DATABASE_URL' in os.environ:
    DATABASES['default'] = dj_database_url.config(
        conn_max_age=500,
        conn_health_checks=True,
    )



# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

# STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
#STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATIC_URL = '/static/'
STATICFILES_DIRS = []

MEDIA_URL = 'media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Static file serving.
# https://whitenoise.readthedocs.io/en/stable/django.html#add-compression-and-caching-support
STORAGES = {
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}


# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# @login_required redirect URL
# Reference: https://stackoverflow.com/questions/3578882/how-to-specify-the-login-required-redirect-url-in-django
LOGIN_URL = "/app/signin/"

# Going through the "python3 manage.py check --deploy" command checklist
# Security.W004
# TODO: Read more about this: https://stackoverflow.com/questions/49166768/setting-secure-hsts-seconds-can-irreversibly-break-your-site
# SECURE_HSTS_SECONDS = 60

# Security.W005
# SECURE_HSTS_INCLUDE_SUBDOMAINS = True

# Security.W008
# SECURE_SSL_REDIRECT = True

# Security.W012
# SESSION_COOKIE_SECURE = True

# Security.W016
# CSRF_COOKIE_SECURE = True

# Security.W021
# SECURE_HSTS_PRELOAD = True

/etc/nginx/sites-available/default

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

    server_name pantunis.com www.pantunis.com;

    location / {
        root /var/www/html/build;

        index index.html index.htm index.nginx-debian.html;

        try_files $uri /index.html;
    }
}

/etc/nginx/sites-available/ipantun

server {
    listen 80;
    server_name ipantun.com www.ipantun.com;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/irfan/ipantun;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

I've also tried multiple solutions suggested on multiple StackOverflow such as checking my SECURE_SSL_REDIRECT which is False by default: source, setting APPEND_SLASH=False and DEBUG=False in settings.py. Nothing works.

If you also see anything inaccurate or straight up wrong in my settings.py or anything that I am sharing here, please feel free to comment or correct me in your answers. Thank you!


Solution

  • Alright, figured it out!

    Turns out, the problem was Cloudflare redirecting my website because the SSL/TLS encryption mode for the domain is set to Flexible and not Full (Strict). Once I set it to Full (Strict), I got an Invalid SSL certificate error. From there, it's just running:

    sudo certbot --nginx -d domain-name.com -d www.domain-name.com

    to get SSL certificates for all of my domains.

    All of these resources helped me:

    1. Why does Cloudflare keep redirecting?

    2. How To Secure Nginx with Let's Encrypt on Ubuntu

    3. Host multiple domains with a single certificate

    I considered turning off Cloudflare Proxy but from what I've read, I'll be better off with them on.