Deployment
Running the admin behind a reverse proxy changes two things you can otherwise ignore in local development: the secret key must be stable across worker processes, and generated URLs must reflect HTTPS even though your app only ever sees plain HTTP from the proxy.
Framework-specific deployment guides
This page covers only what's specific to Admin. For the underlying application and ASGI server, see:
- FastAPI: FastAPI Deployment Documentation
- Uvicorn: Uvicorn Deployment Documentation
import os
from sqlalchemy import create_engine
from starlette.applications import Starlette
from starlette_admin.contrib.sqla import Admin, ModelView
from myapp.models import Post
engine = create_engine(os.environ["DATABASE_URL"])
app = Starlette()
admin = Admin(
engine,
title="My Admin",
base_url="/admin",
secret_key=os.environ["ADMIN_SECRET_KEY"],
)
admin.add_view(ModelView(Post))
admin.mount_to(app)
Secret key
Admin generates a random secret_key at startup when you don't pass one. That's fine for a single local process, but every worker process generates its own key independently, so a CSRF token signed by one worker won't validate on another. Set secret_key from an environment variable before you run more than one process. See Security for the full multi-worker failure mode and how the key is used.
Reverse proxy and HTTPS
Admin builds every internal link (list pages, edit forms, exports, the /static mount, uploaded files served through /_files/...) by calling request.url_for(...), which derives its scheme from the incoming request. When a proxy such as Nginx, Caddy, or Traefik terminates TLS and forwards plain HTTP to your app, Starlette can't tell that the original request was HTTPS unless the proxy sends X-Forwarded-Proto and your ASGI server trusts it. Leave this unconfigured and generated links downgrade to http://, which browsers block or rewrite when the page itself loaded over HTTPS.
Fix this in two places:
-
The proxy forwards the header:
-
Uvicorn trusts it, via
--proxy-headersplus--forwarded-allow-ipsnaming the proxy's IP (or'*'if the proxy is only reachable from inside your network):
Gunicorn's uvicorn.workers.UvicornWorker reads the same two settings from --forwarded-allow-ips. See Uvicorn's deployment docs for the full set of process-manager options.
Warning
--forwarded-allow-ips='*' trusts forwarded headers from any source. Only use it when the app is unreachable except through your proxy, for example when it's bound to a private network or a Unix socket. If the app is directly reachable, restrict the setting to the proxy's actual IP. Otherwise a client can spoof X-Forwarded-Proto and X-Forwarded-For directly.
Static assets
The admin's CSS and JS ship inside the starlette_admin package, and Admin serves them itself through a /static mount under base_url rather than from a separate static host. static_dir only lets you override individual files (see Templates), it doesn't move asset serving off your app process. There's no built-in option to serve these from a CDN. If you need one, add a cache-friendly Cache-Control rule for {base_url}/static/* at the proxy layer instead.
Uploaded files are different: LocalStorage serves them through the app too (/_files/{storage}/{path}, so auth middleware still applies), but S3Storage and other remote backends can serve directly from the provider. See File Storage.
What's next
- Security: The
secret_keymulti-worker footgun in full, plus everything else the built-in protections do and don't cover. - Authentication: Gate access to the admin before it's reachable in production.
- File Storage: Configuring
S3Storageand other remote backends.