Keep Your Database Off the Internet: Railway Private Networking in Practice
Railway services in the same project can talk over a private network using internal DNS names. Here's how to wire an app-plus-database setup, why you should disable the database's public proxy, and how to verify it all works.
28 Jan 2026, 21:58 UTC

If you deploy an app and a Postgres database as separate services on Railway, the default instinct is to connect them the way you'd connect anything else: a public hostname, a port, credentials. That works, but it means your database is reachable from the entire internet, and every query between your app and its data takes a round trip through public routing. Railway's private networking exists to fix both problems at once, and wiring it up correctly is mostly a matter of knowing which hostname to use and which toggle to turn off.
The problem with the default setup
When you add a database service to a Railway project, it's easy to end up connecting to it through its public proxy address. Two costs follow. First, exposure: anything listening on a public port is a target for credential stuffing and scanning, and a database is the last thing you want in that position. Second, latency: traffic that could stay inside Railway's internal network instead leaves and re-enters, adding avoidable milliseconds to every query. For a chatty app making dozens of queries per request, that adds up.
How private networking works
Services within the same Railway project and environment share a private network. Each service gets an internal DNS name, conventionally <service-name>.railway.internal, which other services in that project can resolve. When your app connects to Postgres using that internal hostname on the database's listening port (5432 for Postgres), the traffic never touches the public internet.
Two scoping rules matter:
- Private networking is scoped to a project and environment. A service in your production environment cannot resolve internal names in staging, and nothing crosses project boundaries.
- The internal name resolves only from inside the project. Your laptop can't reach it, which is exactly the point.
Wiring an app-plus-database setup
The typical pattern looks like this:
- Deploy Postgres as its own service in the project.
- In the app service, reference the database's connection variables rather than hardcoding anything. Railway can inject variables such as a connection URL or host/port pairs; reference them in the app service's variable settings so config stays portable across environments.
- Point the app's database host at the internal hostname. For example, if you construct your own connection string instead of using a generated URL, it would look like:
DATABASE_URL=postgresql://user:[contact removed]:5432/mydbThe exact service-name portion of the hostname and the generated variable names depend on what you named your services, so confirm them in the Railway dashboard under the service's variables rather than assuming the format.
Then the important step people skip: disable public networking on the database service. If the database still has a public TCP proxy enabled, you've gained the latency benefit but given up most of the security one. The app keeps working through the internal hostname; the public endpoint simply goes away.
Verifying it actually worked
Don't assume the wiring is right — check it:
- From the app's logs or a one-off deploy shell, confirm the app resolves and connects using the internal hostname. A successful boot with database migrations running is usually proof enough.
- After disabling the public proxy on the database, try connecting from your local machine with the old public address. It should fail. If it succeeds, the proxy is still on.
- Watch query latency before and after the switch if you have instrumentation; internal traffic should be measurably faster than routed public traffic.
Trade-offs and limits
Private networking is not a substitute for authentication. Any service inside the same project can reach the database's internal address, so if one service is compromised, the internal network won't save you. Keep strong credentials on the database and use TLS for connections where the tooling supports it.
The project-and-environment scoping is also a real constraint. If you need a service in one project to talk to a database in another — a shared analytics database, say — private networking won't help. You'll need a public endpoint (with all the hardening that implies) or a different connectivity approach. Plan your project boundaries with that in mind: co-locate services that need fast, private communication.
Finally, details like the exact internal DNS format, variable naming, and which networking options are available on your plan can change as Railway evolves. Treat the dashboard as the source of truth for your project and re-verify after platform updates.
The takeaway
The actionable version: if your app and database live in the same Railway project, connect them over the .railway.internal hostname via referenced variables, then turn off the database's public proxy and confirm external connections fail. You get lower latency and a smaller attack surface for roughly fifteen minutes of work — one of the better effort-to-payoff ratios in deployment hygiene.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.