Skip to content

Set postgresql ALPN on SSL contexts created by asyncpg - #1372

Open
DurandA wants to merge 2 commits into
MagicStack:masterfrom
DurandA:direct-tls-alpn
Open

DurandA wants to merge 2 commits into
MagicStack:masterfrom
DurandA:direct-tls-alpn

Conversation

@DurandA

@DurandA DurandA commented Sep 24, 2026 •

Copy link
Copy Markdown

PostgreSQL 17+ rejects direct SSL connections that don't negotiate the postgresql ALPN protocol:

LOG:  received direct SSL connection request without ALPN protocol negotiation extension

asyncpg never set ALPN, so direct_tls=True (or sslnegotiation=direct) only worked through TLS-terminating proxies, not against the server itself. The existing direct TLS tests all go through the test proxy, which is why this wasn't caught.

This sets ALPN on the contexts asyncpg creates (string ssl modes and ssl=True), like libpq does. It's sent for both negotiation modes, same as libpq; servers without ALPN support just ignore it.

User-provided SSLContexts are left alone, since asyncpg doesn't mutate them elsewhere either. The direct_tls docs now say to call ctx.set_alpn_protocols(['postgresql']) on them.

Added test_direct_tls_native, which connects straight to the test cluster with direct_tls=True (skipped before PG 17). Ran the SSL connection tests against PostgreSQL 18: they pass with the change, and the new test fails for every mode without it.

Fixes #1371

PostgreSQL 17+ rejects direct SSL connections without the `postgresql`
ALPN protocol, so `direct_tls=True` only worked through TLS-terminating
proxies. Set it on the contexts asyncpg creates, like libpq does, and
document that user-provided contexts need it for direct TLS.

Fixes MagicStack#1371.

@elprans elprans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please guard ALPN setup on builds without ALPN; default TCP connections otherwise fail.

Comment thread asyncpg/connect_utils.py Outdated
Comment on lines +817 to +821
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)

elif ssl is True:
ssl = ssl_module.create_default_context()
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_alpn_protocols() raises NotImplementedError without ALPN, breaking default ssl='prefer' connections. Guard both calls:

Suggested change
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)
elif ssl is True:
ssl = ssl_module.create_default_context()
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)
if ssl_module.HAS_ALPN:
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)
elif ssl is True:
ssl = ssl_module.create_default_context()
if ssl_module.HAS_ALPN:
ssl.set_alpn_protocols(_ALPN_PROTOCOLS)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Guarded both calls in fa80694. I checked it by running the SSL tests with HAS_ALPN patched to False and set_alpn_protocols raising NotImplementedError: they pass with the guard and fail without it.

SSLContext.set_alpn_protocols() raises NotImplementedError on builds
without ALPN, which broke every default (sslmode=prefer) TCP connection.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

direct_tls doesn't work with PostgreSQL 17+ because ALPN isn't set

2 participants