Multi-VPC Networking with PrivateLink
Build a private service mesh across 3 VPCs using AWS PrivateLink and NLB.
Introduction
30 Second Summary
When companies handle sensitive financial data, their internal services need to communicate without ever touching the public internet. This is how enterprise networks stay secure at scale.
In this project, you will build a multi-VPC architecture on AWS where an internal service is privately accessible to consumer VPCs through AWS PrivateLink.
What You'll Build
A fintech-style multi-VPC networking architecture where an internal API in a Shared Services VPC is privately exposed to consumer VPCs via a Network Load Balancer and VPC Endpoint Service.
By the end of this project, you'll have:
- Three isolated VPCs (Payments, Analytics, Shared Services) simulating an enterprise fintech environment with proper network isolation.
- A working PrivateLink service published from the Shared Services VPC, accessible privately from consumer VPCs without traversing the public internet.
- Verified end-to-end private connectivity from a consumer VPC test client to the shared internal service.
- Secret Mission: Add a VPC Endpoint Policy to restrict which actions and resources consumer VPCs can access through the endpoint.
Are there any prerequisites?
You need an AWS account with admin access and the AWS CLI installed and configured on your machine. Familiarity with basic AWS networking concepts (like VPCs and subnets) is helpful but not required.
Create the Multi-VPC Infrastructure
Every enterprise eventually outgrows a single network. In fintech, services like Payments, Analytics, and Shared Services each need their own isolated network boundary for security and compliance.
In this step, you will create three VPCs that simulate this enterprise layout. You will also configure the networking plumbing (subnets, security groups, a NAT Gateway) that later steps will build on.
In this step, get ready to:
- Create three isolated VPCs with private subnets.
- Configure security groups for network access control.
- Set up internet connectivity for instance provisioning.
Create three VPCs with private subnets
Each VPC represents an isolated network segment. You will create the Shared Services VPC first (this is where the internal API will live), then the Payments and Analytics consumer VPCs.
- Open the AWS Management Console and confirm your region is set to us-east-1 (N. Virginia) in the top-right corner.
- Search for VPC in the top search bar and select the VPC service.
- On the VPC dashboard, click Create VPC.
- For Resources to create, select VPC only.
- For Name tag, enter shared-services-vpc.
- For IPv4 CIDR block, enter 10.2.0.0/16.
- Leave all other settings as default and click Create VPC.
You should see a success banner confirming the VPC was created.
Why VPC only instead of VPC and more?
The VPC and more option auto-creates subnets, route tables, and gateways with AWS-chosen CIDR ranges. For this project, you need precise control over every subnet CIDR and placement, so creating each component individually is the right approach.
Now enable DNS hostnames on this VPC so that PrivateLink endpoints can resolve correctly later.
- In the left navigation pane, click Your VPCs.
- Select the checkbox next to shared-services-vpc.
- Click Actions, then Edit VPC settings.
- Check the box next to Enable DNS hostnames.
- Confirm Enable DNS resolution is also checked (it should be by default).
- Click Save.
Why enable DNS hostnames?
PrivateLink interface endpoints rely on DNS to route traffic. Both enableDnsHostnames and enableDnsSupport must be true for private DNS names on VPC endpoints to work.
Repeat the same process to create the other two VPCs.
- Return to the VPC dashboard and click Create VPC.
- Select VPC only.
- For Name tag, enter payments-vpc.
- For IPv4 CIDR block, enter 10.0.0.0/16.
- Click Create VPC.
- Enable DNS hostnames on payments-vpc using the same Actions > Edit VPC settings flow.
- Click Create VPC one more time.
- Select VPC only.
- For Name tag, enter analytics-vpc.
- For IPv4 CIDR block, enter 10.1.0.0/16.
- Click Create VPC.
- Enable DNS hostnames on analytics-vpc using the same Actions > Edit VPC settings flow.
Now create a private subnet in each VPC.
- In the left navigation pane, click Subnets.
- Click Create subnet.
- For VPC ID, select shared-services-vpc.
- For Subnet name, enter shared-services-private-subnet.
- For Availability Zone, select us-east-1a.
- For IPv4 subnet CIDR block, enter 10.2.1.0/24.
- Click Create subnet.
Repeat for the Payments VPC.
- Click Create subnet.
- For VPC ID, select payments-vpc.
- For Subnet name, enter payments-private-subnet.
- For Availability Zone, select us-east-1a.
- For IPv4 subnet CIDR block, enter 10.0.1.0/24.
- Click Create subnet.
Repeat for the Analytics VPC.
- Click Create subnet.
- For VPC ID, select analytics-vpc.
- For Subnet name, enter analytics-private-subnet.
- For Availability Zone, select us-east-1a.
- For IPv4 subnet CIDR block, enter 10.1.1.0/24.
- Click Create subnet.
Navigate back to Your VPCs in the left sidebar. You should see all three VPCs listed with their correct CIDR ranges.
Don't see all three VPCs?
Make sure your region is set to us-east-1 in the top-right corner. VPCs are region-specific and will only appear in the region where they were created.
Also check that you did not accidentally select VPC and more instead of VPC only. If you did, the VPC may have a different name or auto-generated subnets.
help me troubleshoot why my VPCs are not showing in the AWS Console
Create security groups for each VPC
Security groups act as virtual firewalls controlling which traffic can reach your resources. Each VPC needs a security group that allows TCP port 80 traffic from within its own CIDR range.
- In the left navigation pane, click Security groups.
- Click Create security group.
- For Security group name, enter shared-services-sg.
- For Description, enter Allow HTTP from Shared Services VPC.
- For VPC, select shared-services-vpc.
- Under Inbound rules, click Add rule.
- Set Type to HTTP (this auto-fills port 80).
- Set Source to Custom and enter 10.2.0.0/16.
- Remove any default outbound rules if present (this security group only needs the inbound rule).
- Click Create security group.
Now create the Payments VPC security group.
- Click Create security group.
- For Security group name, enter payments-endpoint-sg.
- For Description, enter Allow HTTP from Payments VPC.
- For VPC, select payments-vpc.
- Under Inbound rules, click Add rule.
- Set Type to HTTP and Source to Custom with value 10.0.0.0/16.
- Leave the default outbound rule (All traffic to 0.0.0.0/0) in place.
- Click Create security group.
Finally, create the Analytics VPC security group.
- Click Create security group.
- For Security group name, enter analytics-endpoint-sg.
- For Description, enter Allow HTTP from Analytics VPC.
- For VPC, select analytics-vpc.
- Under Inbound rules, click Add rule.
- Set Type to HTTP and Source to Custom with value 10.1.0.0/16.
- Leave the default outbound rule (All traffic to 0.0.0.0/0) in place.
- Click Create security group.
Why restrict inbound to each VPC's own CIDR?
Each security group only allows HTTP traffic from within its own VPC's IP range. This ensures that even if traffic somehow crosses VPC boundaries, it cannot reach resources unless it originated from the expected network.
The consumer VPC security groups (payments-endpoint-sg and analytics-endpoint-sg) keep outbound open because they need to initiate connections to the PrivateLink endpoint.
Can't find your VPC in the security group dropdown?
The VPC dropdown only shows VPCs in the currently selected region. Confirm the region in the top-right corner is us-east-1.
If you see an older default VPC but not your new ones, clear any filters in the search field above the dropdown.
help me find my VPC in the security group creation form
Set up connectivity for instance provisioning
The EC2 instance you will deploy in Step 2 lives in a private subnet with no direct internet access. However, it needs outbound internet connectivity so the SSM Agent can phone home to AWS Systems Manager.
The solution: a public subnet with a NAT Gateway that routes the private subnet's outbound traffic through an Internet Gateway.
- In the left navigation pane, click Internet gateways.
- Click Create internet gateway.
- For Name tag, enter shared-services-igw.
- Click Create internet gateway.
- On the confirmation page, click Actions, then Attach to VPC.
- Select shared-services-vpc and click Attach internet gateway.
Now create the public subnet where the NAT Gateway will live.
- In the left navigation pane, click Subnets.
- Click Create subnet.
- For VPC ID, select shared-services-vpc.
- For Subnet name, enter shared-services-public-subnet.
- For Availability Zone, select us-east-1a.
- For IPv4 subnet CIDR block, enter 10.2.2.0/24.
- Click Create subnet.
The public subnet needs a route to the Internet Gateway so the NAT Gateway can reach the internet.
- In the left navigation pane, click Route tables.
- Click Create route table.
- For Name, enter shared-services-public-rt.
- For VPC, select shared-services-vpc.
- Click Create route table.
- With the route table selected, click the Routes tab, then Edit routes.
- Click Add route.
- For Destination, enter 0.0.0.0/0.
- For Target, select Internet Gateway and choose shared-services-igw.
- Click Save changes.
- Click the Subnet associations tab, then Edit subnet associations.
- Select shared-services-public-subnet and click Save associations.
Now create the NAT Gateway in the public subnet.
- In the left navigation pane, click NAT gateways.
- Click Create NAT gateway.
- For Name, enter shared-services-nat-gateway.
- For Subnet, select shared-services-public-subnet.
- For Connectivity type, leave it as Public.
- Click Allocate Elastic IP to assign a public IP address to the NAT Gateway.
- Click Create NAT gateway.
Why does a NAT Gateway need a public subnet?
A NAT Gateway translates outbound traffic from private instances so it appears to come from the NAT Gateway's public Elastic IP. It must sit in a subnet that has a route to an Internet Gateway so it can forward that traffic to the internet.
The NAT Gateway takes 1-2 minutes to reach the Available state. While it provisions, create the private subnet's route table.
- In the left navigation pane, click Route tables.
- Click Create route table.
- For Name, enter shared-services-private-rt.
- For VPC, select shared-services-vpc.
- Click Create route table.
- With the route table selected, click the Routes tab, then Edit routes.
- Click Add route.
- For Destination, enter 0.0.0.0/0.
- For Target, select NAT Gateway and choose shared-services-nat-gateway.
- Click Save changes.
- Click the Subnet associations tab, then Edit subnet associations.
- Select shared-services-private-subnet and click Save associations.
Navigate back to NAT gateways and confirm the state shows Available for shared-services-nat-gateway.
NAT Gateway stuck in Pending?
NAT Gateways typically take 1-2 minutes to provision. If it stays in Pending for more than 5 minutes, check that the Elastic IP was allocated successfully and that the public subnet has a route to the Internet Gateway.
help me troubleshoot my NAT Gateway that is stuck in pending state
Why PrivateLink instead of VPC Peering?
VPC Peering creates a full bidirectional route between two VPCs. That means both sides can initiate connections to each other, and overlapping CIDR ranges are not allowed.
PrivateLink is purpose-built for the service-provider/consumer model. It is unidirectional: only the consumer VPC can initiate connections to the provider. It also handles overlapping CIDRs gracefully because it uses an ENI with a private IP in the consumer's own subnet.
Your multi-VPC foundation is complete. Next up, you will deploy an internal HTTP service into the Shared Services VPC and front it with a Network Load Balancer for PrivateLink compatibility.
Deploy the Shared Internal Service
Your three VPCs are in place with proper network isolation. Now you need a service that the consumer VPCs will access privately.
In this step, you will launch an EC2 instance inside the Shared Services private subnet and configure it to run a simple HTTP API. This instance will serve as the internal service that the Payments and Analytics VPCs will consume through PrivateLink later in the project.
In this step, get ready to:
- Launch an EC2 instance with an HTTP service in the Shared Services private subnet.
- Verify the service responds correctly via Session Manager.
Launch the shared services application instance
Before launching the EC2 instance, you need an IAM role that grants the instance permission to communicate with AWS Systems Manager. This enables Session Manager access so you can connect to the instance without SSH or a public IP.
- Navigate to the IAM console by searching for IAM in the top search bar and selecting it.
- In the left navigation pane, click Roles.
- Click Create role.
- For Trusted entity type, select AWS service.
- For Use case, select EC2, then click Next.
- On the Add permissions page, search for AmazonSSMManagedInstanceCore.
- Check the box next to AmazonSSMManagedInstanceCore, then click Next.
- For Role name, enter shared-services-ssm-role.
- Click Create role.
What is this role for?
The AmazonSSMManagedInstanceCore policy grants your EC2 instance the permissions it needs to register with AWS Systems Manager. This lets you use Session Manager to open a terminal session on the instance without needing SSH keys or a public IP address.
Now launch the EC2 instance that will run your internal HTTP service.
- Navigate to the EC2 console by searching for EC2 in the top search bar.
- Click Launch instance.
- For Name, enter shared-services-app.
- Under Application and OS Images, confirm that Amazon Linux 2023 is selected (it is the default).
- For Instance type, select t2.micro (or t3.micro if t2.micro is unavailable).
- Under Key pair (login), select Proceed without a key pair from the dropdown. You will use Session Manager instead of SSH.
- Under Network settings, click Edit.
- For VPC, select shared-services-vpc.
- For Subnet, select shared-services-private-subnet (10.2.1.0/24).
- For Auto-assign public IP, confirm it is set to Disable. This instance lives in a private subnet.
- Under Firewall (security groups), select Select existing security group.
- Choose shared-services-sg from the list.
Next, attach the IAM role and add the user data script.
- Expand the Advanced details section at the bottom of the page.
- For IAM instance profile, select shared-services-ssm-role from the dropdown.
- Scroll down to the User data field and paste the following script:
#!/bin/bash
# Update packages and install Python dependencies
yum update -y
# Create a simple HTTP service using Python
mkdir -p /opt/service
cat > /opt/service/app.py << 'EOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class ServiceHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
response = {
"service": "shared-internal-service",
"status": "healthy",
"vpc": "shared-services"
}
self.wfile.write(json.dumps(response).encode())
def log_message(self, format, *args):
pass
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 80), ServiceHandler)
server.serve_forever()
EOF
# Create a systemd service to keep it running
cat > /etc/systemd/system/shared-service.service << 'EOF'
[Unit]
Description=Shared Internal Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/service/app.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Start the service
systemctl daemon-reload
systemctl enable shared-service
systemctl start shared-service
What does this user data script do?
This script runs automatically when the instance first boots. It creates a lightweight Python HTTP server that listens on port 80 and returns a JSON response identifying itself as the shared internal service.
The script also registers the server as a systemd service so it restarts automatically if the instance reboots. Amazon Linux 2023 ships with Python 3 pre-installed, so no additional packages are needed for the HTTP server.
- Click Launch instance.
- Wait for the instance to reach the Running state. This takes 1-2 minutes.
Instance stuck in Pending?
If your instance stays in Pending for more than 3 minutes, check that you selected the correct subnet (shared-services-private-subnet in shared-services-vpc). Also confirm the NAT Gateway in your Shared Services VPC is in the Available state.
help me troubleshoot why my EC2 instance is stuck in Pending state in a private subnet
Verify the service is running
The instance needs a couple of minutes after reaching the Running state for the user data script to finish executing. Give it about 2 minutes, then connect via Session Manager to confirm the HTTP service is responding.
- In the EC2 console, select the shared-services-app instance.
- Click Connect at the top of the page.
- Select the Session Manager tab.
- Click Connect. A terminal session opens in your browser.
Connect button greyed out?
If the Connect button is greyed out, the SSM Agent on the instance has not registered yet. Confirm that you attached the shared-services-ssm-role IAM instance profile during launch. Also verify the private subnet route table has a route to the NAT Gateway, which the SSM Agent uses to reach the Systems Manager endpoints.
help me fix Session Manager connectivity for my EC2 instance in a private subnet
Test the HTTP service by running this command in the Session Manager terminal:
- Run this command to test the service:
curl http://localhost
You should see a JSON response like this:
{"service": "shared-internal-service", "status": "healthy", "vpc": "shared-services"}
- Confirm the instance has a private IP in the 10.2.1.0/24 range by running:
hostname -I
The output should show an IP address starting with 10.2.1. confirming the instance is in the correct private subnet.
Why does the IP matter?
This private IP proves the instance sits inside the Shared Services VPC with no public internet exposure. Later, the Network Load Balancer will route traffic to this IP, and PrivateLink will expose the NLB to consumer VPCs without ever touching the public internet.
Curl returns connection refused?
The user data script may still be running. Wait another minute and try again. If it still fails, check the service status by running systemctl status shared-service in the Session Manager terminal. Look for errors in the output.
help me debug why my Python HTTP service isn't responding to curl on localhost port 80
Your internal service is running and reachable within the Shared Services VPC. Next up, you will place a Network Load Balancer in front of this service to make it compatible with PrivateLink.
Create the Network Load Balancer
Your shared services application is running and responding to HTTP requests in its private subnet. The next step is to make this service available to other VPCs through PrivateLink.
PrivateLink requires a Network Load Balancer to front your service. The NLB acts as the entry point that PrivateLink uses to route traffic from consumer VPCs to your service. In this step, you will create a target group, register your EC2 instance, and set up an internal NLB.
In this step, get ready to:
- Create a target group and register the EC2 instance.
- Create an internal Network Load Balancer.
- Verify the NLB is active and the target is healthy.
Create a target group and register the EC2 instance
A target group tells the load balancer where to send traffic. You will create one that points to your shared services EC2 instance on port 80.
- In the AWS Console, type EC2 in the top search bar and press Enter to open the EC2 Dashboard.
- In the left sidebar, scroll down to the Load Balancing section and click Target Groups.
- Click Create target group.
- Under Choose a target type, select Instances.
- For Target group name, enter shared-services-tg.
- For Protocol, select TCP.
- For Port, enter 80.
- For VPC, select shared-services-vpc.
- In the Health checks section, confirm the protocol is set to TCP and the port is 80. These are the defaults for a TCP target group.
- Click Next.
- On the Register targets page, select the shared-services-app instance from the list.
- Click Include as pending below.
- Click Create target group.
What is a target group?
A target group is a collection of resources (EC2 instances, IP addresses, or other services) that receive traffic from a load balancer. The NLB performs health checks against each target and only routes traffic to healthy ones.
By choosing target type "Instances" and protocol TCP on port 80, you are telling the NLB to forward raw TCP connections to port 80 on your registered EC2 instance.
You should see shared-services-tg listed in the Target Groups console with your instance registered.
Don't see your instance in the register targets list?
Make sure you selected shared-services-vpc as the VPC in the target group settings. Only instances within that VPC will appear for registration.
Also confirm your shared-services-app instance is in a running state. Stopped or terminated instances will not appear.
help me register my EC2 instance as a target in my NLB target group
Create the Network Load Balancer
Now you will create an internal NLB that sits in front of your service. The internal scheme means the NLB only has private IP addresses and is not accessible from the internet.
- In the left sidebar under Load Balancing, click Load Balancers.
- Click Create load balancer.
- Under Network Load Balancer, click Create.
- For Load balancer name, enter shared-services-nlb.
- For Scheme, select Internal.
- For IP address type, keep IPv4 selected.
- In the Network mapping section, for VPC, select shared-services-vpc.
- Under Availability Zones, check the box for us-east-1a.
- Select shared-services-private-subnet (10.2.1.0/24) as the subnet.
- In the Listeners and routing section, confirm the listener is set to TCP on port 80. This is the default.
- For Default action, select your shared-services-tg target group from the dropdown.
- Scroll down and click Create load balancer.
Why a Network Load Balancer?
AWS PrivateLink requires a Network Load Balancer to front your service. You cannot use an Application Load Balancer directly for this purpose. The NLB operates at layer 4 (TCP/UDP) and provides the stable network interface that PrivateLink needs to create endpoint connections.
The internal scheme is also required. Only internal NLBs can be associated with a VPC Endpoint Service because the service must be accessed via private IP addresses, not from the internet.
Target group not appearing in the dropdown?
Make sure shared-services-tg was created with protocol TCP. The NLB listener uses TCP, and the target group protocol must match the listener protocol.
Also verify the target group was created in shared-services-vpc. Target groups in a different VPC will not appear for this load balancer.
help me troubleshoot why my target group is not showing in the NLB listener dropdown
Verify NLB health
The NLB takes 2-3 minutes to provision after creation. You need to confirm it reaches Active state and that your target is healthy before proceeding.
- On the Load Balancers page, find shared-services-nlb in the list.
- Wait for the State column to change from Provisioning to Active. This typically takes 2-3 minutes.
- Once active, note the DNS name value. It follows the format shared-services-nlb-xxxx.elb.us-east-1.amazonaws.com.
- In the left sidebar, click Target Groups.
- Click on shared-services-tg to open its details.
- Select the Targets tab.
- Confirm the shared-services-app instance shows a Health status of Healthy.
What does healthy mean?
The NLB sends TCP connection attempts to port 80 on your registered instance at regular intervals. If the connection succeeds, the target is marked as healthy. Since your HTTP server is running on port 80 and accepting connections, it passes these health checks.
Target showing as unhealthy?
Verify that the shared-services-sg security group allows inbound TCP on port 80 from 10.2.0.0/16. The NLB health check originates from within the VPC CIDR range.
Also confirm your HTTP server is still running. Connect to the instance via Session Manager and run curl http://localhost to verify the service responds.
help me debug why my NLB target is showing unhealthy
Your internal NLB is active and routing traffic to the shared services instance. Next, you will publish this NLB as a VPC Endpoint Service so that consumer VPCs can access it privately through PrivateLink.
Publish the PrivateLink Endpoint Service
Your Network Load Balancer is active and healthy, sitting inside the Shared Services VPC. But right now, only resources within that same VPC can reach it.
To let the Payments and Analytics VPCs connect privately, you need to publish the NLB as a VPC Endpoint Service. This creates a PrivateLink-powered entry point that consumer VPCs can subscribe to without any public internet exposure.
In this step, get ready to:
- Create a VPC Endpoint Service linked to your internal NLB.
- Configure allowed principals to control which accounts can connect.
Create the VPC Endpoint Service
A VPC Endpoint Service wraps your NLB and makes it discoverable to other VPCs through PrivateLink. Consumers never see your internal infrastructure. They only interact with a private endpoint in their own VPC.
- In the VPC console navigation pane, choose Endpoint services.
- Choose Create endpoint service.
- For Load balancer type, choose Network.
- For Available load balancers, select shared-services-nlb.
- For Require acceptance for endpoint, select Acceptance required.
- Leave Enable private DNS name unchecked. You will use the AWS-generated endpoint DNS name instead.
- For Supported IP address types, select IPv4.
- Choose Add new tag and enter Name as the key and shared-internal-service as the value.
- Choose Create.
What is a VPC Endpoint Service?
A VPC Endpoint Service is the provider side of PrivateLink. It publishes your NLB so that other VPCs can create interface endpoints that connect to it.
The connection is unidirectional. Only clients in the consumer VPC can initiate connections to the service in the provider VPC. The provider never reaches back into consumer networks.
After creation, AWS generates a service name in the format com.amazonaws.vpce.us-east-1.vpce-svc-xxxxxxxxxxxxxxxxx. Copy this service name and save it somewhere accessible. You will need it in the next step when creating interface endpoints in the consumer VPCs.
- Select your new endpoint service from the list.
- On the Details tab, find the Service name field and copy the full value.
- Confirm the Status shows Available.
Why enable acceptance required?
With acceptance required enabled, any connection request from a consumer VPC must be manually approved by the service owner. This prevents unauthorized consumers from connecting.
In production environments, this approval workflow is a critical security control. It ensures only vetted teams gain access to internal services.
Endpoint service stuck in Pending?
The endpoint service depends on an active NLB. If the NLB is still provisioning, the endpoint service will not transition to Available.
Return to the Load Balancers page in the EC2 console and confirm shared-services-nlb shows state Active. Wait a moment and refresh the endpoint service page.
Help me troubleshoot my endpoint service status.
Configure allowed principals
Even though the endpoint service exists, no one can connect to it until you explicitly grant access. You control this through the allowed principals list on the endpoint service.
- With your endpoint service still selected, choose the Allow principals tab.
- Choose Allow principals.
- For Principals to add, enter your AWS account ARN in the format:
arn:aws:iam::[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:root
- Choose Allow principals to save.
Why add your own account as an allowed principal?
In this project, the provider and consumer VPCs are all in the same AWS account. In production, the consumer VPCs would typically be in different accounts.
Adding your account ARN grants permission for any VPC in this account to create interface endpoints pointing at this service. Without this, endpoint creation in the consumer VPCs would be rejected.
Find your account ID quickly
Click your account name in the top-right corner of the AWS console. Your 12-digit account ID appears in the dropdown menu.
Getting a permissions error?
Make sure the ARN follows the exact format arn:aws:iam::<account-id>:root with no spaces or trailing characters. The account ID must be exactly 12 digits.
Help me fix my allowed principals configuration.
Your internal service is now published as a PrivateLink endpoint service. Next, you will create interface endpoints in the consumer VPCs and verify that traffic flows privately across VPC boundaries.
Connect Consumer VPCs and Verify Connectivity
Your endpoint service is live and ready to accept connections. Now it is time to connect the consumer VPCs (Payments and Analytics) to the shared internal service using Interface VPC Endpoints.
Each interface endpoint creates a network interface with a private IP inside the consumer VPC's subnet. Traffic flows over the AWS backbone without touching the public internet, proving the zero-trust connectivity pattern you are building.
In this step, get ready to:
- Create interface VPC endpoints in the Payments and Analytics VPCs.
- Launch a test client in the Payments VPC and verify private connectivity.
- Confirm the traffic path stays entirely private.
Create interface endpoints in consumer VPCs
An interface endpoint places an elastic network interface (ENI) directly inside your consumer subnet. Any traffic sent to that ENI routes privately through PrivateLink to the provider's NLB.
- In the VPC console navigation pane, choose Endpoints.
- Click Create endpoint.
- For Type, select Endpoint services that use NLBs and GWLBs.
- In the Service name field, paste the endpoint service name you noted in the previous step (format: com.amazonaws.vpce.us-east-1.vpce-svc-xxxxxxxxxxxxxxxxx).
- Click Verify service. You should see a green confirmation that the service name was found.
- For VPC, select payments-vpc.
- For Subnets, expand the us-east-1a Availability Zone and select payments-private-subnet (10.0.1.0/24).
- For Security groups, remove the default security group and select payments-endpoint-sg.
- Click Create endpoint.
The endpoint status shows as Pending acceptance because you enabled the acceptance workflow on the endpoint service.
Why is acceptance required?
In production, the acceptance workflow prevents unauthorized consumers from connecting to your internal services. The service provider must explicitly approve each connection request before traffic can flow.
- In the navigation pane, choose Endpoint services.
- Select your shared-internal-service endpoint service.
- Open the Endpoint connections tab.
- Select the pending connection from the Payments VPC endpoint.
- Click Actions, then Accept endpoint connection request.
- Type accept in the confirmation prompt and confirm.
Wait 1-2 minutes for the endpoint status to change to Available.
- Now repeat the same process for the Analytics VPC. Navigate back to Endpoints and click Create endpoint.
- For Type, select Endpoint services that use NLBs and GWLBs.
- Paste the same endpoint service name and click Verify service.
- For VPC, select analytics-vpc.
- For Subnets, expand us-east-1a and select analytics-private-subnet (10.1.1.0/24).
- For Security groups, remove the default and select analytics-endpoint-sg.
- Click Create endpoint.
- Return to Endpoint services, select shared-internal-service, open the Endpoint connections tab, select the new pending connection, and accept it the same way.
Service name not found?
Double check that you copied the full service name including the com.amazonaws.vpce.us-east-1.vpce-svc- prefix. Also confirm that your AWS account ARN is listed as an allowed principal on the endpoint service.
help me troubleshoot why my VPC endpoint service name is not being found when I try to create an interface endpoint
Launch a test client and verify private connectivity
To prove the endpoint works, you need a test instance inside the Payments VPC that can reach the endpoint's DNS name. This instance needs SSM Session Manager access, which requires a route to the SSM service endpoints.
- First, set up internet connectivity for the Payments VPC (needed for SSM agent). In the VPC console, navigate to Internet gateways.
- Click Create internet gateway.
- Name it payments-igw and click Create internet gateway.
- Select the new IGW, click Actions, then Attach to VPC.
- Select payments-vpc and click Attach internet gateway.
- Navigate to Subnets and click Create subnet.
- For VPC, select payments-vpc.
- Name the subnet payments-public-subnet.
- Select us-east-1a as the Availability Zone.
- Enter 10.0.2.0/24 as the IPv4 CIDR block.
- Click Create subnet.
- Navigate to NAT gateways and click Create NAT gateway.
- Name it payments-nat-gateway.
- For Subnet, select payments-public-subnet.
- Click Allocate Elastic IP to assign a public IP.
- Click Create NAT gateway.
- Navigate to Route tables.
- Find the route table associated with payments-public-subnet (or create a new one and associate it). Add a route with destination 0.0.0.0/0 and target payments-igw.
- Find the route table associated with payments-private-subnet. Add a route with destination 0.0.0.0/0 and target the payments-nat-gateway you just created.
Why do you need a NAT Gateway here?
The test instance runs in a private subnet with no public IP. The SSM agent on the instance needs to reach the AWS Systems Manager service endpoints over HTTPS. The NAT Gateway provides that outbound path without giving the instance direct internet exposure.
- Navigate to EC2 in the AWS Console and click Launch instances.
- Name the instance payments-test-client.
- Select Amazon Linux 2023 as the AMI.
- Choose t2.micro (or t3.micro) as the instance type.
- Under Network settings, click Edit.
- For VPC, select payments-vpc.
- For Subnet, select payments-private-subnet.
- For Auto-assign public IP, select Disable.
- For Firewall (security groups), select Select existing security group and choose payments-endpoint-sg.
- Under Advanced details, for IAM instance profile, select the same SSM role you created earlier (with the AmazonSSMManagedInstanceCore policy).
- Click Launch instance.
Wait 2-3 minutes for the instance to pass its status checks and register with Systems Manager.
- Select the payments-test-client instance and click Connect.
- Choose the Session Manager tab and click Connect.
Session Manager button is grayed out?
This usually means the SSM agent cannot reach the Systems Manager service. Confirm your NAT Gateway is in the Available state and that the private subnet route table has a 0.0.0.0/0 route pointing to the NAT Gateway. Also verify the IAM instance profile is attached.
help me troubleshoot why Session Manager cannot connect to my EC2 instance in a private subnet
- Return to the VPC console and navigate to Endpoints.
- Select the Payments VPC endpoint you created earlier.
- In the Details tab, find the DNS names section. Copy the first DNS name (the regional one). It follows the format vpce-xxxxxxxxxxxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.us-east-1.vpce.amazonaws.com.
- Back in your Session Manager terminal, run the following curl command, replacing the DNS name with the one you copied:
curl http://vpce-xxxxxxxxxxxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.us-east-1.vpce.amazonaws.com
You should see the JSON response from your shared services application, confirming end-to-end private connectivity across VPC boundaries.
Curl times out or connection refused?
Check that the Payments VPC endpoint status shows Available (not Pending acceptance). If you forgot to accept the connection request, go back to Endpoint services and accept it now.
Also confirm that payments-endpoint-sg allows inbound TCP port 80 from 10.0.0.0/16 and that shared-services-sg allows inbound TCP port 80 from 10.2.0.0/16.
help me debug why my curl request to the VPC endpoint DNS name is timing out
Confirm the traffic path is private
The final piece of evidence is confirming that all connectivity stays within private IP ranges. No public IPs, no internet gateways, and no NAT are involved in the actual service communication path.
- In the VPC console, navigate to Endpoints and select the Payments VPC endpoint.
- Open the Subnets tab and note the Network interface ID listed for the endpoint.
- Click the network interface ID to open it in the EC2 Network Interfaces view.
- Confirm the Primary private IPv4 address falls within the 10.0.1.0/24 range (Payments private subnet).
What does this private IP prove?
The endpoint's ENI lives entirely within the Payments VPC CIDR (10.0.0.0/16). When the test client sends a request, it goes to a local private IP in the same VPC. AWS routes that traffic internally through PrivateLink to the provider's NLB. No Internet Gateway, NAT Gateway, or public IP is involved in this service-to-service communication path.
This is the core value of PrivateLink: private, backbone-only connectivity between VPCs without exposing any endpoints to the public internet.
- Optionally, verify the Analytics VPC endpoint as well. Select it in the Endpoints list and confirm its ENI has a private IP within the 10.1.1.0/24 range.
What is the full connectivity picture?
Your architecture now has three layers of isolation working together. VPC boundaries prevent any direct routing between networks. Security groups on the endpoint ENIs restrict which traffic reaches the interface. The endpoint service acceptance workflow controls who is allowed to connect in the first place.
Together, these create a defense-in-depth model where the shared service is never exposed to the internet, and only explicitly approved consumer VPCs can reach it through private IP addresses.
You have just proved that a consumer VPC can reach a shared internal service entirely over private IPs, with zero internet exposure. This is the production-grade PrivateLink pattern used by fintech companies to secure cross-VPC service communication.
Secret mission
Add VPC Endpoint Policies for Fine-Grained Access Control
Your PrivateLink architecture is secure at the network level. But what if you want to control which IAM principals can actually use the endpoint? VPC Endpoint Policies add an IAM-based access control layer directly on the endpoint itself, creating true defense-in-depth without any internet exposure.
Clean Up Your Resources
Clean Up Your Resources
This project uses AWS resources that accumulate hourly charges while running. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
Your Network Load Balancer costs $0.0225/hr, each NAT Gateway costs $0.045/hr, and each Interface VPC Endpoint costs $0.01/hr per AZ. Left running, these resources accumulate approximately $0.14/hr total.
If you are done with this project, delete all resources now to avoid unexpected charges on your next AWS bill.
Resources you used:
- 3 VPCs (payments-vpc, analytics-vpc, shared-services-vpc) with subnets, route tables, and security groups.
- 2 EC2 instances (shared-services-app, payments-test-client).
- 1 Internal Network Load Balancer (shared-services-nlb) with target group.
- 1 VPC Endpoint Service (shared-internal-service).
- 2 Interface VPC Endpoints (payments-vpc-endpoint, analytics-vpc-endpoint).
- 1 NAT Gateway with Elastic IP in shared-services-vpc.
- 1 Internet Gateway attached to shared-services-vpc.
- 1 IAM instance profile and role for SSM access.
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the architecture in the next hour or two.
- Your NLB, NAT Gateway, and VPC Endpoints will continue accumulating hourly charges.
- EC2 instances will continue running (covered by free tier if eligible, otherwise ~$0.012/hr each).
- Come back to this section when you are ready to pause or delete.
Pause - I'll come back to this later
Stop EC2 instances to save compute costs. Note that the NLB, NAT Gateway, and VPC Endpoints still charge hourly even when instances are stopped.
- In the EC2 console, select the shared-services-app instance.
- Click Instance state then Stop instance.
- Repeat for the payments-test-client instance.
- To fully pause costs, you also need to delete the NLB, NAT Gateway, and VPC Endpoints. Consider the Delete tab if you want to eliminate all hourly charges.
Delete - I don't want to use this again
Remove all project resources to stop all charges. Delete in this order to avoid dependency errors.
Delete VPC Endpoints first
- In the VPC console, select Endpoints in the left navigation.
- Select payments-vpc-endpoint, click Actions, then Delete VPC endpoints.
- Type delete to confirm.
- Repeat for analytics-vpc-endpoint.
Delete the endpoint service
- Select Endpoint services in the left navigation.
- Select shared-internal-service, click Actions, then Delete endpoint services.
- Type delete to confirm.
Delete the Network Load Balancer and target group
- In the EC2 console, select Load Balancers under Load Balancing in the left navigation.
- Select shared-services-nlb, click Actions, then Delete load balancer.
- Type confirm and click Delete.
- Select Target Groups in the left navigation.
- Select shared-services-tg, click Actions, then Delete.
Delete the NAT Gateway and release the Elastic IP
- In the VPC console, select NAT gateways in the left navigation.
- Select your NAT Gateway in shared-services-vpc, click Actions, then Delete NAT gateway.
- Type delete to confirm.
- If you added a NAT Gateway to the Payments VPC for the test client, delete that one too.
- After the NAT Gateway state changes to Deleted, go to the EC2 console and select Elastic IPs under Network & Security.
- Select the unassociated Elastic IP, click Actions, then Release Elastic IP addresses.
- Click Release to confirm.
Terminate EC2 instances
- In the EC2 console, select Instances in the left navigation.
- Select shared-services-app and payments-test-client.
- Click Instance state, then Terminate instance.
- Click Terminate to confirm.
Delete the Internet Gateway
- In the VPC console, select Internet gateways in the left navigation.
- Select the Internet Gateway attached to shared-services-vpc.
- Click Actions, then Detach from VPC.
- Once detached, click Actions again, then Delete internet gateway.
- Type delete to confirm.
Delete subnets, route tables, security groups, and VPCs
- Select Subnets in the left navigation.
- Filter by each VPC and delete all subnets you created (shared-services-private-subnet, shared-services-public-subnet, payments-private-subnet, analytics-private-subnet).
- Select Route tables in the left navigation. Delete any custom route tables you created (not the Main route table).
- Select Security groups in the left navigation. Delete shared-services-sg, payments-endpoint-sg, and analytics-endpoint-sg.
- Finally, select Your VPCs in the left navigation.
- Select each VPC (shared-services-vpc, payments-vpc, analytics-vpc), click Actions, then Delete VPC.
- Type delete to confirm each deletion.
Delete the IAM instance profile and role
- In the IAM console, select Roles in the left navigation.
- Find the role you created for SSM access.
- Select the role, click Delete, and type the role name to confirm.
- The associated instance profile is deleted automatically when the role is deleted.
Verify everything is deleted
Check the VPC console one final time. Filter by us-east-1 and confirm you only see the default VPC remaining. Check the EC2 console to confirm no instances, load balancers, or target groups remain.
Nice Work!
Nice Work!
You just built a production-grade private service mesh across three isolated VPCs. Your internal API is now accessible entirely over the AWS backbone, with zero internet exposure and multiple layers of security controlling access.
You've learned how to:
- Architect a multi-VPC fintech environment (Payments, Analytics, Shared Services) with proper network isolation, private subnets, and scoped security groups.
- Publish an internal service through AWS PrivateLink using a Network Load Balancer and a VPC Endpoint Service with a manual acceptance workflow.
- Connect consumer VPCs via Interface VPC Endpoints and verify end-to-end private connectivity using Session Manager and curl without any internet path.
- Secret Mission: Applied VPC Endpoint Policies for IAM-based fine-grained access control, proving defense-in-depth at the network layer.
Ready to quiz yourself?