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.
Share your Flask app from flask run with a public HTTPS URL, with generated links pointing at the tunnel.
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 Flask app you can run with
flask run
Start a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:5000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:5000
Request log https://hrzn.run/dashboard/tunnels/my-appYour public URL is https://my-app.hrzn.run. Keep this terminal open.
Use the public host and scheme
The CLI sets Host to localhost:5000. Without changes, url_for(..., _external=True) and request.url_root return http://localhost:5000/. The public host arrives in X-Forwarded-Host.
Wrap the app in Werkzeug's ProxyFix with x_host=1 to read that header. Don't set x_proto. Horizon appends to X-Forwarded-Proto, so the app can receive https,http, and ProxyFix reads the last value, http. Set the scheme yourself instead.
from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
class ForceHttps:
def __init__(self, wsgi_app):
self.wsgi_app = wsgi_app
def __call__(self, environ, start_response):
environ["wsgi.url_scheme"] = "https"
return self.wsgi_app(environ, start_response)
app.wsgi_app = ProxyFix(ForceHttps(app.wsgi_app), x_host=1)ForceHttps is for local sharing. Remove both wrappers when you deploy, and configure your real proxy instead. Restart flask run after you edit the file.
Check it works
Add a route that prints the URL Flask generates.
from flask import url_for
@app.get("/where")
def where():
return url_for("where", _external=True)Open https://my-app.hrzn.run/where. On the first visit Horizon shows a Before you continue page. Select Continue to site. The page prints https://my-app.hrzn.run/where. The Horizon terminal prints:
GET 200 /whereTroubleshooting
Links point at localhost:5000
ProxyFix isn't active, or x_host=1 is missing. Check that app.wsgi_app is wrapped, and restart flask run.
Links start with http:// instead of https://
The ForceHttps wrapper is missing, or ProxyFix wraps it from the inside. Wrap in this order: ProxyFix(ForceHttps(app.wsgi_app), x_host=1).
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that
flask runlistens on port 5000, the port in yourhrzn tunnelcommand.
Next steps
- Read Flask's guide to running behind a proxy.
- Share a different stack: FastAPI or Django.
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 a FastAPI app
Share a FastAPI app with a Horizon tunnel, open the /docs Swagger UI on the public URL, and fix redirects that point at localhost.