Forbidden(403)

Hi, first time attempting to deploy on the cloud and in “Production”. My application was working correctly until I attempted to implement HTTPS. I was able to access the admin panel without issue make and make one post, now I am unable access the admin panel.
I was browsing other similar issues and most of the fixes consisted of adding CSRF_TRUSTED_ORIGINS.
Not in my case.

I think I am stuck with my novice understanding of the security of HTTPS: The POST request from nginx doesn’t have the correct headers to satisfy Djangos requirements to access the resources being requested?

I have added my IP/Domain to the CSRF list, rebuilt the images and cleared the browsing history.

I am out of ideas on what I need to try next.

I have a standard html front end.
Cookie is present when inspecting: csrftoken l5nlxaw### www.django-blog-jb.com Lax

Forbidden (403)

CSRF verification failed. Request aborted.
Origin checking failed -

here are some web container logs and nginx container logs:

80.64.30.78 - - [21/Jan/2025:19:32:33 +0000] "GET /remote/login HTTP/1.1" 403 555 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203" "-"
80.64.30.78 - - [21/Jan/2025:19:32:34 +0000] "GET /login HTTP/1.1" 400 59546 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203" "-"
154.213.184.16 - - [21/Jan/2025:19:33:51 +0000] "GET / HTTP/1.1" 400 59405 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:51 +0000] "GET / HTTP/1.1" 200 4468 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:56 +0000] "GET / HTTP/1.1" 200 4468 "https://www.django-blog-jb.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:56 +0000] "GET /about/ HTTP/1.1" 200 5082 "https://www.django-blog-jb.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:57 +0000] "GET /sitefeatures/ HTTP/1.1" 200 8304 "https://www.django-blog-jb.com/about/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:57 +0000] "GET /my_blog HTTP/1.1" 200 4009 "https://www.django-blog-jb.com/sitefeatures/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:35:59 +0000] "GET / HTTP/1.1" 200 4468 "https://www.django-blog-jb.com/my_blog" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:36:16 +0000] "GET /admin HTTP/1.1" 301 5 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:36:17 +0000] "GET /admin/ HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:36:17 +0000] "GET /admin/login/?next=/admin/ HTTP/1.1" 200 4160 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:19:36:20 +0000] "POST /admin/login/?next=/admin/ HTTP/1.1" 403 2563 "https://www.django-blog-jb.com/admin/login/?next=/admin/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"


