How we move traffic between our servers without exposing it to the Internet
Published on 2026-09-12 by frfrand
Cuando una app en un servidor necesita una base que vive en otro, hay dos caminos: abrir un puerto al mundo o hacer que se vean entre ellos. Por qué elegimos el segundo.

It is one of the most common situations as soon as a project grows a bit: the application runs on one server and the database on another. How do they talk to each other?
The quick answer is to open the database port and point the app to the other server's public IP. It works in five minutes. And it leaves a database exposed to the Internet, which is exactly what one spends the following years trying to avoid.
We solve it the other way around.
The problem with the easy solution
Opening port 5432 to the world has three costs that are not visible on the first day:
- The database ends up on automatic scanner lists within minutes.
- Security depends on the database password and on its version not having vulnerabilities. Forever.
- Traffic between your two servers goes out to the Internet and comes back in, adding latency and a segment you do not control.
That last point is underestimated. Two servers from the same provider, sometimes in the same data center, sending queries over the public Internet.
What we do instead
Between ArduMaker VPSs, internal traffic travels through encrypted tunnels between the machines. From the application's point of view, the database is on localhost, on the usual port. From the Internet's point of view, the database server has no database port open.
The important piece is the direction in which the tunnel is raised: it is initiated by the side that needs to connect. This means that the server that stores the data does not need to accept incoming connections from anyone. There is no door to protect, because there is no door.
How it looks in practice
Configuration is done from the panel: you choose the initiating VPS, the receiving VPS, and which port is published from one side to the other. From there:
# on the app server, the database is local DATABASE_URL=postgres://usuario:clave@localhost:5432/midb
Your application's connection string points to localhost. If tomorrow the database moves to another server, you change the tunnel configuration and the application doesn't even notice.
That decoupling is half the value. The other half is that the exposed surface does not grow as your infrastructure grows.
What else you gain
Less duplicated configuration. There is no need to maintain firewall rules for every pair of servers that talk to each other.
Reversible. Decommissioning a tunnel does not leave a forgotten open port: when you cut the tunnel, access disappears.
Auditable. The tunnel is an object of the platform, with its status and its history. You can answer "who can talk to this database?" by looking at a list, instead of reconstructing it from iptables rules.
Own keys. The receiver accepts only the keys you declared. Adding a new server is adding its key, and removing it is deleting it.
When it is not enough
If what you need is for a third-party service to reach your database (an externally hosted analytics tool, for example), a tunnel between your servers does not solve that. In that case, the options are a VPN that includes that third party, a read-only replica designed to be exposed, or your own API that returns only what that third party needs.
The third option is usually the best, even though it takes more work: it is the only one where you decide exactly what goes out.
The underlying idea
It is the same idea that runs through the entire platform: what does not need to be open, is not opened. And when two things need to communicate, it is better to make them visible to each other than to make them visible to everyone.