<@ULVA73B9P> my self hosted prefect lives on host ...
# ask-marvin
o
@Marvin my self hosted prefect lives on host in docker compose with services and so on, now i deployed a prefect kubernetes image to k8s and tried to start the pod, it is ready but it says: Unable to connect to 'ws://myprivatedomain:4200/api/events/in'. Please check your network settings to ensure websocket connections to the API are allowed. Otherwise event data (including task run data) may be lost. Reason: Unable to authenticate to the event stream. Please ensure the provided auth_token you are using is valid for this environment. . Set PREFECT_DEBUG_MODE=1 to see the full error. 112556.426 | ERROR | kopf.activities.startup - Activity 'initialize_clients' failed with an exception and will try again in 60 seconds: Unable to authenticate to the event stream. Please ensure the provided auth_token you are using is valid for this environment. Traceback (most recent call last): File "/usr/local/lib/python3.13/site-packages/prefect/events/clients.py", line 374, in _auth_handshake message: Dict[str, Any] = orjson.loads(await self._websocket.recv()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.13/site-packages/websockets/asyncio/connection.py", line 324, in recv raise self.protocol.close_exc from self.recv_exc websockets.exceptions.ConnectionClosedError: no close frame received or sent The above exception was the direct cause of the following exception: what must i do ?!?
m
thought for 160.4 seconds
It’s an auth mismatch on the events WebSocket. In Prefect 3 self‑hosted, the events stream at
/api/events/in
uses a shared auth string when the server is configured with it. Your Kubernetes pod (operator/worker) must send the same value to authenticate. What to do (assuming Prefect 3.x): 1) Check the server’s auth setting in your docker-compose - On the container that runs the API (and services), verify whether
PREFECT_SERVER_API_AUTH_STRING
is set and note its exact value. - If it is set, every external client must use the same value. If you don’t want auth, remove it from the server (and restart), but using auth is recommended. 2) Configure the Kubernetes pod with the matching client env vars - Set
PREFECT_API_URL
to your API base URL (including
/api
). - Set
PREFECT_API_AUTH_STRING
to exactly the same value as
PREFECT_SERVER_API_AUTH_STRING
. Example (with a Secret + env refs):
Copy code
apiVersion: v1
kind: Secret
metadata:
  name: prefect-credentials
type: Opaque
stringData:
  PREFECT_API_URL: <http://myprivatedomain:4200/api>
  PREFECT_API_AUTH_STRING: "admin:mypassword"   # must match the server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prefect-operator-or-worker
spec:
  template:
    spec:
      containers:
        - name: prefect
          image: prefecthq/prefect:3-latest
          env:
            - name: PREFECT_API_URL
              valueFrom:
                secretKeyRef:
                  name: prefect-credentials
                  key: PREFECT_API_URL
            - name: PREFECT_API_AUTH_STRING
              valueFrom:
                secretKeyRef:
                  name: prefect-credentials
                  key: PREFECT_API_AUTH_STRING
          # command/args as appropriate for your operator or worker
