Luis Barragan
06/05/2026, 9:17 AMMarvin
06/05/2026, 9:18 AMMarvin
06/05/2026, 9:20 AMlocalhost:4200 as its API URL, so when you open the forwarded UI on localhost:14200, the browser then makes API calls to your own machine’s localhost:4200.
Fix: configure the remote Prefect server to advertise the correct API URL to the UI.
What to set on the remote server
Set:
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
Then restart the remote Prefect server.
If you’re using Docker Compose, that would look something like:
yaml
environment:
PREFECT_UI_API_URL: <http://localhost:14200/api>
Why this works:
- you open the UI at <http://localhost:14200>
- that request is SSH-forwarded to the remote server
- the UI asks the server for /ui-settings
- the remote server responds with the configured api_url
- if that api_url is <http://localhost:14200/api>, your browser will send API requests through the same SSH tunnel instead of to your local Prefect instance on 4200
Important note
- localhost in PREFECT_UI_API_URL is interpreted by the browser, not the remote machine
- so in your case, <http://localhost:14200/api> is exactly what you want
How to verify
From your local machine:
bash
curl <http://localhost:14200/ui-settings>
You should see JSON with:
json
{
"api_url": "<http://localhost:14200/api>"
}
If it still says <http://localhost:4200/api>, the remote server is still advertising the wrong value.
If you use a config file instead of env vars
In prefect.toml on the remote server:
toml
[server.ui]
api_url = "<http://localhost:14200/api>"
Then restart the server.
Related settings
- PREFECT_UI_API_URL / `PREFECT_SERVER_UI_API_URL`: explicit UI API URL
- if unset, Prefect may fall back to PREFECT_API_URL or server host/port defaults, which often end up as <http://localhost:4200/api>
Docs/settings references:
- Prefect server guide
- Settings reference
If you want, I can help you write the exact docker-compose.yml change for your current Prefect server container.Luis Barragan
06/05/2026, 9:26 AMPREFECT_API_URL=<http://127.0.0.1:4200/api>
Could I keep it the same and just add a new
PREFECT_UI_API_URL=<http://localhost:14200/api>
? Would it still work?Marvin
06/05/2026, 9:26 AMMarvin
06/05/2026, 9:27 AMPREFECT_API_URL is used by Prefect clients/workers/CLI
- PREFECT_UI_API_URL is what the browser UI uses
So you can keep:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
and add:
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
That means:
- processes running on the remote server still talk to the API at 127.0.0.1:4200
- your browser visiting the forwarded UI at localhost:14200 will call the API through the SSH tunnel at localhost:14200/api
That’s actually a good setup for your use case.
A couple of cautions:
1. Don’t include angle brackets in the actual .env
Use:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
not:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
2. Set PREFECT_UI_API_URL on the remote server container/process
It needs to be available to the Prefect server that serves the UI.
3. Restart the server after changing it
4. Verify with:
bash
curl <http://localhost:14200/ui-settings>
You should see:
json
{
"api_url": "<http://localhost:14200/api>"
}
If you want, paste your Compose service/env block and I’ll sanity-check exactly where those vars should go.Luis Barragan
06/05/2026, 9:38 AMLocalForward 14200 localhost:14200
When I go to http://localhost:14200/dashboard I see a gray screen. This: http://localhost:14200/ui-settings returns a ERR_CONNECTION_RESET.Marvin
06/05/2026, 9:38 AMMarvin
06/05/2026, 9:38 AMLocalForward 14200 localhost:14200 means:
- local port 14200
- forwarded to remote localhost:14200
But your Prefect server is listening on remote 4200, not 14200.
So the tunnel is now pointing at the wrong remote port, which explains:
- gray screen
- /ui-settings connection reset
What you want instead
Keep the SSH forward as:
ssh-config
LocalForward 14200 localhost:4200
That means:
- local localhost:14200 -> remote localhost:4200
Then on the remote Prefect server, set:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
This is correct because:
- remote server processes use 127.0.0.1:4200
- your local browser uses localhost:14200
Why PREFECT_UI_API_URL=<http://localhost:14200/api> is okay
Even though it’s configured on the remote server, that value is consumed by the browser, so localhost:14200 refers to your local machine, which is exactly where the SSH tunnel is listening.
Recommended setup
SSH config:
ssh-config
Host my-prefect-server
HostName <http://your.server.com|your.server.com>
User your-user
LocalForward 14200 localhost:4200
Remote `.env`:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
Then restart the remote Prefect server container/process.
Checks to run
1. On the remote machine, confirm Prefect is actually listening on 4200:
bash
curl <http://127.0.0.1:4200/api/health>
2. On your local machine, after reconnecting SSH:
bash
curl <http://localhost:14200/ui-settings>
You should get JSON back.
3. Also test:
bash
curl <http://localhost:14200/api/health>
If that works, the tunnel is good.
One subtle issue to watch for
If the UI loads from localhost:14200 and then calls <http://localhost:14200/api>, that should work fine through the same tunnel. If it still doesn’t, the server may not have picked up PREFECT_UI_API_URL, or there may be a browser cache issue. A hard refresh/incognito window can help.
If you want, I can help you debug this step-by-step from:
- your SSH config
- your docker compose ports/env
- the output of curl <http://127.0.0.1:4200/api/health> on the remote
- the output of curl <http://localhost:14200/ui-settings> locallyLuis Barragan
06/05/2026, 9:41 AMMarvin
06/05/2026, 9:41 AMMarvin
06/05/2026, 9:41 AM127.0.0.1:4200 there is the remote server’s loopback, because you ran the curl on the remote host. So that output alone does not mean it’s pointing at your laptop.
The real issue is that your server has not picked up PREFECT_UI_API_URL, because /ui-settings is still advertising:
json
"api_url":"<http://127.0.0.1:4200/api>"
It should instead advertise:
json
"api_url":"<http://localhost:14200/api>"
Also: your output shows angle brackets in the URL:
json
"<http://127.0.0.1:4200/api>"
Actually your pasted command/output suggests you may literally have values like:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
If so, that is wrong for .env files. The values must be plain URLs, no < >.
Use exactly:
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
Next steps
1. Fix the .env values to remove angle brackets.
2. Make sure the vars are applied to the Prefect server container/process, not just somewhere else.
3. Restart/recreate the server container, not just the worker.
For Docker Compose, usually:
bash
docker compose up -d --force-recreate prefect-server
or if service names differ, recreate the service that serves the API/UI.
4. Re-check on the remote host:
bash
curl <http://127.0.0.1:4200/ui-settings>
You want:
json
{"api_url":"<http://localhost:14200/api>", ...}
Important detail
If this is a containerized setup, editing a host .env file is only useful if that file is actually referenced by the container via:
- env_file:
- variable substitution in docker-compose.yml
- or exported into the process environment before startup
So also inspect the running container env.
For example:
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep PREFECT
You should see:
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
If you don’t, the server container never received it.
Likely diagnosis
Most likely one of these is true:
- PREFECT_UI_API_URL is not actually set in the server container
- the container wasn’t recreated after changing .env
- the .env contains invalid bracketed values like <http://...>
If you paste your docker-compose.yml service for Prefect server, I can tell you exactly where the variable needs to go.Luis Barragan
06/05/2026, 9:45 AMPREFECT_UI_API_URL is not actually set in the server container. I added to the .env file only. It should be taken because I do this on the prefect-server container yaml:
env_file:
- .envMarvin
06/05/2026, 9:45 AMMarvin
06/05/2026, 9:46 AMdocker inspect shows PREFECT_UI_API_URL is not in the running prefect-server container, then the container did not get that variable, even if env_file: .env is in the Compose YAML.
Most likely causes:
1. the container was not recreated after the .env change
2. the wrong .env file is being loaded
3. the variable is malformed in .env
4. the service you’re checking is not actually the one serving Prefect UI/API
First: make sure the .env line is exactly this
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
No quotes needed, and no angle brackets.
Then recreate the service, not just restart it:
bash
docker compose up -d --force-recreate prefect-server
A plain restart often won’t refresh env from env_file.
Then check again:
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep PREFECT_UI_API_URL
You should see:
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
If you still don’t see it, verify Compose is reading the file you think it is:
bash
docker compose config
That shows the fully rendered config. Confirm:
- the prefect-server service has env_file:
- it points to the correct path
- there isn’t another compose file overriding it
You can also inspect the rendered env section more directly with:
bash
docker compose config | sed -n '/prefect-server:/,/^[^[:space:]]/p'
Another subtle but important distinction
Docker Compose has:
- project-level .env for variable substitution in compose files
- env_file: for injecting env vars into the container
These are related but not the same thing. In your case, since you explicitly use:
yaml
env_file:
- .env
the file should be injected into the container if the service is recreated.
Quick sanity test
Temporarily add something obvious to `.env`:
bash
FOO_TEST=hello
Recreate:
bash
docker compose up -d --force-recreate prefect-server
Then inspect:
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep FOO_TEST
- if it appears, your env_file is working and the issue is specific to the Prefect var/value
- if it does not appear, the service is not using the env_file you think it is
After it is present in the container, verify:
bash
curl <http://127.0.0.1:4200/ui-settings>
Expected:
json
{"api_url":"<http://localhost:14200/api>", ...}
Then locally, with SSH forward set back to:
ssh-config
LocalForward 14200 localhost:4200
test:
bash
curl <http://localhost:14200/ui-settings>
curl <http://localhost:14200/api/health>
If you want, paste:
- the prefect-server section of your docker-compose.yml
- the exact .env lines for the two Prefect vars
- the output of docker compose config | sed -n '/prefect-server:/,/^[^[:space:]]/p'
and I’ll pinpoint what’s wrong.