The question is rarely whether to encrypt something between two cloud machines; it is which interface you are encrypting and who checks whose identity. I start these conversations with the service rather than the network diagram, because the service already knows what it needs. Three boundaries cover most cases: the application's own TLS, a single forwarded TCP port over SSH, and a routed overlay that gives every participant an address. They are not ranked by strength, they are ranked by how much of the problem they cover.

Start with the service, not the network

Walk outward from the process. What protocol does it speak, which name will the client verify, and who is allowed to open a connection at all? Those three answers eliminate options faster than any comparison table, and the service's own configuration usually answers them in a couple of minutes.

Take a small internal API that already serves HTTPS on port 8443 with a certificate for api.internal.example. ss -ltnp on the host shows what it binds to, and openssl s_client -connect 127.0.0.1:8443 -verify_hostname api.internal.example shows whether the certificate validates for the name the client will actually use. If both are healthy, the service can protect its own stream and the only remaining work is reachability. That is a much smaller problem than building a network.

Step 1: does the service protect itself?

This is the option to take when it fits, and it fits more often than teams assume. The service's encryption covers the whole path between client and server, including every hop in between, which no tunnel around the middle improves. Configure the expected name and trust explicitly instead of relying on whatever the client's default store happens to contain.

Encryption and reachability stay separate controls, and merging them is where this option goes wrong. A TLS service on an unrestricted public listener still accepts connection attempts from anyone, so the listener and the network rules have to be scoped independently of the fact that the payload is protected. Then check the dependencies, because an API over TLS says nothing about the discovery endpoint, the admin port or the bulk transfer channel beside it. Each of those needs its own answer, and one unprotected sibling is enough to make the whole boundary a fiction.

Step 2: is one TCP port the whole requirement?

When the requirement really is one TCP port, a forward is the smallest thing that works. An SSH local forward carries that port through an authenticated connection that already exists.

The shape is ssh -N -L 8443:127.0.0.1:8443 [email protected], and the detail that matters is the target address: 127.0.0.1 is resolved on the remote side, so the connection to the service is opened from the bastion rather than from your laptop. Bind the local listener narrowly as -L 127.0.0.1:8443:... so you are not publishing the service to your own local network by accident. On the server side, restrict what the account may forward instead of trusting the client to behave. PermitOpen, AllowTcpForwarding local and a GatewayPorts no default are the three settings that turn a general-purpose relay into a specific channel.

The contract is narrow in both directions. SSH protects its own channel, and a further hop from the bastion to another application needs its own protection. A service listening only on the bastion's loopback has a very different exposure from one reached across another network, and the forward does not change that. Keep the application's authentication and TLS checks in place rather than treating the tunnel as a substitute for them.

This works well for one connection with one owner. At scale it becomes a system: local port conflicts, supervision, key rotation and the question of who restarts the forward after a reboot.

Step 3: is routing the actual requirement?

That list is the honest cost of the smallest option, and it is why teams eventually replace a handful of forgotten background commands with something managed. The replacement is not automatically a VPN. If the requirement is still one TCP port, a supervised forward with a documented owner is cheaper to run than an overlay and it fails in ways you can see. Measure operational ownership before you measure elegance.

Routing becomes the right answer when routing is the requirement. Several applications needing ordinary IP connectivity, protocols a TCP forward cannot carry, or services that move between hosts all point the same way.

An overlay gives participants an address and a route, and then the real work starts: non-overlapping ranges, route ownership, DNS, enrollment, revocation and what happens when a peer disappears. None of that arrives with the address. A peer admitted to the overlay is also not automatically authorized for every operation on the destination, so service-level authentication stays.

What you own afterwards

Two things belong in the record you leave behind. The first is the identity the client is expected to verify, because every one of these boundaries degrades quietly once a name check gets relaxed to make something work. The second is the removal step: which key, which rule or which route disappears when access ends, and who performs it.

The walkthrough ends where it started, with the service. Pick the boundary that covers the actual traffic and verifies the peers you meant to verify, then write down how access is updated, monitored and removed. The OpenSSH reference is the place to confirm the forwarding options before you promise a narrow contract. If you cannot describe the removal step in one sentence, the boundary is larger than the problem.

Artaigilean co-cheangailte