PostgreSQL certificate SAN/CA prevents sslmode=verify-full
redjanna
PROOP

a month ago

Hello Railway Support,

We are preparing a protected synthetic staging environment for production readiness. PostgreSQL TLS connections work with sslmode=require, but we cannot safely enable sslmode=verify-full because the server certificate presented to our application identifies localhost, while the application connects through a Railway-generated hostname. We also could not identify a supported CA bundle and hostname combination that validates both the chain and server identity.

We have already:

  • confirmed encrypted TLS connectivity with sslmode=require;
  • placed the application and PostgreSQL in the same Railway region;
  • separated runtime and migration database roles;
  • verified readiness and completed an isolated restore test.

No secrets, credentials, customer data, or connection strings are included in this request.

Could you please confirm:

  1. Which hostname and CA bundle should clients use for hostname-verified TLS (sslmode=verify-full) with Railway PostgreSQL?
  2. Can Railway provide a certificate whose SAN matches the private/internal hostname or TCP proxy hostname?
  3. If verify-full is not currently supported by the standard PostgreSQL service, what is Railway's recommended production-safe pattern: private-network trust, a custom postgres-ssl image, or another supported option?

This is currently blocking our product-production promotion.

Thank you.

$20 Bounty

2 Replies

Railway
BOT

a month ago

This thread has been opened as a public bounty so the community can help solve it. The thread and any further activity are now visible to everyone.

Status changed to Open Railway about 1 month ago


a month ago

You should use railway's private networking url with sslmode=require, as Services inside same railway project communicate over isolated private network using ipv6 or service-name.railway.internal Domain

Also make sure that both of your services are deployed to same region.


sri-shakthi
FREE

a month ago

**This is expected behavior of Railway's stock Postgres image, not a misconfiguration on your side **

I can demonstrate it. Probing a fresh Railway Postgres over its TCP proxy:

openssl s_client -connect : -starttls postgres

Certificate chain

0 s:CN = localhost

i:CN = root-ca

1 s:CN = root-ca

i:CN = root-ca

...

Verify return code: 19 (self-signed certificate in certificate chain)

Screenshot 2026-07-18 at 5.44.01 PM.png

The default Postgres service deploys from railwayapp-templates/postgres-ssl,

whose init-ssl.sh generates a self-signed server cert at container boot (note the NotBefore timestamp matches deployment time), with CN=localhost and a SAN containing only DNS:localhost, signed by a root CA that exists only inside that container and is never published. So verify-full cannot pass against the stock service by design — your sslmode=require result is the ceiling out of the box.

  1. Hostname + CA bundle for verify-full: none currently exists. The cert only identifies localhost, and since the CA is per-deployment, there's no downloadable bundle. (A libpq trick — host=localhost hostaddr= with an extracted root.crt — technically passes, but proxy IPs aren't stable; don't ship it.)

2. Cert with a SAN matching the private/proxy hostname: not provided by Railway today, but achievable: fork railwayapp-templates/postgres-ssl, edit init-ssl.sh to add subjectAltName = DNS:${RAILWAY_PRIVATE_DOMAIN} (plus the proxy domain if needed), point your Postgres service's Source at your image, set REGENERATE_CERTS=true to reissue, then extract root.crt from the data volume and give it to your app as sslrootcert. That's genuine verify-full against postgres.railway.internal.

**3. Recommended production-safe pattern: private networking. **Your app and DB are in the same project, so connect via the private endpoint and remove the public TCP proxy entirely (Postgres service → Settings → Public Networking → delete). With no public exposure, require over the private mesh is the commonly accepted posture. If compliance specifically mandates hostname-verified TLS, the custom image in (2) is the supported path; a middle ground is extracting the generated root.crt and using sslmode=verify-ca (chain validation, no hostname check).

Operational gotcha if you go custom: certs auto-regenerate when expired or within 30 days of expiry on restart — pin trust to the root CA, never the leaf, or connections break silently on redeploy.

Attachments


Welcome!

Sign in to your Railway account to join the conversation.

Loading...