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:
- Which hostname and CA bundle should clients use for hostname-verified TLS (sslmode=verify-full) with Railway PostgreSQL?
- Can Railway provide a certificate whose SAN matches the private/internal hostname or TCP proxy hostname?
- 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.
2 Replies
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.
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)
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.
- 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