Skip to the page

Share a Django dev server and fix CSRF origin errors

Give runserver a public https link, and fix 'CSRF verification failed. Origin checking failed' with one line in settings.py.

Updated

Shared through ouicu, a Django dev server shows its pages but refuses every form: the admin login fails with CSRF verification failed. One line in settings.py fixes it, by trusting the link's origin.

Django at a glance
Django
Dev server port8000
On your Wi-Fipython manage.py runserver 0:8000
Host checkALLOWED_HOSTS

CSRF verification failed. Request aborted.

Log in to /admin/ through a ouicu link and Django answers 403 Forbidden, with this heading and, with DEBUG on, the reason under it:

What the browser shows
CSRF verification failed. Request aborted.Origin checking failed - https://calm-otter-4821.ouicu.app does not match any trusted origins.

The release notes for Django 4.0 put it plainly: “CSRF protection now consults the Origin header, if present.” ouicu hands runserver Host: localhost:8000, so ALLOWED_HOSTS passes as the project comes. But the browser's origin is the link's, https://calm-otter-4821.ouicu.app, and the two don't match. Pages load; forms don't.

Django through a ouicu link
Through the linkWhat it takes
The pageWorks, nothing to change
Hot reloadNothing to test
A form POSTNeeds CSRF_TRUSTED_ORIGINS = ["https://*.ouicu.app"]

Fix it: trust the link's origin

Add one line to settings.py, restart runserver, and log in again:

CSRF_TRUSTED_ORIGINS = ["https://*.ouicu.app"]

Django's settings reference allows the wildcard: “The setting also supports subdomains, so you could add 'https://*.example.com', for example, to allow access from all subdomains of example.com.” It trusts every name on ouicu.app, other people's too, which is fine on your laptop. With a reserved name, list just yours:

CSRF_TRUSTED_ORIGINS = ["https://studiolund.ouicu.app"]

Write the scheme. Since Django 4.0: “Values in the CSRF_TRUSTED_ORIGINS setting must include the scheme (e.g. 'http://' or 'https://') instead of only the hostname.” Keep it in your development settings.

Or let Django read the proxy's headers

To have Django see the public address itself, for build_absolute_uri() and the links it builds too:

ALLOWED_HOSTS = [".ouicu.app", ".localhost", "127.0.0.1", "[::1]"]USE_X_FORWARDED_HOST = TrueSECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

Then the login works with no CSRF_TRUSTED_ORIGINS, and request.build_absolute_uri() gives https://calm-otter-4821.ouicu.app/where/ where it gave http://localhost:8000/where/ before. Keep localhost in the list: ALLOWED_HOSTS = [".ouicu.app"] alone turns your own browser away with 400 Bad Request. And Django warns of the second setting:

“Modifying this setting can compromise your site's security.”

Django docs, Settings

ouicu sets X-Forwarded-Proto on every request, so it holds here; keep these to development all the same.

Invalid HTTP_HOST header

Tunnels that pass the public name on as Host meet ALLOWED_HOSTS before the CSRF check, and the terminal says:

In the terminal running runserver
Invalid HTTP_HOST header: 'calm-otter-4821.ouicu.app'. You may need to add 'calm-otter-4821.ouicu.app' to ALLOWED_HOSTS.

With ouicu you won't see it: the host is localhost. With DEBUG on and ALLOWED_HOSTS empty, Django answers ['.localhost', '127.0.0.1', '[::1]'] and nothing else. For another tunnel, add its name to the list as above.

Share runserver with ouicu

Start runserver as usual, then share its port in a second terminal. Getting started has the install.

python manage.py runserver# in a second terminalouicu share 8000

runserver listens on 127.0.0.1 only, and says so as it starts: Starting WSGI development server at http://127.0.0.1:8000/. That's all ouicu needs. On Free, a share runs for up to 2 hours.

A phone on your Wi-Fi is another matter. Django's docs: “Note that the default IP address, 127.0.0.1, is not accessible from other machines on your network.” It needs python manage.py runserver 0:8000 and the computer's address in ALLOWED_HOSTS. The QR code ouicu prints skips both (QR codes, how to open localhost on your phone).

Keep the admin to the people you send it to

A link reaches every page runserver answers, the admin login included. With DEBUG on, an error shows whoever hit it Django's debug page, with “a lot of metadata about your environment, such as all the currently defined Django settings”. For a client, put a password on the share, on Hobby and Pro:

ouicu share 8000 --password

Or let in only the people you name, on Pro, with --allow. Either way ouicu asks at the door, before a request reaches Django. And the link carries web pages only: a FileResponse download, a video or a PDF from your media folder is kept from visitors, as is anything over 50 MB (what isn't passed on).

Reloading, and why not upload

runserver restarts when you save a Python file, but has no hot reload in the browser: reload the page, on your screen and theirs. And ouicu deploy uploads static files only, while a Django site renders each page on the server (Uploading a built site). So for a client, share runserver while you walk them through it; see show a client a website before it goes live.

Tested with Django 6.1.2 on , with Python 3.12.3: django-admin startproject in a fresh virtualenv with a superuser, served by runserver and shared with ouicu share through ouicu's edge: the home page, the admin login before and after each fix, and build_absolute_uri(), in Chromium at the https link.

Sources

Prices, defaults and quotes about other products, and the day each was last checked at its source.

  1. django-admin and manage.py: runserver, Django docs. Checked .
  2. django/views/csrf.py, Django on GitHub. Checked .
  3. django/middleware/csrf.py, Django on GitHub. Checked .
  4. Django 4.0 release notes, Django docs. Checked .
  5. Settings, Django docs. Checked .
  6. django/http/request.py, Django on GitHub. Checked .