Sep 17, 2024
Sep 17, 2024
A customer came to us recently with what seemed like a straightforward problem: they needed private access from their infrastructure into services running inside a partner’s network. They’d already evaluated AWS PrivateLink, and on paper it looked right. In practice it meant provisioning NLBs, creating endpoint services, exchanging AWS account IDs, and coordinating connection approvals, then repeating all of that for each region. The partner’s security team also flagged the NLB’s ENIs: permanent inbound entry points into their VPC that someone would have to provision, secure, and track for the lifetime of the connection. What started as a connectivity problem had turned into a permanent infrastructure commitment on both sides.
They ended up going a different direction. That conversation stuck with me because it captures something that’s easy to miss when you’re evaluating connectivity options on paper: the right answer depends heavily on who owns what, where services actually live, and how much infrastructure you’re willing to carry long-term.
AWS gives you three native answers (PrivateLink, VPC peering, and VPC Lattice), and ngrok offers a fourth that works differently from all of them. Here’s how to tell them apart, what each one actually costs, and where each one breaks down.
PrivateLink establishes private connections between VPCs, AWS services, and on-premises networks by provisioning interface VPC endpoints (elastic network interfaces with private IP addresses) inside the consumer’s VPC. Traffic flows through AWS’s backbone without touching the public internet.
The primary use case is private access to a specific application or service, particularly where compliance or security requirements rule out public exposure. A financial institution accessing a banking application, or a team sharing a central database across development, staging, and production VPCs, are classic fits. It also works for hybrid setups: connect an on-premises ERP system to an AWS service via PrivateLink and they behave as though colocated.
What works well: traffic never leaves AWS, high availability across multiple Availability Zones, shareable across accounts, no CIDR overlap constraints. TCP and UDP are both supported (UDP requires dual-stack NLBs). The default quota is 50 interface endpoints per VPC, raisable through Service Quotas.
What doesn’t: the NLB requirement is the most common friction point. PrivateLink only supports NLBs (not ALBs) as the endpoint service frontend, so if your service runs behind an ALB you need an NLB in front of it. There’s no service discovery; consumers need the endpoint DNS name and must request access per service. Cost layers accumulate quickly: per-AZ ENI-hour, NLB usage, and per-GB data processing all run simultaneously. And the endpoint MTU is capped at 8,500 bytes with no Path MTU Discovery, which can silently drop oversized packets.
Consumer VPC Producer VPC ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ EC2 │ │ Service │ │ │ │ │ │ │ │ ENI ◄──────────────────── NLB │ │ (local IP) │ │ │ └──────────────┘ └──────────────┘
The consumer sees a local IP only, with no network adjacency to the producer VPC. The NLB handles load balancing on the producer side.
VPC peering creates a direct link between two VPCs so they communicate as if they’re on the same network, using private IPv4 or IPv6 addresses with no internet gateway or VPN required. AWS updates route tables on both sides automatically. You get TCP, UDP, and ICMP support, cross-region capability, and no bandwidth limits. Traffic between peered VPCs in the same Availability Zone is free.
What works well: low cost with no data-processing fees, no bandwidth limits, security group referencing across peered VPCs (same-region peering only).
What doesn’t: peering is strictly point-to-point. If VPC A peers with B and C, traffic can’t flow from B to C through A (no transitive routing). Connecting N VPCs in a full mesh requires N(N−1)/2 connections: 10 for 5 VPCs, 45 for 10. The default limit is 50 active peering connections per VPC, with a hard maximum of 125. CIDR overlap between any two VPCs blocks you entirely.
Transit Gateway is the fix for the scale problem. Attach each VPC to a central hub (up to 5,000 VPCs) and the gateway handles routing between all of them, including VPNs and Direct Connect. Cost: $0.05/hr per VPC attachment (~$36.50/month) plus $0.02/GB processed. For five or more VPCs it’s usually worth it; for two or three, peering is simpler and cheaper.
VPC peering (point-to-point mesh) Transit Gateway (hub-and-spoke)
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ VPC A │─────│ VPC B │ │ VPC A │ │ VPC B │ │ VPC C │
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
│ │ └──────────┼──────────┘
└────┬────────┘ ┌────┴────┐
┌─┴─────┐ │Transit │──── VPN/DX
│ VPC C │ │Gateway │
└───────┘ └─────────┘
Three peered VPCs need three connections and five need ten, with no transitive routing between them. Transit Gateway needs one attachment per VPC and centralizes routing, though it’s still Layer 3.
VPC Lattice went GA in March 2023 and is the option AWS is pushing hardest today. It operates at Layer 7, above the network layer, and introduces a managed service-connectivity plane: register services into a service network, associate your VPCs to that network, and any VPC can reach any service across accounts, without per-pair configuration.
Unlike PrivateLink and peering, Lattice handles service discovery automatically, enforces IAM-based authorization at the service level, and provides built-in access logging. It natively supports overlapping CIDRs. Because it’s L7, it supports path-based routing, weighted traffic splitting for canary deployments, and request-level auth policies that PrivateLink and peering can’t do.
One hard deadline: AWS App Mesh reaches end-of-life on September 30, 2026, and VPC Lattice is one of AWS’s recommended migration targets (ECS Service Connect is the other, for ECS workloads). If you’re on App Mesh, plan your migration now.
Where it gets complicated: a VPC can only be directly associated with one service network (reaching more takes service network endpoints), which limits certain “shared services with network isolation” patterns. Cross-region requires combining Lattice with cross-region PrivateLink. Lattice is designed for AWS workloads; multi-cloud or on-premises connectivity requires additional tooling. And while Lattice added TCP resource support for databases and IP targets, the data plane is HTTP/HTTPS/gRPC/TLS; raw arbitrary TCP protocols go through a resource gateway, not native Lattice routing.
Pricing: $0.025/hr per provisioned service (~$18.25/month each), plus $0.025/GB and $0.10 per million HTTP requests (first 300k/hr free).
┌───────┐ ┌───────┐ ┌───────┐
│ VPC A │ │ VPC B │ │ VPC C │
└───┬───┘ └───┬───┘ └───┬───┘
└───────────┼───────────┘
┌──────┴──────┐
│ Service │──── Service A (EC2/ECS/Lambda)
│ Network │──── Service B
└─────────────┘──── Service C
L7 routing, IAM auth, and service discovery come built in, with no per-pair configuration. The data plane is HTTP/HTTPS/gRPC/TLS only.
Assumes us-east-1 prices and a 730-hour month. PrivateLink needs one interface endpoint per service per consumer VPC (15 here), priced at a single AZ each; running them across two AZs for HA roughly doubles that line, plus the NLB itself in front of each service. AWS-native options get cheaper per-GB at high volume. ngrok’s data transfer rate is higher ($0.10/GB) but its setup cost is near-zero, and endpoint hours are only billed while an endpoint actually sees traffic.
This is how that customer ended up solving it. ngrok’s private connectivity solution uses two components: an ngrok agent in the remote network that creates private endpoints, and ngrokd, a daemon in the local network that discovers those endpoints, assigns each one a local IP address, and forwards traffic to them through ngrok’s cloud over mTLS. There’s no public endpoint anywhere in this path.
In the remote network, configure the agent’s endpoints with the kubernetes binding. Despite the name, this has nothing to do with Kubernetes: it’s simply a privacy mechanism that makes endpoints reachable only through ngrokd, not from the public internet. The agent can run anywhere, and the upstream can be any address or port the agent can reach:
1# ngrok.yml in the remote network2version: 33agent:4 authtoken: <SERVICE_USER_AUTHTOKEN>5 6endpoints:7 - name: api8 url: http://api.example.com9 upstream:10 url: 808011 bindings:12 - kubernetes13 - name: db14 url: tcp://db.example.com:543215 upstream:16 url: 543217 bindings:18 - kubernetesIn the local network, ngrokd discovers those endpoints automatically and makes them available as local addresses. Run ngrokctl list to see what’s reachable:
1Discovered Bound Endpoints:2 http://api.example.com → 10.107.0.2:803 tcp://db.example.com:5432 → 10.107.0.3:5432Services in the local network connect to those addresses as if the remote services were running locally: no VPN, no route table changes, no firewall rules opened in the remote network.
Local network ngrok cloud
┌────────────────────┐ ┌─────────────────────┐
│ Local services │ │ Private endpoints │
│ │ │ mTLS │ (not publicly │
│ ngrokd ─────────────────────►│ addressable) │
│ (one local IP │ │ │
│ per endpoint) │ └─────────────────────┘
└────────────────────┘ ▲
│ (outbound TLS)
│
┌──────────┴──────────┐
│ Remote network │
│ │
│ ngrok agent │
│ api: :8080 │
│ db: :5432 │
└─────────────────────┘
No public endpoints or inbound rules in the remote network.
The remote network’s perimeter is untouched: the agent makes an outbound TLS connection to ngrok’s cloud, and ngrokd connects from the local side over mTLS. Neither side opens anything inbound. Because this isn’t AWS-native, it works the same whether the remote network is on AWS, GCP, Azure, or bare metal on-premises. Adding a new remote network means deploying one more agent, not another round of NLB provisioning and cross-account coordination.
Each remote network gets an isolated service user and authtoken. If an agent is compromised, you revoke that one credential without touching anything else. And if a single agent is itself too fragile, run a second agent in the same network with pooling_enabled: true on each endpoint: ngrok distributes traffic across the pool and fails over automatically when an agent goes down.
ngrok isn’t the right fit for every situation. If you’re moving terabytes of east-west traffic between VPCs in the same region, PrivateLink or VPC Lattice will be cheaper per-GB and keep traffic on the AWS backbone. If your compliance requirements prohibit traffic transiting an external network path, native AWS options are the safer choice.
Where ngrok consistently wins: connecting infrastructure that lives outside AWS, reaching into customer networks without requiring them to configure anything, and replacing permanent AWS infrastructure with something you can spin up in minutes. The pay-as-you-go model means you’re not carrying the cost of idle endpoints or over-provisioned NLBs.
If you need both high-volume internal traffic and flexible external connectivity, keep heavy east-west traffic on PrivateLink or Lattice and route edge connectivity (on-prem systems, third-party integrations, developer access) through ngrok.
Use VPC peering for two or three VPCs with non-overlapping CIDRs and no expectation of significant growth.
Add Transit Gateway when you have more than roughly five VPCs, need hub-and-spoke routing, or want to bridge VPNs or Direct Connect.
Use PrivateLink when you need strict service-level network isolation, have overlapping CIDRs, or are exposing a service to external customers or partners.
Use VPC Lattice when you’re building a microservice architecture on AWS, want L7 routing and IAM auth built in, or are migrating off App Mesh before September 2026.
Use ngrok when you need to reach into remote or customer networks without touching their firewall rules, want zero public attack surface (no internet-facing endpoint to harden), or need cross-cloud and on-prem connectivity without VPN or Direct Connect.
Choosing the right connectivity solution is genuinely hard: the options have changed a lot, and the right answer depends on where your services actually live.
AWS PrivateLink gives you airtight service-level isolation but bills you for the privilege at every layer. VPC peering is cheap and simple until it isn’t, at which point you’re staring down a mesh of route tables. Transit Gateway tames the mesh but adds its own cost and operational surface. VPC Lattice is the most capable native option, but it’s L7-only and cross-region still requires workarounds.
ngrok sidesteps all of that. Run an agent in the remote network and ngrokd on your side, and you have private connectivity to any service in any network without touching a route table, provisioning an NLB, or opening a single inbound firewall rule. There’s no public endpoint to attack. And because ngrok is pay-as-you-go, you’re not carrying the cost of infrastructure you provisioned six months ago for a use case that changed.
Create a free ngrok account and have your first endpoint running in minutes with the quickstart.
Have a connectivity puzzle that doesn’t fit neatly into any of the above? I’m a customer engineer at ngrok, and untangling exactly this kind of “which option actually fits my network” question is what I do every day. Email me directly and I’ll help you figure out the right one.