Robert Szomolanyi
03/23/2026, 1:21 PMMarvin
03/23/2026, 1:21 PMMarvin
03/23/2026, 1:26 PMcurl -v $PREFECT_API_URL/health
Expect HTTP 200. If you’re using the default local server on the same machine, the typical value is:
PREFECT_API_URL=<http://127.0.0.1:4200/api>
If the worker runs in Docker and the server runs on the host, 127.0.0.1 will not work; use the host’s IP/hostname or Docker networking (e.g., host.docker.internal on macOS/Windows).
2) Check for recent server restarts or errors
- Inspect server logs around the timestamp of the failure for restarts or API errors. Avoid restarting the server while runs are active.
3) If using HTTPS to a local server or reverse proxy
- Ensure the worker trusts the certificate chain (self-signed CA bundle installed). See SSL and proxy configuration guidance:
- Configure network/SSL for Prefect
4) Turn on more logging and health checks
- Set PREFECT_LOGGING_LEVEL=DEBUG on the worker to get detailed lease renewal and client logs.
- Use worker healthchecks and connectivity checks:
- Worker healthchecks
5) Sanity-check concurrency config
- If you recently enabled deployment concurrency, temporarily remove the concurrency limit to confirm whether the failure is isolated to lease renewal vs. general API connectivity. If API connectivity is the issue, you’ll still see errors when setting run state.
Relevant docs
- Concurrency and leases: Global concurrency limits
- Deployment-level concurrency: Deployments – Concurrency limiting
- Running local server and client URL: Run a local Prefect server
- Network/SSL troubleshooting: Configure network access
- Worker overview: Workers
If you can share a bit more, I can help pinpoint the exact cause:Marvin
03/23/2026, 1:26 PMPREFECT_API_URL value on the worker?
- Did the server restart or change certificates around the time of the failure?
- Are you using HTTP or HTTPS in front of the local server?Robert Szomolanyi
03/23/2026, 2:54 PMMarvin
03/23/2026, 2:54 PMMarvin
03/23/2026, 2:56 PMwhile true; do date; curl -sk -o /dev/null -w "%{http_code}\n" $PREFECT_API_URL/health; sleep 5; done
If you observe occasional non-200 or failures right after ~5 minutes of inactivity, that confirms an idle-timeout issue.
2) Increase/align timeouts on the OpenShift route/ingress
- Bump the route’s idle/keep-alive timeouts above your schedule interval (e.g., 10–15 minutes) so the worker’s first call at flow start doesn’t hit a dead connection.
- On OpenShift, this is typically done via route annotations for HAProxy timeouts. Your platform/SRE team can set the appropriate annotations/ingress config to ensure upstream connections aren’t dropped at 5 min.
3) Ensure the worker fully trusts the TLS chain
- If you’re using a custom or internal CA on the OCP route, install the CA bundle on the worker and set:
SSL_CERT_FILE=/path/to/ca-bundle.crt
Docs: Configure network access (proxies/SSL)
4) Sanity checks on URL and networking
- Make sure PREFECT_API_URL is the externally reachable route (not a cluster-internal DNS name that intermittently fails from the worker’s network).
- Avoid IPs behind short-lived NAT mappings. Prefer stable DNS and ensure DNS TTLs aren’t too aggressive for your environment.
5) Turn on debug logs to see the exact lease error
- On the worker:
PREFECT_LOGGING_LEVEL=DEBUG
This will log the exception that caused “Concurrency lease renewal failed …” and can confirm you’re seeing a connection reset/SSL issue on the first lease call.
6) Quick diagnostic toggles (optional)
- Temporarily remove the deployment concurrency limit for a few runs to see if failures disappear. If they do, the first failing call is very likely the lease renewal. If not, you’ll probably still see the SSL error on the subsequent state-setting call — still indicating the same network/TLS issue.
7) Longer-term mitigations
- Run the worker in the same cluster/namespace as the server where possible to remove external routing.
- If allowed, test over plain HTTP temporarily to isolate TLS termination issues (only for troubleshooting, not for production).
Relevant docs
- Concurrency and leases: Global concurrency limitsMarvin
03/23/2026, 2:56 PMRobert Szomolanyi
03/24/2026, 8:45 AM"POST /api/v2/concurrency_limits/leases/7fafac45-0830-462b-a6b2-7fe7d8f43074/renew HTTP/1.1" 410 Gone
Flow is very short: < 1s.
There is no network drop or some other problems.
I am using PostregSQL database and I am not using Redis for backend services.
I am no version 3.6.3. Could it be some known bug ?Marvin
03/24/2026, 8:46 AMMarvin
03/24/2026, 8:49 AMPOST /api/v2/concurrency_limits/leases/<lease_id>/renew ... 410 Gone
is the server telling the worker “that lease no longer exists (it expired or was revoked).”
- When the worker then tries to renew a non-existent lease, the engine cancels the run to avoid over-allocating slots, hence
“Concurrency lease renewal failed - slots are no longer reserved. Terminating execution to prevent over-allocation.”
Why you’re seeing it at flow start/finish with a 5-minute schedule
- In Prefect 3.x, deployment-level concurrency uses an initial “deployment lease” created by the server before the run actually starts. That lease has a default TTL of 300 seconds (5 minutes).
- With your schedule every 5 minutes and concurrency=1, there are times when the run starts right as the initial lease lifespan is expiring. If the lease expires (and is reaped) just before the worker’s first renewal, the server returns 410 Gone. Because your flow is very short (<1s), this can look like it “finishes” and then fails when reporting its result — in reality, the background lease task hit the 410 right at start/end.
- This is not affected by PostgreSQL; leases are not stored in Postgres. If you’re not using Redis, you are on the in-memory or filesystem lease storage depending on your server config, but the core issue here is lease TTL vs. your 5-minute cadence.
Known fixes and improvements after 3.6.3
- There have been robustness fixes to concurrency leasing since 3.6.3 (including improved renewal behavior and retry logic) that reduce flakiness around lease renewal timing.
- Recommendation: upgrade both server and workers to the latest 3.6.x (or newer) to pick up these improvements. Latest notes: Release notes
How to resolve in your setup
- Increase the initial deployment lease duration above your maximum expected queueing/polling/scheduling delay so the lease does not expire just as runs begin:
- Global setting (server):
PREFECT_SERVER__CONCURRENCY__INITIAL_DEPLOYMENT_LEASE_DURATION=600 # e.g., 10 minutes
- Or set a per-deployment grace period via deployment concurrency options (if you configure deployment YAML), e.g., grace_period 600s.
Docs: Deployments – Concurrency limiting
- Keep server and worker clocks in sync (NTP). Significant clock skew can make leases appear older to the server than they are.
- Optionally, use Redis-based lease storage for strongest guarantees in production (atomic ops, no FS races):
pip install prefect-redis
PREFECT_SERVER__CONCURRENCY__LEASE_STORAGE=prefect_redis.lease_storage
- Enable debug logs on the worker to confirm the exact renewal failure:
PREFECT_LOGGING_LEVEL=DEBUG
You should see the 410 Gone tied to the first lease renewal attempt at run start.
Relevant docs
- Concurrency and leases: Global concurrency limits
- Deployment concurrency: Deployments – Concurrency limiting
- Settings reference (server, concurrency): Settings reference
If you can share your current server setting for PREFECT_SERVER__CONCURRENCY__INITIAL_DEPLOYMENT_LEASE_DURATION (or the deployment’s concurrency_options.grace_period), I can suggest exact values. Upgrading from 3.6.3 and increasing the initial lease duration (e.g., to 600–900s) typically eliminates these 410s for 5‑minute schedules.