Private Network
Communicate securely between services without traversing the public internet.
Your Render services in the same region can communicate over their shared private network, without traversing the public internet:
- Each web service and private service has a unique hostname on the private network.
- These services can listen for private network traffic on almost any port (see below) and use any protocol.
- Free web services can send private network requests, but they can’t receive them.
- Each PostgreSQL and Redis instance has an internal URL specifically for private network connections.
- Background workers and cron jobs can send private network requests, but they can’t receive them.
Private network communication is fast, safe, and reliable. It uses stable internal hostnames and IPs that dynamically map to individual instance addresses (which can change between deploys).
Direct IP-based communication is also supported for advanced use cases.
Port restrictions
Each service is limited to a maximum of 75 open ports.
The following ports cannot be used for private network communication:
10000
18012
18013
19099
What’s on my private network?
Static sites are not on a private network.
Other Render services are on the same private network if they’re deployed in the same region and they belong to the same workspace.
With a Professional workspace or higher, you can block private network traffic from entering or leaving a particular environment.
How to connect
These service types each have an internal address or URL:
- Web services
- Private services
- PostgreSQL databases
- Redis instances
This value is available from each service’s Connect menu in the Render Dashboard (see the Internal tab):
Private services also display this value as their Service Address:
The private service above has the internal address elasticsearch-2j3e:9200
. Other services on the private network can communicate with it at this address.
You might need to specify a service’s expected protocol in its internal address string when you connect.
For example, you might need to specify http://elastic-qeqj:10000
instead of just elastic-qeqj:10000
.
Background workers and cron jobs don’t have an internal address, so they can’t receive inbound private network traffic. However, they can send requests to other service types on their private network.
Direct IP communication (advanced)
Use this method only if hostname-based communication does not serve your use case.
For advanced use cases, you can send private network requests directly to the IP of a specific service instance. This is most commonly useful for scaled services in the following cases:
- You need to message each running instance of your service individually (such as to pull metrics with a monitoring tool like Prometheus).
- You want to implement custom load balancing logic for your service, instead of relying on Render’s built-in load balancing.
Each web service and private service has an associated discovery hostname that resolves to all of its active instance IPs. By convention, this hostname has the format [INTERNAL_HOSTNAME]-discovery
(e.g., myapp-ne5j-discovery
). To find your service’s internal hostname, see How to connect.
Each service exposes its discovery hostname to its own environment via the RENDER_DISCOVERY_SERVICE
environment variable. If you manage your services via Blueprints, you can also access another service’s discovery hostname (see Referencing values from other services).
Example: Obtaining instance IPs
The snippet below shows a JavaScript function that fetches all of a service’s instance IP addresses via DNS lookup and prints them to the console. For other languages, use a supported DNS lookup library.
Use a lookup API that relies on the underlying system’s DNS resolver.
This ensures that your lookup applies necessary DNS configuration (such as rules defined in /etc/resolv.conf
).
const dns = require('dns');
// Obtain discovery hostname from environment variable
const discoveryHostname = process.env.RENDER_DISCOVERY_SERVICE;
function fetchAndPrintIPs() {
// Perform DNS lookup
// all: true returns all IP addresses for the given hostname
// family: 4 returns IPv4 addresses
dns.lookup(discoveryHostname, { all: true, family: 4 }, (err, addresses) => {
if (err) {
console.error('Error resolving DNS:', err);
return;
}
// Map over results to extract just the IP addresses
const ips = addresses.map(a => a.address);
console.log(`IP addresses for ${discoveryHostname}: ${ips.join(', ')}`);
});
}