Share a Django dev server
Share a Django dev server with a Horizon tunnel, and fix the "CSRF verification failed" error on POST forms with CSRF_TRUSTED_ORIGINS.
Share your Django runserver with a public HTTPS URL, with POST forms and the admin login working.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - A Django project you can run with
python manage.py runserver
Start a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so the setting in the next step would stop matching. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:8000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:8000
Request log https://hrzn.run/dashboard/tunnels/my-appYour public URL is https://my-app.hrzn.run. Keep this terminal open.
Trust the public origin for POST requests
The CLI sets the Host header to localhost:8000, so Django's ALLOWED_HOSTS check passes. With DEBUG = True and an empty ALLOWED_HOSTS, Django accepts localhost.
POST forms need one more setting. Browsers send an Origin header with the public address, https://my-app.hrzn.run. Django compares Origin with the current host and with CSRF_TRUSTED_ORIGINS. The host is localhost:8000, so the comparison fails unless you list the public origin. Each entry needs its scheme.
CSRF_TRUSTED_ORIGINS = ["https://my-app.hrzn.run"]Restart runserver after you edit the file.
Check it works
Open https://my-app.hrzn.run in a browser. On the first visit Horizon shows a Before you continue page. Select Continue to site.
Then submit any form that uses {% csrf_token %}, such as the admin login at /admin/. The Horizon terminal prints one line per request:
GET 200 /admin/login/
POST 302 /admin/login/Troubleshooting
CSRF verification failed. Request aborted.
Django shows this page with a 403 status. The reason line says what failed.
Origin checking failed - https://my-app.hrzn.run does not match any trusted origins.meansCSRF_TRUSTED_ORIGINSis missing or doesn't match. Add"https://my-app.hrzn.run"with the scheme, and restartrunserver.CSRF cookie not set.means the browser didn't send the cookie. Open the app through the public URL only, not a mix oflocalhost:8000and the public URL.- If you started the tunnel without
-s, the subdomain changed. UpdateCSRF_TRUSTED_ORIGINS, or use"https://*.hrzn.run"to match any subdomain.
Invalid HTTP_HOST header
The full message reads Invalid HTTP_HOST header: 'my-app.hrzn.run'. You may need to add 'my-app.hrzn.run' to ALLOWED_HOSTS. You only see it if you set USE_X_FORWARDED_HOST = True, because Django then reads the public host from X-Forwarded-Host. Add the public host to ALLOWED_HOSTS, or remove the setting.
Absolute URLs point to localhost
Django builds absolute URLs, such as request.build_absolute_uri(), from Host, which is localhost:8000. Set USE_X_FORWARDED_HOST = True and add the public host to ALLOWED_HOSTS to use the public host.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that
runserverlistens on port 8000, the port in yourhrzn tunnelcommand.
Next steps
- Read Django's reference for CSRF_TRUSTED_ORIGINS.
- Share a different stack: Flask or FastAPI.
Use a Horizon tunnel with Expo
Point an Expo or React Native app on a phone at a local API through a Horizon tunnel, with an EXPO_PUBLIC_ environment variable.
Share a Flask app
Share a Flask app from flask run with a Horizon tunnel, and make url_for build the public https address with Werkzeug ProxyFix.