Pierre L
12/11/2025, 3:54 PMprefect.exceptions.PrefectHTTPStatusError: Server error '500 Internal Server Error' for url '<http://prefect-server.prefectoss.svc.cluster.local:4200/api/flow_runs/dbdf008d-f9d3-42b1-be21-ef2a4b12b567>' (url can change)
I found these logs in the prefect server pod :
11:08:48.406 | ERROR | prefect.server - Encountered exception in request: Traceback (most recent call last): File "/usr/local/lib/python3.11/site-packages/asyncpg/connection.py", line 2421, in connect return await connect_utils._connect( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/asyncpg/connect_utils.py", line 1049, in _connect conn = await _connect_addr( ^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/asyncpg/connect_utils.py", line 886, in _connect_addr return await __connect_addr(params, True, *args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/asyncpg/connect_utils.py", line 931, in __connect_addr tr, pr = await connector ^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/asyncpg/connect_utils.py", line 818, in _create_ssl_connection new_tr = await loop.start_tls( ^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/asyncio/base_events.py", line 1268, in start_tls await waiter asyncio.exceptions.CancelledError
In the logs of postgres, at the same time, I have :
{"level":"info","ts":"2025-12-10T11:08:48.310120701Z","logger":"postgres","msg":"record","logging_pod":"cnpg-database-cluster-2","record":{"log_time":"2025-12-10 11:08:48.306 UTC","process_id":"386195","connection_from":"10.2.14.94:42384","session_id":"693954b9.5e493","session_line_num":"1","session_start_time":"2025-12-10 11:08:41 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"08P01","message":"SSL error: unexpected eof while reading","backend_type":"not initialized","query_id":"0"}} {"level":"info","ts":"2025-12-10T11:08:48.31023509Z","logger":"postgres","msg":"record","logging_pod":"cnpg-database-cluster-2","record":{"log_time":"2025-12-10 11:08:48.307 UTC","process_id":"386195","connection_from":"10.2.14.94:42384","session_id":"693954b9.5e493","session_line_num":"2","session_start_time":"2025-12-10 11:08:41 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"08006","message":"could not receive data from client: Connection reset by peer","backend_type":"not initialized","query_id":"0"}} {"level":"info","ts":"2025-12-10T11:08:48.310409409Z","logger":"postgres","msg":"record","logging_pod":"cnpg-database-cluster-2","record":{"log_time":"2025-12-10 11:08:48.309 UTC","process_id":"386196","connection_from":"10.2.14.94:42388","session_id":"693954b9.5e494","session_line_num":"1","session_start_time":"2025-12-10 11:08:41 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"08006","message":"could not accept SSL connection: Connection reset by peer","backend_type":"not initialized","query_id":"0"}}
The prefect-server pods have not restarted and they do not seem limited by resources. we have :
Limits: cpu: 300m memory: 600Mi
Requests: cpu: 300m memory: 600Mi
while the max cpu usage I see on grafana peaks to 82m.
My questions :
knowing that all is working most of the time, do I need to set
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
?
If not, explain if I should set these environment variables and which values I should set:
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE
PREFECT_SQLALCHEMY_POOL_SIZE
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_RECYCLE
PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUTMarvin
12/11/2025, 3:54 PMMarvin
12/11/2025, 3:57 PMstart_tls cancelled; Postgres “SSL error: unexpected EOF”). If your CNPG cluster requires or prefers TLS (very common), you should explicitly enable TLS on the Prefect server and provide the CA. That will make the handshake/verification explicit and more resilient.
- For pool settings, the defaults are generally fine; if you see intermittent issues under failover or network blips, modest tuning of recycle/timeout can help. Use the PREFECT_SERVER_DATABASE_... variables (not the generic PREFECT_SQLALCHEMY_... ones) for the server.
Why this is happening
- The Prefect server log shows asyncio cancelling loop.start_tls(...), which only occurs when the client is upgrading to TLS. Your Postgres logs at the exact moment show SSL negotiation failing/reset by peer. Together, that indicates a TLS handshake that was interrupted (e.g., brief CNPG failover, network flap, TLS secret rotation, or a proxy/load-balancer in the path).
- When Prefect’s server is configured explicitly for TLS, it constructs an SSL context and passes it to asyncpg, enforcing verification and hostname checks. Without explicit TLS config, behavior can be less predictable depending on your connection URL and CNPG policies.
Should you set PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED?
- If your CNPG exposes TLS (recommended/typical), yes — set it to true and supply the CA bundle so the client validates the server properly. This tends to reduce odd “unexpected EOF” failures during handshake.
- Also ensure the hostname you connect to matches the certificate’s CN/SAN. If using a service name that doesn’t match, you can set TLS_CHECK_HOSTNAME=false as a workaround (preferred fix is to connect using a name that matches the cert).
Suggested starting config for CNPG
Add these to your prefect-server Deployment (mount the CA secret from CNPG so the path exists):
# Connection URL (example)
PREFECT_SERVER_DATABASE_CONNECTION_URL=<postgresql+asyncpg://user:password@cnpg-cluster.namespace.svc.cluster.local:5432/prefect>
# TLS
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/var/run/secrets/cnpg/ca.crt
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CHECK_HOSTNAME=true
# If mTLS is required by your CNPG policy:
# PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CERT_FILE=/var/run/secrets/cnpg/tls.crt
# PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_KEY_FILE=/var/run/secrets/cnpg/tls.key
Pool settings — which to use and recommended values
Use the server-scoped variables below. The generic PREFECT_SQLALCHEMY_* variables are not used by the Prefect 3 server.
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE
- Default: 5
- Recommendation: Leave at 5–10 unless you’ve measured persistent pool exhaustion. With two API replicas and moderate traffic, 5–10 per pod is typical.
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_RECYCLE
- Default: 3600 seconds
- Recommendation: 600–1800 if you want to more aggressively refresh connections to avoid stale sockets across CNPG failovers/LB timeouts. A common choice is 900.
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT
- Default: 30.0 seconds
- Recommendation: Consider 60 if you see timeouts acquiring a connection during brief failovers or spikes.
- Also available: PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW (default 10)
If you use PgBouncer in front of CNPG
- Disable statement caching:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_STATEMENT_CACHE_SIZE=0
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_PREPARED_STATEMENT_CACHE_SIZE=0
Optional timeouts you may consider
- Increase connection timeout if failovers are slightly slow:
```
PREFECT_SERVER_DATABASE_CONNECTION_TIMEOUT=5.0 # default; consider 10–15 if neededMarvin
12/11/2025, 3:57 PMPREFECT_SERVER_DATABASE_* settings. The generic PREFECT_SQLALCHEMY_POOL_SIZE is not used by the server process in 3.x.
References (source-verified)
- Server DB settings schema (TLS and pooling): prefect/settings/models/server/database.py
- Async Postgres engine uses SSL context when TLS is enabled: prefect/server/database/configurations.py
- Settings overview: Prefect settings docs
A few clarifying questions to tune this further
- Is TLS required/enabled on your CNPG cluster? If yes, do you have the CA path you can mount into the Prefect server pods?
- Are you using PgBouncer/connection pooler in front of CNPG?
- Do you see CNPG failovers or pod restarts around the timestamps of these errors?
If you share those details (and your connection URL), I can suggest exact values for your environment.Pierre L
12/11/2025, 4:29 PMPREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED only useful when using mTLS ? It looks like mTLS is used inside CNPG but not directly with prefect server, is it correct ?
My connection string is: postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME
is this a working value (in most contexts) or I need to adapt ?
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/var/run/secrets/cnpg/ca.crt
- Is TLS required/enabled on your CNPG cluster ?
it is enabled. I do not know if it is required.
If yes, do you have the CA path you can mount into the Prefect server pods?
no I don't have it (for the moment)
- Are you using PgBouncer/connection pooler in front of CNPG?
no
- Do you see CNPG failovers or pod restarts around the timestamps of these errors?
no restart and no change of the primary DB, I only see the logs I sent you.Marvin
12/11/2025, 4:29 PMMarvin
12/11/2025, 4:30 PMPREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED applies to any TLS.
- Use it for standard server-auth TLS (most CNPG setups). mTLS is only needed if your CNPG policy requires client certs — in that case you’d also set ...TLS_CERT_FILE and ...TLS_KEY_FILE.
- CNPG often uses mTLS internally between its components; clients like Prefect typically use server-auth TLS only unless you’ve enforced client cert auth.
Is your connection string OK?
- Yes, this is correct: <postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME>
- For TLS, Prefect passes an SSL context to asyncpg via connect_args (not the URL), so the URL is fine as-is.
- Make sure HOST is the CNPG service name that matches the certificate’s CN/SAN (e.g., the cluster’s -rw service DNS). If it doesn’t match, you’ll need to either use the matching DNS name or set ...TLS_CHECK_HOSTNAME=false (prefer using a matching DNS name).
Is this CA path OK?
- Yes — if that’s where you mount the CNPG CA into the Prefect server pods:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/var/run/secrets/cnpg/ca.crt
- The exact path is up to you; it just needs to point at the CA file in the mounted secret.
Given your answers
- TLS is enabled on CNPG, and you’re not using PgBouncer. No failovers observed.
- I recommend enabling TLS explicitly on Prefect and mounting the CNPG CA. This tends to eliminate the “SSL error: unexpected EOF” / asyncpg start_tls cancellations you’re seeing.
What to set (minimal, standard server-auth TLS)
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/var/run/secrets/cnpg/ca.crt
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CHECK_HOSTNAME=true
- Only add these if CNPG requires client certs:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CERT_FILE=/var/run/secrets/cnpg/tls.crt
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_KEY_FILE=/var/run/secrets/cnpg/tls.key
How to mount the CA in your Deployment (example)
- Find the CNPG secret that contains `ca.crt`:
kubectl get secret -n <namespace> | grep <your-cnpg-cluster-name>
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.<http://data.ca|data.ca>\.crt}' | head
- Mount it:
volumes:
- name: cnpg-ca
secret:
secretName: <your-secret-with-ca.crt>
volumeMounts:
- name: cnpg-ca
mountPath: /var/run/secrets/cnpg
readOnly: true
Pool settings (keep defaults unless you see pressure; optional tweaks)
- Use the server-scoped variables (not the generic PREFECT_SQLALCHEMY_*):
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE (default 5) — usually fine at 5–10
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_RECYCLE (default 3600) — consider 600–1800 to refresh sockets more often
- PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT (default 30.0) — consider 60 if you hit timeouts during brief blips
- PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW (default 10)
Why this helps
- With TLS explicitly configured, Prefect builds a proper SSL context (min TLS 1.2, CERT_REQUIRED, optional hostname verification) and passes it to asyncpg. That removes ambiguity and aligns with CNPG’s TLS setup, reducing handshake/EOF resets.
References (source-verified)
- Server DB settings (TLS + pooling): settings/models/server/database.py
- Async Postgres engine SSL handling: server/database/configurations.py
- Settings overview: Prefect settings docs
If you’d like, share the CNPG service DNS you use in the URL and I can confirm whether hostname checking will pass or suggest the right value for TLS_CHECK_HOSTNAME.Pierre L
12/11/2025, 4:52 PMPREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED : Whether PostgreSQL support mTLS authentication
I don't understand why I should provide a client certificate (with PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE ) while the whole stack work most of the time.Marvin
12/11/2025, 4:53 PMMarvin
12/11/2025, 4:55 PMPREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED turns on creation of an SSLContext for the asyncpg connection. That SSLContext is used for any TLS (server-auth TLS and, optionally, mutual TLS if you also provide a client cert/key). It is not limited to mTLS.
Source of truth in code:
- TLS settings model: SQLAlchemyTLSSettings
- Engine creation (where the SSL context is built and passed to asyncpg): AsyncPostgresConfiguration.engine()
Key behaviors (verified in the code):
- ...TLS_ENABLED=true just makes Prefect build an ssl.SSLContext and pass it to asyncpg via connect_args["ssl"].
- If you do not set TLS_CERT_FILE and TLS_KEY_FILE, Prefect will NOT do client cert auth — it’s just regular server-auth TLS.
- TLS_CA_FILE is optional. If omitted, Prefect uses ssl.create_default_context(...) which relies on the system trust store. You only need TLS_CA_FILE if your DB uses a custom/enterprise CA not in the system trust.
- Hostname verification is enabled by default via TLS_CHECK_HOSTNAME=true and certificates are required (verify_mode = ssl.CERT_REQUIRED) when TLS is enabled.
Why you don’t need to provide a client certificate
- Because client certs are only required for mTLS. If your PostgreSQL only requires server-side TLS, you can set just:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
and omit `TLS_CERT_FILE`/`TLS_KEY_FILE`. Add TLS_CA_FILE only if your server’s cert is not signed by a CA your system trusts.
Minimal examples:
- Server-auth TLS with public CA:
PREFECT_SERVER_DATABASE_CONNECTION_URL=<postgresql+asyncpg://user:pass@host:5432/db>
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
- Server-auth TLS with custom CA:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/etc/ssl/certs/org-root-ca.pem
- Mutual TLS:
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_ENABLED=true
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CA_FILE=/etc/ssl/certs/org-root-ca.pem
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_CERT_FILE=/etc/ssl/certs/client-cert.pem
PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_TLS_KEY_FILE=/etc/ssl/private/client-key.pem
References:
- Issue proposing the settings (worded as mTLS, but the implementation supports both): #18096
- Implementation PR: #18097
If “the whole stack works most of the time” without these settings, it likely means:
- Your DB is allowing non-TLS connections, or
- You’re using a path that doesn’t enforce TLS, or
- You haven’t been passing an SSL context at all (asyncpg won’t use TLS unless given one), but your DB doesn’t strictly require it.
If you want to ensure TLS is always used and properly verified, enable ...TLS_ENABLED=true. Only add TLS_CA_FILE if your system CA store can’t validate the server’s cert, and only add client `CERT`/`KEY` if your DB requires mTLS.
Quick check: are you on Prefect 3.x with an asyncpg URL (postgresql+asyncpg://...) and does your Postgres actually require mTLS? If you can share your Postgres provider/enforcement settings, I can suggest the minimal config for your case.Pierre L
12/12/2025, 8:30 AMMarvin
12/12/2025, 8:31 AMMarvin
12/12/2025, 8:32 AMimport ssl
import asyncpg
import os
# If you’re on a managed service, download and point to their CA file.
# For AWS RDS: <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html>
CA_PATH = "/path/to/your/root-ca.pem"
ctx = ssl.create_default_context(cafile=CA_PATH)
ctx.check_hostname = True
ctx.verify_mode = ssl.CERT_REQUIRED
# Many services require TLS 1.2+
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
# Prefer using the DNS hostname (not an IP) so SNI and hostname checks work.
pool = await asyncpg.create_pool(
host="<http://mydb.xxxxxx.rds.amazonaws.com|mydb.xxxxxx.rds.amazonaws.com>",
port=5432,
user="app",
password=os.environ["PG_PASSWORD"],
database="mydb",
ssl=ctx,
max_size=10,
)
Notes:
- Prefer the hostname over an IP to ensure SNI and certificate hostname verification succeed.
- If you don’t have a managed-service CA, you can start with ssl.create_default_context() and omit cafile, but using the correct CA bundle is more reliable across containers and CI.
- If you still see errors after enabling SSL, the next likely culprits are network timeouts, NAT/idle connection drops, or PgBouncer configuration (especially with prepared statements). But SSLContext is a very common first fix.
Could you share:
- The exact error message(s) and stack trace
- Where Postgres is hosted (RDS/Aurora, Cloud SQL, Neon, Supabase, self-managed)
- Whether you connect by hostname or IP
- asyncpg and Python versions
- Whether this is running inside a Prefect flow/worker, and if you use a pool
With that, I can give more targeted guidance. Helpful references:
- asyncpg connection params: asyncpg.connect docs
- Python SSL context: Python ssl.SSLContext
- AWS RDS TLS/CA guidance: RDS SSL docsPierre L
01/02/2026, 8:33 AMMarvin
01/02/2026, 8:34 AMMarvin
01/02/2026, 8:34 AMpod:
spec:
containers:
- name: prefect-job
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
If helpful, share a bit more about your setup and I can give targeted knobs to turn:
- Prefect version (assuming 3.x unless you say otherwise)
- Where the CPU limit was tight (API, worker pods/containers, DB)
- Orchestrator runtime (Kubernetes, Docker, ECS, etc.) and how you deploy workers
- Typical peak concurrency/throughput
Happy to help you dial in the exact settings.Bring your towel and join one of the fastest growing data communities. Welcome to our second-generation open source orchestration platform, a completely rethought approach to dataflow automation.
Powered by