Sounds like you’ve put Nginx basic auth in front of Prefect and the UI can’t reach /api once auth is enabled. Two common culprits:
1) The UI is calling a different-origin API (because PREFECT_UI_API_URL isn’t set to your proxy URL), so the browser’s CORS preflight hits your Nginx auth challenge and dies.
2) Nginx is not forwarding the Authorization header or websocket upgrades to the upstream, so some API/UI calls break.
Quickest path to “it just works” is to keep the UI and API on the same origin at the proxy and make sure the UI knows that. Here’s a working example.
Nginx config (single host, basic auth, websockets, and Authorization forwarding)
- Put both UI and API behind the same host (avoid CORS entirely).
- Forward the Authorization header upstream.
- Support websocket upgrades.
Example:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name
myprefect.example.com;
# Basic auth at the proxy
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
# If 'prefect server start' serves both UI and /api on 4200 (default),
# a single location is enough:
location / {
proxy_pass
http://127.0.0.1:4200;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Websocket/SSE upgrades
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Forward client Authorization to upstream (important for CLI/agents)
proxy_set_header Authorization $http_authorization;
}
# If your API is actually on another port, split it explicitly:
# location /api/ {
# proxy_pass
http://127.0.0.1:8100;
# proxy_http_version 1.1;
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection $connection_upgrade;
# proxy_set_header Authorization $http_authorization;
# }
}
Prefect config to point the UI and clients at the proxy
- Set the UI’s API URL to your proxy’s /api to keep it same-origin.
- Point CLI/agents to the same URL.
Environment variables:
export PREFECT_UI_API_URL="
https://myprefect.example.com/api"
export PREFECT_API_URL="
https://myprefect.example.com/api"
Or via CLI (verified command exists):
prefect config set ui.api_url="
https://myprefect.example.com/api"
If you must run UI and API on different origins
- You’ll need CORS on the Prefect API, including Authorization in allowed headers; otherwise the browser’s preflight will fail against your Basic Auth challenge.
- In Prefect 3.x, configure CORS on the server API, e.g. set:
- server.api.cors_allowed_origins to the UI origin(s)
- server.api.cors_allowed_headers to include Authorization, Content-Type
- server.api.cors_allowed_methods as needed
See:
https://docs-3.prefect.io/v3/advanced/security-settings
Why this fixes your issue
- With auth on the proxy and UI+API on the same origin, the browser includes Basic credentials for all requests after the first challenge. No CORS preflights get blocked by a 401.
- Forwarding Authorization ensures upstream sees any credentials your Prefect clients send (e.g., API key auth or server basic auth).
- Websocket/SSE headers prevent log streams and live updates from mysteriously failing.
Known gotcha
- If you also enable Prefect’s own basic auth (PREFECT_SERVER_API_AUTH_STRING), the UI/CLI behavior can get fussy. There’s an open thread about Basic Auth quirks:
https://github.com/PrefectHQ/prefect/issues/16555
If this still misbehaves, please share:
- How you’re running Prefect Server (prefect server start, Docker, Helm) and which ports the UI and API are on.
- Your current Nginx config.