Invalid HTTP_HOST header: '3.15.27.43'. You may need to add '3.15.27.43' to ALLOWED_HOSTS.
my_blog-web-1  | Traceback (most recent call last):
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
my_blog-web-1  |     response = get_response(request)
my_blog-web-1  |                ^^^^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/utils/deprecation.py", line 128, in __call__
my_blog-web-1  |     response = self.process_request(request)
my_blog-web-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/middleware/common.py", line 48, in process_request
my_blog-web-1  |     host = request.get_host()
my_blog-web-1  |            ^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/http/request.py", line 151, in get_host
my_blog-web-1  |     raise DisallowedHost(msg)
my_blog-web-1  | django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '3.15.27.43'. You may need to add '3.15.27.43' to ALLOWED_HOSTS.
my_blog-web-1  | Bad Request: /login
my_blog-web-1  | Invalid HTTP_HOST header: '3.15.27.43'. You may need to add '3.15.27.43' to ALLOWED_HOSTS.
my_blog-web-1  | Traceback (most recent call last):
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
my_blog-web-1  |     response = get_response(request)
my_blog-web-1  |                ^^^^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/utils/deprecation.py", line 128, in __call__
my_blog-web-1  |     response = self.process_request(request)
my_blog-web-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/middleware/common.py", line 48, in process_request
my_blog-web-1  |     host = request.get_host()
my_blog-web-1  |            ^^^^^^^^^^^^^^^^^^
my_blog-web-1  |   File "/usr/local/lib/python3.11/site-packages/django/http/request.py", line 151, in get_host
my_blog-web-1  |     raise DisallowedHost(msg)
my_blog-web-1  | django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '3.15.27.43'. You may need to add '3.15.27.43' to ALLOWED_HOSTS.
my_blog-web-1  | Bad Request: /
my_blog-web-1  | Forbidden (Origin checking failed - https://www.django-blog-jb.com does not match any trusted origins.): /admin/login/

ALLOWED_HOSTS = [
        'www.django-blog-jb.com',
        'django-blog-jb.com',
        '3.15.27.43,'
        'localhost',
]

CSRF_TRUSTED_ORIGINS = [
    "https://www.django-blog-jb.com",
    "https://django-blog-jb.com",
    "https://3.15.27.43",
]

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True


nginx

# Define the limit request zone at the HTTP level
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;

server {
    listen 80;
    server_name www.django-blog-jb.com django-blog-jb.com;

    # Redirect HTTP traffic to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name www.django-blog-jb.com django-blog-jb.com;

    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/www.django-blog-jb.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.django-blog-jb.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Serve static files
    location /static/ {
       alias /app/staticfiles/;
       access_log /var/log/nginx/static_access.log;
       error_log /var/log/nginx/static_error.log debug;
    }

    # Proxy pass to the Gunicorn app
    location / {
        proxy_pass http://web:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Apply rate limiting to all requests
        limit_req zone=one burst=10 nodelay;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }

   # Block known malicious traffic
    location ~* (\.env|/remote/login) {
        return 403;
    }
}

Notice what’s wrong with this line - especially if you compare it to the lines above and below it.

fixed, attempted to login and still nothing

[ec2-user@ip-172-31-10-224 config]$ docker compose logs web
my_blog-web-1  | [2025-01-21 21:29:40 +0000] [1] [INFO] Starting gunicorn 23.0.0
my_blog-web-1  | [2025-01-21 21:29:40 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
my_blog-web-1  | [2025-01-21 21:29:40 +0000] [1] [INFO] Using worker: sync
my_blog-web-1  | [2025-01-21 21:29:40 +0000] [7] [INFO] Booting worker with pid: 7
my_blog-web-1  | Forbidden (Origin checking failed - https://www.django-blog-jb.com does not match any trusted origins.): /admin/login/


/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/01/21 21:29:41 [notice] 1#1: using the "epoll" event method
2025/01/21 21:29:41 [notice] 1#1: nginx/1.27.3
2025/01/21 21:29:41 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) 
2025/01/21 21:29:41 [notice] 1#1: OS: Linux 6.1.115-126.197.amzn2023.x86_64
2025/01/21 21:29:41 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 32768:65536
2025/01/21 21:29:41 [notice] 1#1: start worker processes
2025/01/21 21:29:41 [notice] 1#1: start worker process 28
76.34.237.147 - - [21/Jan/2025:21:29:50 +0000] "GET / HTTP/1.1" 200 4468 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:21:29:56 +0000] "GET /admin/ HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:21:29:56 +0000] "GET /admin/login/?next=/admin/ HTTP/1.1" 200 4160 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"
76.34.237.147 - - [21/Jan/2025:21:29:58 +0000] "POST /admin/login/?next=/admin/ HTTP/1.1" 403 2563 "https://www.django-blog-jb.com/admin/login/?next=/admin/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "-"


Are you issuing the request in the browser as https://www.django-blog-jb.com?

Is there any JavaScript involved here, or is this all only HTML?

What are your current / corrected settings for ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS?

Look at the headers being set by the browser on the requests. Find the host and origin headers and post them here.

No JavaScript only HTML,

So I’m in the dev tools, network session,

I notice host doesn’t have https and origins does?

this is at the bottom:

POST https://www.django-blog-jb.com/admin/login/?next=/admin/ 403 (Forbidden)

Headers Tab

Request URL:
https://www.django-blog-jb.com/admin/login/?next=/admin/
Request Method:
POST
Status Code:
403 Forbidden
Remote Address:
3.15.27.43:443
Referrer Policy:
same-origin
connection:
keep-alive
content-length:
2563
content-type:
text/html; charset=utf-8
cross-origin-opener-policy:
same-origin
date:
Wed, 22 Jan 2025 12:04:20 GMT
referrer-policy:
same-origin
server:
nginx/1.27.3
x-content-type-options:
nosniff
x-frame-options:
DENY
accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-encoding:
gzip, deflate, br, zstd
accept-language:
en-US,en;q=0.9
cache-control:
max-age=0
connection:
keep-alive
content-length:
141
content-type:
application/x-www-form-urlencoded
cookie:
csrftoken=UppKxZO0rQ9via72O3w5QDvoz5fdVuxp
host:
www.django-blog-jb.com
origin:
https://www.django-blog-jb.com
referer:
https://www.django-blog-jb.com/admin/login/?next=/admin/
sec-ch-ua:
"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile:
?0
sec-ch-ua-platform:
"macOS"
sec-fetch-dest:
document
sec-fetch-mode:
navigate
sec-fetch-site:
same-origin
sec-fetch-user:
?1
upgrade-insecure-requests:
1
user-agent:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
ALLOWED_HOSTS = [
        'www.django-blog-jb.com',
        'django-blog-jb.com',
        '3.15.27.43',
        'localhost',
]

CSRF_TRUSTED_ORIGINS = [
    "https://www.django-blog-jb.com",
    "https://django-blog-jb.com",
    "https://3.15.27.43",
]

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True