Modern cloud estates split workloads across many accounts to isolate blast radius, align ownership, and simplify financial control. The networking model must keep that separation while enabling predictable, low-friction connectivity. The practical goal is straightforward: consistent east–west service discovery, strong identity and policy boundaries, and DNS that “just works” for teams. This article compares VPC Lattice and PrivateLink in multi-account designs and outlines a reproducible shared-services approach with guardrails that hold under growth.
A workable starting point is an organization layout with separate environment OUs, a network-services account, and a security log archive. The network account owns shared DNS, egress controls, and cross-account resource sharing. Organization-level constraints reduce configuration drift and prevent accidental public exposure. Denying certain actions everywhere except the network account is effective, provided exceptions are intentional and audited.
// AWS Organizations SCP preventing internet gateways and enforcing VPC endpoint tagging
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyIGWAttachOutsideNetworkAccount",
"Effect": "Deny",
"Action": "ec2:AttachInternetGateway",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:PrincipalAccount": "111122223333" }
}
},
{
"Sid": "RequireTagsOnVpcEndpointCreation",
"Effect": "Deny",
"Action": "ec2:CreateVpcEndpoint",
"Resource": "*",
"Condition": {
"Null": { "aws:RequestTag:network-boundary": "true" },
"ForAnyValue:StringNotEqualsIfExists": {
"aws:TagKeys": [ "network-boundary", "owner" ]
}
}
}
]
}
VPC Lattice provides a service-centric layer for HTTP/HTTPS and TCP in a region. It abstracts away direct connectivity between VPCs and simplifies client authorization with policy attached to the service. In practice, Lattice is a good fit for intra-organization east–west traffic where producers and consumers live in different accounts. It reduces the matrix of routes, security groups, and NACLs to a smaller set of associations and policies. Name resolution uses Lattice-provided DNS names or custom names integrated with Route 53; clients call the service endpoint over private links within the region.
PrivateLink exposes a Network Load Balancer–fronted service privately to other VPCs and, if needed, other organizations. It operates at Layer 4 and is intentionally narrow in scope: no path-based routing, no headers, and no UDP. The strength of PrivateLink is isolation by design. Consumer VPCs get Elastic Network Interfaces in subnets and call a specific endpoint; producers retain control by allowing only designated principals and requiring acceptance per connection when appropriate. PrivateLink is suitable for regulated boundaries, third-party integrations, and cases where IP overlap is a hard constraint.
A stable pattern is to use VPC Lattice for most internal service-to-service communication and reserve PrivateLink for strict producer–consumer contracts or external partners. This avoids overusing endpoint sprawl while giving security teams clear levers. It also keeps migration flexibility: a producer that starts on PrivateLink can later front the same targets with VPC Lattice and move clients by DNS cutover.
# VPC Lattice service network and target group configuration for multi-account connectivity
resource "aws_vpc_lattice_service_network" "sn" {
name = "sn-platform"
}
resource "aws_vpc_lattice_service" "svc" {
name = "orders-api"
auth_type = "AWS_IAM"
}
resource "aws_vpc_lattice_target_group" "tg" {
name = "orders-tg"
type = "IP" # or "ALB" / "INSTANCE" / "LAMBDA" as applicable
config {
port = 8080
protocol = "HTTP"
vpc_identifier = aws_vpc.app.id
health_check {
path = "/health"
protocol = "HTTP"
interval_seconds = 10
}
}
}
resource "aws_vpc_lattice_listener" "listener" {
service_identifier = aws_vpc_lattice_service.svc.id
protocol = "HTTPS"
port = 443
default_action {
forward {
target_groups {
target_group_identifier = aws_vpc_lattice_target_group.tg.id
}
}
}
}
resource "aws_vpc_lattice_service_network_vpc_association" "assoc_app" {
service_network_identifier = aws_vpc_lattice_service_network.sn.id
vpc_identifier = aws_vpc.app.id
}
resource "aws_vpc_lattice_service_network_service_association" "assoc_svc" {
service_identifier = aws_vpc_lattice_service.svc.id
service_network_identifier = aws_vpc_lattice_service_network.sn.id
}
Authorization belongs with the service, not the caller. For teams inside a single AWS Organization, a simple allow policy keyed on the org ID keeps onboarding light. Narrow it further by account, OU, or IAM role as needed, and attach an explicit deny for risky paths. The policy model is familiar to teams already using IAM.
// VPC Lattice service authorization policy allowing organization-wide access with path restrictions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrg",
"Effect": "Allow",
"Principal": "*",
"Action": [ "vpc-lattice-svcs:Invoke" ],
"Resource": "*",
"Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-abc123" } }
},
{
"Sid": "DenySensitivePaths",
"Effect": "Deny",
"Principal": "*",
"Action": "vpc-lattice-svcs:Invoke",
"Resource": "*",
"Condition": { "StringLike": { "vpc-lattice-svcs:RequestPath": [ "/admin*", "/internal/*" ] } }
}
]
}
For producers that must keep a tight consumer list or support external tenants, PrivateLink remains the right tool. The producer deploys an NLB with targets in private subnets, exposes an endpoint service, and grants permissions to consumer accounts or principals. The consumer creates an interface endpoint in selected subnets with a security group that defines egress to the service. Endpoint policies are for AWS service APIs and do not govern custom endpoint services; rely on service permissions and security groups for data-plane control.
# CloudFormation template creating VPC endpoint service for cross-account PrivateLink access
# --- AWS::ElasticLoadBalancingV2::LoadBalancer, TargetGroup, and Listener omitted for brevity ---
Resources:
EndpointService:
Type: AWS::EC2::VPCEndpointService
Properties:
AcceptanceRequired: true
NetworkLoadBalancerArns:
- arn:aws:elasticloadbalancing:eu-central-1:111122223333:loadbalancer/net/prod-nlb/aaaaaaaaaaaa
AllowConsumerAccount:
Type: AWS::EC2::VPCEndpointServicePermissions
Properties:
ServiceId: !Ref EndpointService
AllowedPrincipals:
- arn:aws:iam::444455556666:root
Outputs:
ServiceName:
Value: !GetAtt EndpointService.ServiceName
Shared services reduce duplication and enable consistent observability and DNS. Central Route 53 Resolver outbound endpoints with forwarding rules let application VPCs resolve private zones hosted in the shared account or on-premises. Use AWS Resource Access Manager to share rules to consumer accounts; consumers associate the rule with their VPCs. The same pattern works for a central inspection VPC using Gateway Load Balancer and for a central NAT with Network Firewall, assuming you enforce egress-only via the network account.
# Route 53 Resolver configuration for centralized DNS forwarding with RAM sharing
resource "aws_route53_resolver_outbound_endpoint" "out" {
name = "rslv-out"
vpc_id = aws_vpc.net.id
security_group_ids = [aws_security_group.rslv.id]
ip_address {
subnet_id = aws_subnet.net_a.id
}
ip_address {
subnet_id = aws_subnet.net_b.id
}
}
resource "aws_route53_resolver_rule" "forward_onprem" {
domain_name = "corp.internal."
name = "forward-onprem"
rule_type = "FORWARD"
resolver_endpoint_id = aws_route53_resolver_outbound_endpoint.out.id
target_ip {
ip = "10.50.0.10"
port = 53
}
target_ip {
ip = "10.50.0.11"
port = 53
}
}
resource "aws_ram_resource_share" "dns_share" {
name = "share-dns-rules"
allow_external_principals = false
}
resource "aws_ram_resource_association" "dns_assoc" {
resource_arn = aws_route53_resolver_rule.forward_onprem.arn
resource_share_arn = aws_ram_resource_share.dns_share.arn
}
resource "aws_ram_principal_association" "dns_principal" {
principal = "444455556666"
resource_share_arn = aws_ram_resource_share.dns_share.arn
}
Day-two operations depend on clear boundaries and simple rollouts. For VPC Lattice, onboarding a new VPC requires only associating it with the service network and ensuring the application security groups allow outbound 443 to the Lattice managed ENIs. For PrivateLink, endpoint creation is per consumer VPC per AZ, so the blast radius is narrower but there are more moving parts. Teams often standardize a minimal command set for both models to keep them testable in CI.
# Associate VPC with Lattice service network and verify DNS resolution
aws vpc-lattice create-service-network-vpc-association \
--service-network-identifier sn-abc123 \
--vpc-identifier vpc-0abc123def456
aws vpc-lattice get-service --service-identifier svc-abc123 | jq '.dnsEntry'
aws ec2 describe-network-interfaces \
--filters "Name=description,Values=*VPC Lattice*sn-abc123*" \
--query 'NetworkInterfaces[].PrivateIpAddress'
Regionality and naming shape architecture choices. VPC Lattice is regional; cross-region access requires separate service networks and inter-region connectivity at another layer. PrivateLink supports specific inter-region pairings for some scenarios; where unavailable, consider keeping consumers in-region or using a transport layer such as Transit Gateway peering plus Lattice per region. DNS must reflect the decision. A neutral practice is giving each region its own private zone suffix and mapping environment-aware names to region-local endpoints.
Network segmentation still matters when using Lattice or PrivateLink. Place producers in private subnets without direct egress, attach security groups with minimal egress, and centralize outbound traffic through inspection. If you adopt a shared NAT or firewalls, enforce a single path by denying NAT gateways outside the network account with SCP, and by reserving dedicated route tables managed via infrastructure-as-code. Avoid implicit transitive routing through peering or overlapping route tables; both Lattice and PrivateLink remove the need for VPC peering in many cases.
Cost controls are easier when endpoints and services are tagged by owner and boundary. VPC Lattice costs scale with the number of services, associations, and requests. PrivateLink costs scale with the number of interface endpoints and data processing per AZ. In practice, consolidating “chatty” internal calls behind Lattice and keeping PrivateLink for fewer, high-value producer–consumer edges keeps both bills predictable. Periodic reporting that breaks down usage by tag and account helps steer architecture decisions without heavy governance.
A gradual migration path mitigates risk. For teams on PrivateLink today, front existing targets with VPC Lattice while keeping the NLBs. Advertise the Lattice DNS name to a subset of consumers and monitor telemetry for latency and errors. If the service performs as expected, update DNS search paths or service discovery records for more consumers. Retain PrivateLink for external tenants and for workloads whose contracts benefit from the explicit producer acceptance model.
Observability must match the chosen data path. Enable access logs on Lattice services and ship them to a central bucket with partitioning by account and service. For PrivateLink, rely on NLB access logs, interface endpoint flow logs, and application-level telemetry to reconstruct traffic. Shared metrics namespaces and consistent dimensions for service, environment, and region keep dashboards and alerting composable across accounts.
The end state is not a single technology choice but a small, documented set. Use VPC Lattice for most internal service-to-service traffic to simplify policy and discovery. Use PrivateLink to cross strong trust boundaries or to publish services to other organizations. Keep DNS, egress, and inspection in a shared network account with RAM-based sharing and organization guardrails. The combination is resilient to growth and team autonomy while keeping the operational surface area understandable.
References
AWS VPC Lattice Documentation https://docs.aws.amazon.com/vpc-lattice/
AWS PrivateLink Guide https://docs.aws.amazon.com/vpc/latest/privatelink/
AWS Transit Gateway Documentation https://docs.aws.amazon.com/vpc/latest/tgw/
AWS Route 53 Resolver Documentation https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver.html
AWS RAM (Resource Access Manager) https://docs.aws.amazon.com/ram/latest/userguide/what-is.html