Notes and checks: - The error shows
<ws://myprivatedomain:4200/api/events/in>
, which is derived from
PREFECT_API_URL
. That’s good. If your endpoint is HTTPS, set
PREFECT_API_URL=<https://myprivatedomain:4200/api>
so it upgrades to
wss://.../events/in
. - Make sure port 4200 is reachable from the cluster. - Do not set
PREFECT_API_KEY
when self‑hosting; that’s for Prefect Cloud and won’t work here. - If you want more detail while testing, set
PREFECT_DEBUG_MODE=1
in the pod to see the full auth handshake messages. If you’re using the Prefect Kubernetes Operator (the
kopf
log suggests you are), add these env vars to the operator’s Deployment (or via your Helm values) so the operator can authenticate to your self‑hosted API. If you’re running a Kubernetes Worker instead, add the same env vars to that worker’s Deployment. If you can share: - whether this pod is the Prefect Kubernetes Operator or a Worker - the
PREFECT_SERVER_API_AUTH_STRING
value configured on your docker-compose server (you can redact it, just confirm whether it’s set) - the current
PREFECT_API_URL
and
PREFECT_API_AUTH_STRING
in the k8s pod …I can tailor the exact patch/Helm values you need.
o
@Marvin what if i do not want events stream? can i disable it in the kubernetes pod?
m
thought for 158.6 seconds
Yes. For the Prefect Kubernetes Operator/Observer, you can disable its events connection entirely. Key options: - Stop the observer (prevents any events client from being created) - Set this on the operator/worker pod: -
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
- Keep the observer running but stop pod event replication - This still initializes the events client, but won’t emit pod/job events: -
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_REPLICATE_POD_EVENTS=false
If your goal is to avoid the WebSocket connection (and the auth error), use the first option: set
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
on that pod and restart it. That prevents the operator from creating the events client at startup, so it won’t try to connect to
/api/events/in
. If you’d like, share whether this pod is the operator or a worker, and I can provide a snippet (Deployment/Helm values) showing exactly where to add the env var.
o
@Marvin when i set
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
Worker 'KubernetesWorker 772ab2ea-fd5f-4961-a04a-d91a7a4c5aff' started! 115807.009 | WARNING | prefect.events.clients - Unable to connect to 'ws://bre-kas-dev06:4200/api/events/in'. Please check your network settings to ensure websocket connections to the API are allowed. Otherwise event data (including task run data) may be lost. Reason: Unable to authenticate to the event stream. Please ensure the provided auth_token you are using is valid for this environment. . Set PREFECT_DEBUG_MODE=1 to see the full error. 115807.010 | ERROR | GlobalEventLoopThread | prefect._internal.concurrency - Service 'EventsWorker' failed with 1 pending items.
m
thought for 527.4 seconds
You’re seeing that because the Kubernetes observer is separate from Prefect’s global EventsWorker. Disabling the observer stops K8s pod event replication, but the EventsWorker still starts inside the worker process and tries to open the
/api/events/in
websocket whenever
PREFECT_API_URL
is set. What you can do: Option A (recommended if you just want the noise gone) - Supply the correct auth for events so the connection succeeds: - On your worker/operator pod, set
PREFECT_API_AUTH_STRING
to exactly match the server’s
PREFECT_SERVER_API_AUTH_STRING
. - This keeps events enabled but stops the warnings/errors. Option B (server-side, no auth) - Remove
PREFECT_SERVER_API_AUTH_STRING
from your self-hosted server/services so the WS does not require auth. Clients will connect without errors. Only do this if that’s acceptable for your environment. Option C (truly disable events from this pod) - There is no single env var to disable the EventsWorker today; it will run as long as
PREFECT_API_URL
is set (and you need that for the worker to function). - If you must disable it, use a small customization to force a NullEventsClient before the CLI starts. One robust approach is a sitecustomize shim in your worker image: 1) Build a tiny layer that adds sitecustomize.py:
Copy code
# sitecustomize.py
  try:
      from prefect.events.worker import EventsWorker
      from prefect.events.clients import NullEventsClient
      EventsWorker.set_client_override(NullEventsClient)
  except Exception:
      pass
