OTP 26 changes SSL defaults and your Postgres connection might break
OTP 26 changes the default SSL verify option. This can break your Ecto Postgres connection if you rely on SSL without supplying CA certificates.
OTP 26 ships with safer SSL defaults. The default value for the verify option changed from :verify_none to :verify_peer. This means host verification is now enabled by default and requires trusted CA certificates to be supplied via cacerts or cacertsfile.
This is a good change for security, but it can catch you off guard when upgrading.
The problem
If you use SSL to connect to Postgres (mandatory on providers like Digital Ocean), the connection will simply fail after upgrading to OTP 26. You’ll see an error like this:
** (DBConnection.ConnectionError) connection not available and request was dropped
from queue after 15000ms. This means requests are coming in and there are no idle
connections, no ## connection processes are available to be started and/or checked out,
or the pool is otherwise not accepting requests
The underlying issue is that Postgrex passes your ssl_opts to Erlang’s :ssl module, which now requires CA certificates for peer verification.
The fix
You could set verify: :verify_none to restore the old behaviour, but that disables certificate verification entirely. Since OTP 25, :public_key.cacerts_get/0 can fetch the CA certificates from your operating system’s certificate store instead. It works on Linux, macOS and Windows without hardcoding any file paths. Add this to your runtime.exs:
config :myapp, MyApp.Repo,
# ...
ssl_opts: [
verify: :verify_peer,
cacerts: :public_key.cacerts_get()
]
This needs to be in runtime.exs (not config.exs or prod.exs) because :public_key.cacerts_get/0 is a runtime call. It should read the certificates from the machine your app runs on, not from your build machine.
If you’re running in a Docker container, make sure the ca-certificates package is installed in your image.
A note on Azure
This works with Azure Database for PostgreSQL out of the box. Azure uses certificates signed by publicly trusted root CAs (DigiCert Global Root G2, Microsoft RSA Root CA 2017), which are included in standard OS certificate stores.
If you’re connecting through Azure Private Endpoints with custom DNS, hostname verification might fail because the certificate’s common name doesn’t match your private DNS name. In that case you can set server_name_indication explicitly:
ssl_opts: [
verify: :verify_peer,
cacerts: :public_key.cacerts_get(),
server_name_indication: ~c"your-server.postgres.database.azure.com"
]