Ferchau Gupta
06/25/2026, 6:29 PMMarvin
06/25/2026, 6:29 PMMarvin
06/25/2026, 6:31 PMPREFECT_UI_SERVE_BASE=/prefect-ui
And usually also:
- PREFECT_UI_API_URL=<external API URL the browser should call>
For example, if users access Prefect at:
- UI: <https://my-app.com/prefect-ui>
- API: <https://my-app.com/api>
then set:
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/api>
What this does
- PREFECT_UI_SERVE_BASE tells Prefect’s UI to serve under /prefect-ui
- PREFECT_UI_API_URL tells the frontend where to send API requests
So yes, serving the self-hosted dashboard behind Traefik at a subpath is supported in 2.20.
Important caveat
Your reverse proxy needs to be configured consistently with that setup. In practice, you usually either:
1. Preserve the /prefect-ui prefix when forwarding to Prefect, or
2. Strip the prefix only if your upstream routing and app config still line up correctly
Because Prefect is explicitly configured with /prefect-ui, the safest pattern is to make Traefik route that path to the Prefect server in a way that matches the configured base path.
A typical environment example
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/api>
If your API is also exposed under the same prefix instead, you’d set the API URL accordingly.
Things to test after setup
- <https://my-app.com/prefect-ui>
- <https://my-app.com/prefect-ui/ui-settings>
That ui-settings endpoint should return JSON including the API URL the UI will use.
One more note
Prefect 2.x is in the older generation, and if you later move to Prefect 3.x, deployment/reverse-proxy guidance changes a bit. But for 2.20 specifically, subpath UI hosting is supported.
If you want, I can also sketch a Traefik example config for this setup.Ferchau Gupta
06/25/2026, 7:13 PMMarvin
06/25/2026, 7:13 PMMarvin
06/25/2026, 7:13 PM<https://my-app.com/prefect-ui>
I’ll show the cleanest pattern first.
Recommended approach
Expose the Prefect server behind Traefik and configure Prefect with:
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/api>
If you want the API under the same subpath family instead, use something like:
bash
PREFECT_UI_API_URL=<https://my-app.com/prefect-api>
depending on your routing.
---
Option 1: UI at /prefect-ui, API at /api
This is a common setup if your app already owns the main domain.
Prefect container env
yaml
environment:
PREFECT_UI_SERVE_BASE: /prefect-ui
PREFECT_UI_API_URL: <https://my-app.com/api>
Docker Compose + Traefik example
yaml
services:
prefect:
image: prefecthq/prefect:2.20.0-python3.11
command: prefect server start --host 0.0.0.0 --port 4200
environment:
PREFECT_UI_SERVE_BASE: /prefect-ui
PREFECT_UI_API_URL: <https://my-app.com/api>
labels:
- traefik.enable=true
# Route UI traffic
- traefik.http.routers.prefect-ui.rule=Host(`<http://my-app.com|my-app.com>`) && PathPrefix(`/prefect-ui`)
- traefik.http.routers.prefect-ui.entrypoints=websecure
- traefik.http.routers.prefect-ui.tls=true
- traefik.http.routers.prefect-ui.service=prefect
# Prefect server listens on 4200
- traefik.http.services.prefect.loadbalancer.server.port=4200
networks:
- traefik
networks:
traefik:
external: true
In this model:
- Browser visits <https://my-app.com/prefect-ui>
- Prefect serves the UI under /prefect-ui
- UI calls API at <https://my-app.com/api>
But this only works if something routes /api to the Prefect API. If /api is already your app’s API, don’t use this unless your app proxies those Prefect API calls onward.
---
Option 2: UI at /prefect-ui, API at /prefect-api
This is often simpler and avoids conflicts with your application’s own /api.
Prefect env
yaml
environment:
PREFECT_UI_SERVE_BASE: /prefect-ui
PREFECT_UI_API_URL: <https://my-app.com/prefect-api>
Traefik labels
yaml
services:
prefect:
image: prefecthq/prefect:2.20.0-python3.11
command: prefect server start --host 0.0.0.0 --port 4200
environment:
PREFECT_UI_SERVE_BASE: /prefect-ui
PREFECT_UI_API_URL: <https://my-app.com/prefect-api>
labels:
- traefik.enable=true
# UI route
- traefik.http.routers.prefect-ui.rule=Host(`<http://my-app.com|my-app.com>`) && PathPrefix(`/prefect-ui`)
- traefik.http.routers.prefect-ui.entrypoints=websecure
- traefik.http.routers.prefect-ui.tls=true
- traefik.http.routers.prefect-ui.service=prefect
# API route
- traefik.http.routers.prefect-api.rule=Host(`<http://my-app.com|my-app.com>`) && PathPrefix(`/prefect-api`)
- traefik.http.routers.prefect-api.entrypoints=websecure
- traefik.http.routers.prefect-api.tls=true
- traefik.http.routers.prefect-api.service=prefect
- traefik.http.services.prefect.loadbalancer.server.port=4200
networks:
- traefik
networks:
traefik:
external: true
This gives you:
- UI: <https://my-app.com/prefect-ui>
- API: <https://my-app.com/prefect-api>
That’s usually the least confusing option.
---
Do you need StripPrefix?
Usually, I would avoid StripPrefix here unless you’ve tested it carefully.
Since Prefect is explicitly configured with:
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
it’s best if requests arrive in a way consistent with that external path.
So prefer:
- `PathPrefix(/prefect-ui)`
- no stripping
If you strip the prefix, the app may no longer see paths the way its UI config expects.
---
Minimal static Traefik file example
If you prefer file-based Traefik config instead of labels:
```yaml
http:
routers:
prefect-ui:
rule: "Host(<http://my-app.com|my-app.com>) && PathPrefix(/prefect-ui)"
service: prefect
entryPoints:
- websecureMarvin
06/25/2026, 7:13 PM<http://my-app.com|my-app.com>) && PathPrefix(/prefect-api)"
service: prefect
entryPoints:
- websecure
tls: {}
services:
prefect:
loadBalancer:
servers:
- url: "http://prefect:4200"
And then set:
```bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/prefect-api>
---
How to verify
After starting, test:
bash
curl -i <https://my-app.com/prefect-ui/ui-settings>
You should see JSON containing your configured API URL.
Then open:
text
<https://my-app.com/prefect-ui>
If the UI loads but API calls fail, the usual cause is:
- PREFECT_UI_API_URL points to the wrong public URL, or
- Traefik is not routing the API path to Prefect
---
CLI note
I verified prefect server start exists in 2.20 and supports the expected flags:
- --host
- --port
- --ui
So this command is valid:
bash
prefect server start --host 0.0.0.0 --port 4200
---
Useful links
- Prefect repo: github.com/PrefectHQ/prefect
- Prefect 2 docs landing page: docs-2.prefect.io
If you want, I can also give you a full docker-compose example with Traefik + Prefect + Postgres for copy/paste.Ferchau Gupta
06/26/2026, 6:22 AMMarvin
06/26/2026, 6:22 AMMarvin
06/26/2026, 6:24 AMPREFECT_UI_SERVE_BASE and PREFECT_UI_API_URL
- Prefect 3.x: those still work, but Prefect 3 also has the newer canonical PREFECT_SERVER_UI_* names and better internal handling for UI routing
So for your use case, the concept is not fundamentally different:
- UI can still live at /prefect-ui
- reverse proxy can still expose Prefect under a subpath
- you still need to make sure the UI knows the correct public API URL
---
In Prefect 2.20
Typical settings:
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/prefect-api>
---
In Prefect 3.x
Preferred settings are:
bash
PREFECT_SERVER_UI_SERVE_BASE=/prefect-ui
PREFECT_SERVER_UI_API_URL=<https://my-app.com/prefect-api>
But for compatibility, the old names still work too:
bash
PREFECT_UI_SERVE_BASE=/prefect-ui
PREFECT_UI_API_URL=<https://my-app.com/prefect-api>
So if you upgrade from 2.20 to 3.x, your existing env vars will likely still work.
---
What improved in Prefect 3
1. New canonical setting names
Prefect 3 organizes server settings under PREFECT_SERVER_*, so UI settings are more explicitly server-related.
2. Better default API URL behavior
In 2.x, the UI often fell back to /api.
In 3.x, API URL resolution is smarter and can derive from other configured API settings.
3. Better path handling internally
Prefect 3 has more robust normalization of subpaths and routing behavior.
4. TOML-based config support
In 3.x, you can also manage this in config files more cleanly, not just env vars.
---
What does not really change
Your Traefik strategy is basically the same:
- route `PathPrefix(/prefect-ui)` to Prefect
- set Prefect’s UI serve base to /prefect-ui
- set the API URL to the externally reachable API path
- avoid unnecessary prefix stripping unless you’ve tested it carefully
---
Example for Prefect 3
yaml
environment:
PREFECT_SERVER_UI_SERVE_BASE: /prefect-ui
PREFECT_SERVER_UI_API_URL: <https://my-app.com/prefect-api>
And Traefik routing still looks conceptually like:
yaml
- traefik.http.routers.prefect-ui.rule=Host(`<http://my-app.com|my-app.com>`) && PathPrefix(`/prefect-ui`)
- traefik.http.routers.prefect-api.rule=Host(`<http://my-app.com|my-app.com>`) && PathPrefix(`/prefect-api`)
---
CLI note
prefect server start still exists in Prefect 3.
I verified the command family in your earlier 2.x context, and in 3.x the server-start workflow remains the right approach for self-hosting.
---
One important migration-related note
If you move from 2.x to 3.x, the biggest differences are not reverse proxying — they’re more around deployments and workers. For example, in 3.x:
- prefect deployment build is removed
- use prefect deploy or flow.from_source(...).deploy(...) instead
That’s unrelated to UI proxying, but it’s usually the bigger upgrade gotcha.
---
Bottom line
For your exact use case, the difference is mostly:
- Prefect 2.20: PREFECT_UI_*
- Prefect 3.x: prefer PREFECT_SERVER_UI_*, but old names still work
and the reverse-proxy pattern with Traefik stays almost the same.
If you want, I can next give you a side-by-side Traefik + docker-compose example for Prefect 2.20 vs Prefect 3 so you can compare them directly.