Keep Service-to-Service Traffic Inside Railway With Private Networking
Move service-to-service calls off the public internet in Railway using .railway.internal DNS and reference variables, with environment-scoped isolation and a private Postgres example.
19 May 2026, 09:37 UTC

Your API and database are in the same Railway project but they still talk over the public internet. That adds latency, exposes a public endpoint you don’t need, and makes service-to-service traffic count as egress. The useful move is to keep that traffic inside the project’s private network and let Railway’s internal DNS handle discovery.
Public hops cost more than you expect
Railway groups deployments into projects and environments. By default it is easy to point a service at another service’s public domain. For a chatty architecture — a web app polling an API, an API hitting Postgres on every request — that means packets leave the platform, hit the public internet, and come back in.
Internal traffic over the private network is generally not billed as egress the way public traffic is, and it avoids the extra round-trip and TLS termination overhead for calls that never needed to leave the project. The trade is visibility: you lose the ability to test the service from your laptop without a tunnel, which is the point for internal-only components.
How private networking works in Railway
Each service in a project gets an internal DNS name that resolves only inside the project’s private network. The common pattern is <service-name>.railway.internal. No public domain or exposed port is required for service-to-service traffic.
Private networking is scoped per environment. A service in staging cannot resolve or reach production’s internal hostnames, giving lightweight isolation without separate accounts.
Railway also generates connection variables for managed services and supports reference variables. A reference variable like ${{Postgres.DATABASE_URL}} lets one service consume another service’s variables so credentials and hosts stay in sync when the upstream service changes. Confirm exact variable names in the dashboard; names are platform-defined and can change.
Worked example: Node API with a private Postgres
Goal: an API service with a public domain, and a Postgres service with no public exposure, communicating over the private network.
Setup decisions
- Put both services in the same project and the same environment.
- Disable public exposure on the Postgres service. Only the API keeps a public domain.
- Use a reference variable for the database connection so the API does not hardcode a host.
Configuration sketch
In the API service Variables tab, add a reference to the Postgres service’s connection string. The dashboard shows the generated variable for the Postgres service, typically DATABASE_URL. Reference it as ${{Postgres.DATABASE_URL}} in the API service. The API code then reads process.env.DATABASE_URL as usual.
If you prefer an explicit host, the internal DNS name for the Postgres service is postgres.railway.internal on port 5432. Use that only if your driver requires a host override, and verify the name in the service’s Networking section.
Verification steps you can run without changing state
- Open the API service in the Railway dashboard and use the exec or deploy log console. Required permission: deploy access to the project.
- Resolve the internal name:
nslookup postgres.railway.internal. Expected check: the name resolves to a private address inside the project. - Test reachability:
curl http://postgres.railway.internal:5432will not return HTTP, but a TCP connect attempt should succeed if the service is listening. Risk: running network diagnostics in production can add load; run in staging first. - Check Variables tab for the API service and confirm the reference variable resolves after redeploy. A redeploy is required for variable changes to take effect.
Compare network usage in the project usage view before and after moving traffic off public endpoints. The change is observable as a reduction in public egress for service-to-service calls.
Trade-offs and limits
Private networking does not replace authentication. Any compromised service in the same project and environment can reach internal ports. Keep database credentials strong and enable app-level auth.
Internal DNS names and exact variable names are platform-defined. Do not hardcode assumptions from older tutorials; confirm them in the dashboard for your project.
Region placement matters. Services in different regions will still incur cross-region latency even over the private network. IPv6-only internal addressing can affect some runtimes; test with your actual stack.
If you need to roll back, re-enable public exposure on the database service and point the API at the public connection string. That restores external access but reintroduces public egress and exposure.
Actionable close
Move internal-only services off public domains, use the project-scoped .railway.internal names for service discovery, and wire credentials with reference variables. Verify reachability with a hostname lookup from inside the caller service and confirm variables resolve after redeploy. Keep authentication on the service itself, and keep staging and production in separate environments to preserve isolation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.