Home / AWS

AWSHow-toAdvanced

AWS PrivateLink for your own service: endpoint services, cross-account consumers and private DNS

Publish an internal service to other VPCs and accounts with PrivateLink instead of peering. Build the NLB-backed endpoint service, approve consumers, set up private DNS with domain verification, and debug the usual connection failures.

Deepak Bhardwaj3 min read

Applies toAWS PrivateLink (VPC endpoint services, interface endpoints), Network Load Balancer, Route 53, AWS CLI v2

VPC peering and Transit Gateway connect whole networks. PrivateLink exposes one service on one port, so consumers can have overlapping CIDRs and see nothing else in your VPC. It is the cleanest way for a platform team to offer an internal API to many accounts.

How the pieces fit

Consumer VPC                          Provider VPC
 app -> interface endpoint (ENI) ==PrivateLink==> NLB -> targets (EC2/ECS/IP)
        DNS: api.internal.example.com              endpoint service: com.amazonaws.vpce.<region>.vpce-svc-xxxx

Traffic always starts from the consumer side. The provider cannot open connections back through the endpoint.

Step 1: the provider NLB

Use an internal NLB in the same AZs your consumers use.

aws elbv2 create-load-balancer --name nlb-orders-api --type network --scheme internal \
  --subnets subnet-a subnet-b subnet-c
aws elbv2 create-target-group --name tg-orders-api --protocol TCP --port 8443 \
  --vpc-id vpc-provider --target-type ip --health-check-protocol HTTPS --health-check-path /health
aws elbv2 create-listener --load-balancer-arn $NLB --protocol TCP --port 443 \
  --default-actions Type=forward,TargetGroupArn=$TG

NLBs can have security groups now; if yours does, allow the consumer endpoint traffic (it appears from the NLB's perspective as coming from the PrivateLink side; check the "Enforce inbound rules on PrivateLink traffic" setting).

Step 2: the endpoint service

aws ec2 create-vpc-endpoint-service-configuration \
  --network-load-balancer-arns $NLB --acceptance-required \
  --private-dns-name api.internal.example.com \
  --supported-ip-address-types ipv4

aws ec2 modify-vpc-endpoint-service-permissions --service-id vpce-svc-0abc \
  --add-allowed-principals arn:aws:iam::222222222222:root arn:aws:iam::333333333333:root

You can also allow a whole organisation with an aws:PrincipalOrgID-style principal pattern where supported; check the current docs. Keep --acceptance-required on so new consumers are a deliberate decision.

Private DNS name verification

To let consumers use api.internal.example.com instead of the long vpce DNS name, prove you own the domain:

aws ec2 describe-vpc-endpoint-service-configurations --service-ids vpce-svc-0abc \
  --query "ServiceConfigurations[].PrivateDnsNameConfiguration"
# create the TXT record it shows in the PUBLIC hosted zone for example.com, then:
aws ec2 start-vpc-endpoint-service-private-dns-verification --service-id vpce-svc-0abc

Step 3: the consumer endpoint

In the consumer account:

aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface \
  --vpc-id vpc-consumer --service-name com.amazonaws.vpce.eu-west-1.vpce-svc-0abc \
  --subnet-ids subnet-x subnet-y --security-group-ids sg-endpoint \
  --private-dns-enabled

Back in the provider account, accept it:

aws ec2 describe-vpc-endpoint-connections --filters Name=service-id,Values=vpce-svc-0abc \
  --query "VpcEndpointConnections[].[VpcEndpointId,VpcEndpointOwner,VpcEndpointState]" --output table
aws ec2 accept-vpc-endpoint-connections --service-id vpce-svc-0abc --vpc-endpoint-ids vpce-0consumer

AZ alignment

An endpoint can only be created in AZs where the provider NLB has a subnet. AZ names differ between accounts; AZ IDs (euw1-az1) do not. Compare IDs:

aws ec2 describe-availability-zones --query "AvailabilityZones[].[ZoneName,ZoneId]" --output table

Enable cross-zone load balancing on the NLB if consumers in one AZ must reach targets in others.

Debugging

Symptom Cause Fix
Endpoint stuck pendingAcceptance Provider has not accepted Accept, or add consumer to auto-accept list
InvalidServiceName when creating endpoint Consumer account not in allowed principals, or wrong region Add principal; endpoint services are regional
Connection timeout Endpoint SG blocks the port, or NLB targets unhealthy Allow 443 from app SG on sg-endpoint; check target health
Private DNS not resolving Verification pending, --private-dns-enabled off, or VPC DNS hostnames disabled Check verification state; enable enableDnsHostnames and enableDnsSupport
Works in one AZ only Provider not in the consumer's AZ ID Add NLB subnet in that AZ ID or enable cross-zone
Targets see NLB IPs, not clients Expected with PrivateLink Enable Proxy Protocol v2 on the target group and read the TLV with the endpoint ID
aws elbv2 describe-target-health --target-group-arn $TG --output table
aws elbv2 modify-target-group-attributes --target-group-arn $TG --attributes Key=proxy_protocol_v2.enabled,Value=true

Prevent it next time

  • Publish an onboarding page: service name, port, AZ IDs supported, how to request access.
  • Monitor NLB UnHealthyHostCount and endpoint service metrics; alert the provider team, not the consumers.
  • Use Proxy Protocol v2 to log which consumer endpoint each request came from.