2) Put this file on PYTHONPATH in your worker image (Python auto-imports sitecustomize on startup). Then run the normal
prefect worker start ...
command. This will suppress all event emission (no websocket attempts). Why your current change didn’t help -
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
only disables the K8s observer. The EventsWorker is independent and still tries to send events because your pod has
PREFECT_API_URL
set. Source refs - Events client/WebSocket auth handshake: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/events/clients.py - EventsWorker decision logic: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/events/worker.py - K8s observer init (shows it’s separate from EventsWorker): https://github.com/PrefectHQ/prefect/blob/main/src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py - K8s observer settings (enabled/replicate_pod_events): https://github.com/PrefectHQ/prefect/blob/main/src/integrations/prefect-kubernetes/prefect_kubernetes/settings.py If you want, I can share a quick patch to your Deployment/Helm values to add
PREFECT_API_AUTH_STRING
, or a minimal Dockerfile snippet for the sitecustomize approach.
o
@Marvin so now i configured the
PREFECT_API_AUTH_STRING
and teh
PREFECT_SERVER_API_AUTH_STRING
and they perfectly match. but i still get the same error as the pod still tries to use an auth token albeit the fact we are self hosted sudo kubectl -n um-demo logs pod/prefect-worker-5c6f466477-xwq7m 123444.521 | WARNING | prefect.events.clients - Unable to connect to 'ws://another_domain:4300/api/events/in'. Please check your network settings to ensure websocket connections to the API are allowed. Otherwise event data (including task run data) may be lost. Reason: Unable to authenticate to the event stream. Please ensure the provided auth_token you are using is valid for this environment. . Set PREFECT_DEBUG_MODE=1 to see the full error. 123444.524 | ERROR | kopf.activities.startup - Activity 'initialize_clients' failed with an exception and will try again in 60 seconds: Unable to authenticate to the event stream. Please ensure the provided auth_token you are using is valid for this environment. Traceback (most recent call last): File "/usr/local/lib/python3.13/site-packages/prefect/events/clients.py", line 374, in _auth_handshake message: Dict[str, Any] = orjson.loads(await self._websocket.recv()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.13/site-packages/websockets/asyncio/connection.py", line 324, in recv raise self.protocol.close_exc from self.recv_exc websockets.exceptions.ConnectionClosedError: no close frame received or sent The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/usr/local/lib/python3.13/site-packages/kopf/_core/actions/execution.py", line 254, in execute_handler_once result = await invoke_handler( ^^^^^^^^^^^^^^^^^^^^^ ...<9 lines>... ) ^ File "/usr/local/lib/python3.13/site-packages/kopf/_core/actions/execution.py", line 400, in invoke_handler result = await invocation.invoke( ^^^^^^^^^^^^^^^^^^^^^^^^ ...<9 lines>... ) ^ File "/usr/local/lib/python3.13/site-packages/kopf/_core/actions/invocation.py", line 115, in invoke result = await fn(**kwargs) # type: ignore ^^^^^^^^^^^^^^^^^^
m
thought for 115.1 seconds
Thanks for the details — the fact that you set both
PREFECT_API_AUTH_STRING
(client) and
PREFECT_SERVER_API_AUTH_STRING
(server) to the same value is exactly right. The “auth_token” wording in the log is generic; self‑hosted still requires that same shared token when the server is configured with it. Given you’re still seeing “Unable to authenticate,” the most common causes are: 1) The pod env isn’t what we think - Verify the worker pod actually has the values you expect (no typos/whitespace):
Copy code
kubectl -n um-demo exec -it <pod-name> -- sh -lc 'env | sort | grep "^PREFECT_"'
- Double‑check `PREFECT_API_URL`: - Must include the
/api
suffix - Must use the correct scheme/port for how you expose the API - If you terminate TLS at a proxy and access via https externally, set
PREFECT_API_URL=<https://another_domain:4300/api>
(this makes the client use wss://). - If you truly use plain http,
http://...
is fine. 2) Reverse proxy/ingress is stripping the WebSocket subprotocol - When
PREFECT_SERVER_API_AUTH_STRING
is set, the server requires the WebSocket subprotocol
prefect
. If a proxy drops
Sec-WebSocket-Protocol: prefect
, the server will close the connection before the auth handshake completes — the client will log an auth failure and “no close frame”. - If you’re using NGINX or another L7 in front of Prefect, ensure it forwards WebSocket headers and the subprotocol. For NGINX, something like:
Copy code
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header Sec-WebSocket-Protocol $http_sec_websocket_protocol;
- For NGINX Ingress Controller, ensure it’s up to date (it handles ws upgrades automatically) and, if you use custom headers via annotations, preserve
Sec-WebSocket-Protocol
. 3) Typo/whitespace in the token on either side - The token comparison is exact. If you’re storing it in a Secret, confirm no stray whitespace:
Copy code
kubectl -n <ns> get secret <secret> -o jsonpath='{.data.PREFECT_API_AUTH_STRING}' | base64 -d | sed -n l
You should see the value with line-end marker
$
and no extra spaces or quotes. 4) The API URL is not actually the same endpoint your server is bound to - Your logs show
<ws://another_domain:4300/api/events/in>
. Make sure the server is reachable there and that the same container running the API has
PREFECT_SERVER_API_AUTH_STRING
set to the value you expect. If you split “API” and “services” into separate containers, the auth string must be present in the API container specifically. 5) TLS mismatch - If you access via HTTPS externally, use
https://...
in
PREFECT_API_URL
(so the client uses wss://). If you use a self‑signed cert, set:
Copy code
PREFECT_API_TLS_INSECURE_SKIP_VERIFY=true
Quick ways to pinpoint the issue A) Turn on Prefect debug logs in the pod
Copy code
PREFECT_DEBUG_MODE=1
This will print the handshake step and typically shows whether the server closed due to a missing subprotocol vs an invalid token. B) Test the WebSocket by hand (bypassing your worker) - From a machine that can reach the API, use wscat/websocat and include the subprotocol:
Copy code
wscat -c <ws://another_domain:4300/api/events/in> -s prefect
# then paste:
{"type":"auth","token":"<exact PREFECT_SERVER_API_AUTH_STRING value>"}
You should receive:
Copy code
{
  "type": "auth_success"
}
If this succeeds directly but your worker still fails, the problem is your proxy/ingress path from the cluster to the API (likely the subprotocol header being dropped). If this fails the same way, the problem is server-side config or the token value itself. C) Check server logs at the time of connection attempts - The Prefect API process will log if it rejects the socket due to protocol/auth; that can confirm whether it saw the subprotocol and what it did next. If you don’t actually care about events from this pod and just want to suppress the warnings, two safe alternatives:
- Remove
PREFECT_SERVER_API_AUTH_STRING
from the API container (disables WS auth requirement). Clients will connect without auth. - Force-disable event emission in the worker image using a tiny sitecustomize shim:
Copy code
# sitecustomize.py on PYTHONPATH in the worker image
from prefect.events.worker import EventsWorker
from prefect.events.clients import NullEventsClient
EventsWorker.set_client_override(NullEventsClient)
If you can share: - Your current
PREFECT_API_URL
value - Whether you’re going through an ingress/proxy (and which one) - The env dump from the worker pod (
env | grep PREFECT_
) …I can spot the issue and provide an exact ingress snippet or config patch. Useful references: - Server WS auth and subprotocol requirement: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/server/utilities/subscriptions.py - Client auth handshake: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/events/clients.py - Events decision logic in worker: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/events/worker.py