Fix "You’re Accessing the Development Server Over HTTPS"

Fix the Django runserver error saying you’re accessing the development server over HTTPS but it only supports HTTP: clear cached HSTS, turn off SSL redirects in development, avoid .dev domains, and run local HTTPS properly when you need it.

Problem statement

You open your local project and the page never loads. The terminal running runserver fills with lines like these:

[03/Oct/2026 10:12:44] code 400, message Bad request version ('\x16\x03\x01\x02\x00\x01...')
[03/Oct/2026 10:12:44] You're accessing the development server over HTTPS, but it only supports HTTP.

The browser is speaking TLS to a server that only speaks plain HTTP. The \x16\x03\x01 bytes are the start of a TLS handshake. Usually nothing is wrong with Django: the browser has been told to always use HTTPS for this host, by an HSTS header your own settings sent earlier, by the domain's top-level suffix, or by a redirect it cached.

Why runserver sees TLSBrowserupgrades to HTTPSrunserverplain HTTP onlyHandshake bytescode 400Fix the browseror the settingsThe development server cannot answer a TLS handshake, so stop the browser from starting one.
  1. Type the URL explicitly as http://127.0.0.1:8000/, not localhost with a cached redirect.
  2. Set SECURE_SSL_REDIRECT = False and SECURE_HSTS_SECONDS = 0 in development settings.
  3. Clear the cached HSTS entry: in Chrome open chrome://net-internals/#hsts and delete localhost; in Firefox forget the site from history.
  4. Do not use .dev, .app, or other HSTS-preloaded domains for local hosts; use .localhost or .test.
  5. If you need HTTPS locally, put a TLS proxy such as Caddy in front of runserver, or use runserver_plus with a certificate.

Step-by-step solution

1. Confirm the browser is the one upgrading

Open a private window and visit http://127.0.0.1:8000/. If it works there, the normal window has cached either a redirect or an HSTS policy for the host. Also try curl, which never uses browser HSTS:

curl -I http://127.0.0.1:8000/

A 200 or 302 to a normal path means runserver is fine. A 301 to https://... means your settings are redirecting, which is step 2.

2. Turn off HTTPS settings in development

Production settings usually include:

SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

If these leak into local development, the first request redirects to HTTPS and the HSTS header tells the browser to stick to HTTPS for a year. Keep them in production settings only, or read them from the environment with safe defaults:

# settings/development.py
from .base import *  # noqa

DEBUG = True
SECURE_SSL_REDIRECT = False
SECURE_HSTS_SECONDS = 0
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
cause and fixWhat you noticeCause and fixworks in a private windowcached HSTS or redirect;clear it for the hostcurl shows 301 to httpsSECURE_SSL_REDIRECT onlocally; disable ithost ends in .dev or .appHSTS preloaded TLD; use.localhost or .testonly behind a local proxyproxy speaks TLS torunserver; use http upstreamRunserver is rarely at fault; something told the browser to use TLSdjango-deployment.com
Find which layer asked for HTTPS and change that layer, not runserver.

3. Clear the cached HSTS policy

HSTS is stored per host name. Removing the setting does not remove the browser's memory of it.

  • Chrome and Edge: open chrome://net-internals/#hsts, type localhost (or your custom host) under Delete domain security policies, and click Delete.
  • Firefox: open History, find the site, right-click, and choose Forget About This Site.
  • Safari: remove the host from ~/Library/Cookies/HSTS.plist while Safari is closed.

Then load http://localhost:8000/ again. Using 127.0.0.1 instead of localhost is a quick workaround, because the policy was stored for the name, not the address.

4. Pick a local host name that is not HSTS preloaded

Every host under .dev and .app is on the browser HSTS preload list, so no browser will ever request them over HTTP. myproject.dev cannot work with runserver no matter what you clear. Use names under .localhost, which resolve to the loopback address in modern browsers, or .test, which is reserved for testing:

ALLOWED_HOSTS = ["localhost", "127.0.0.1", ".localhost", ".test"]

5. Run HTTPS locally when you really need it

Some features, such as secure cookies, OAuth callbacks, and service workers on custom hosts, need HTTPS even in development. Do not point the browser at runserver over HTTPS; terminate TLS in front of it. With Caddy:

caddy reverse-proxy --from myproject.localhost --to 127.0.0.1:8000

Caddy issues a locally trusted certificate and proxies plain HTTP to runserver. Alternatively, django-extensions provides runserver_plus --cert-file cert.pem, which serves TLS itself using Werkzeug.

do you need HTTPS locally?What are you testing?ordinary pages and formsPlain HTTPhttp://127.0.0.1:8000secure cookies, OAuthCaddy proxyTLS in frontquick one-off checkrunserver_pluswith --cert-fileOnly add TLS locally when a feature depends on itdjango-deployment.com
In every option, runserver itself keeps speaking plain HTTP.

Explanation

runserver uses Python's simple HTTP server, which has no TLS support. When a browser opens an HTTPS connection to port 8000, the first bytes it sends are a TLS ClientHello. Django tries to parse them as an HTTP request line, fails, logs code 400 with the binary bytes, and prints the hint about HTTPS. The browser shows a connection error because it never gets a TLS response.

The browser chose HTTPS because of HSTS (Strict-Transport-Security), a cached permanent redirect, a preloaded top-level domain, or simply an https:// URL you typed or bookmarked. HSTS is the stubborn one: once received, it applies for max-age seconds even after you remove the header from your settings.

Edge cases / notes

  • This error in production. If production logs show this message, you are running runserver behind a proxy that forwards HTTPS to it. Replace runserver with Gunicorn or Uvicorn; see Why Not Use runserver in Production.
  • Docker port mapping. A local Nginx or Traefik container configured with proxy_pass https://web:8000 causes the same error. Use http:// for the upstream.
  • includeSubDomains. An HSTS header with includeSubDomains sent from example.com also covers local.example.com. Avoid using real domains for local development.

Next steps

FAQ

Why does it work with 127.0.0.1 but not localhost?

HSTS policies are stored per host name. The browser remembered the policy for localhost, so the numeric address bypasses it.

Can I make runserver support HTTPS?

Not the built-in one. Use a TLS proxy such as Caddy in front of it, or runserver_plus from django-extensions.

Will turning off SECURE_HSTS_SECONDS fix the browser immediately?

No. It stops new policies, but the browser keeps the old one until it expires or you delete it manually.

Is it safe to disable these settings?

In development, yes. Keep SECURE_SSL_REDIRECT, HSTS, and secure cookies enabled in production settings.

Keep going