Fly.io’s Private Networking (6PN) lets your apps talk to each other without hitting the public internet, which is faster and more secure.

Let’s see it in action. Imagine you have a web app (my-web-app) and a database app (my-db).

# Deploy your web app (if you haven't already)
fly deploy --app my-web-app

# Deploy your database app (if you haven't already)
fly deploy --app my-db

Now, my-web-app needs to talk to my-db over 6PN. You’ll need to add a services block to your fly.toml for my-web-app:

[[services]]
  internal_port = 5432 # The port your DB listens on
  protocol = "tcp"
  bind = "0.0.0.0:5432" # Or whatever port your web app connects to internally

  [services.concurrency]
    type = "connections"
    hard_limit = 100
    soft_limit = 80

  [[services.tcp_checks]]
    interval = "10s"
    timeout = "3s"

And for my-db, you’ll need to expose its internal port:

[[services]]
  internal_port = 5432 # The port your DB listens on
  protocol = "tcp"
  bind = "0.0.0.0:5432"

  [services.concurrency]
    type = "connections"
    hard_limit = 100
    soft_limit = 80

After deploying these changes, my-web-app can connect to my-db using its Fly App Name.

# Inside your my-web-app code (e.g., Node.js)
const pg = require('pg');
const client = new pg.Client({
  user: 'postgres',
  host: 'my-db.internal', // <-- The key part!
  database: 'mydatabase',
  password: process.env.DB_PASSWORD,
  port: 5432,
});
await client.connect();

The magic happens because Fly.io assigns a special .internal DNS name to each app within a region. When my-web-app tries to connect to my-db.internal, Fly.io intercepts this request. It sees that both my-web-app and my-db are deployed to the same region and are configured with services. Instead of routing the traffic out to the internet and back in, it establishes a direct, private connection between the two Fly VMs using its global network of private IP addresses. This bypasses NAT gateways and public IP addresses entirely.

The [[services]] block in fly.toml is crucial. It tells Fly.io that this application is serving something on a given internal_port. When you specify bind = "0.0.0.0:5432", you’re saying your application is listening on all interfaces on port 5432 within the VM. Fly.io then makes this service discoverable by other apps in the same region via the .internal hostname. The protocol and concurrency settings help Fly.io manage the connection pool and health checks for that service.

The tcp_checks are important for ensuring the service is actually available. Fly.io will periodically attempt to establish a TCP connection to the internal_port on your app’s VMs. If it can’t connect after a certain number of attempts, it will stop routing traffic to that specific VM. This prevents your web app from sending requests to a database instance that has crashed or is unresponsive.

The .internal hostname is not just a DNS alias; it’s a signal to the Fly.io platform that you intend for this connection to be private. When Fly.io resolves my-db.internal, it doesn’t give you a public IP. Instead, it returns a private IP address from Fly’s 6PN network, which is then routed directly between the VMs. This is why you don’t need to open firewall ports or expose your database to the public internet.

If your apps are in different regions, the .internal hostname still works, but the traffic will traverse Fly.io’s global private network. This is still more direct and secure than going over the public internet, but there will be increased latency compared to apps in the same region. The underlying mechanism is the same: Fly.io manages the private IP allocation and routing between its globally distributed infrastructure.

You can also use these private IPs directly if you know them, but relying on the .internal hostname is recommended as Fly.io manages the IP address assignment and can change them without notice. The fly postgres create command automatically configures the necessary services for you.

Understanding how Fly.io’s internal DNS resolution and private IP routing work is key to building secure and performant distributed applications on the platform.

You’ll next want to explore how to manage secrets for your database connections securely.

Want structured learning?

Take the full Fly-io course →