Call Bedrock from an EC2 instance or a Lambda in a private subnet with default settings, and the request does not stay inside AWS the way most people assume. The SDK resolves the public bedrock-runtime DNS name, the packet walks out through a NAT gateway or an internet gateway, and it reaches the service over the public path. Traffic is encrypted with TLS, so this is not a leak. It is still a problem for anyone who has to prove, on paper, that model traffic never touches the internet. This part closes that path with an interface VPC endpoint, and shows what the setup costs once it is running.
bedrock for control-plane calls and bedrock-runtime for inference. Turn on private DNS so application code does not change. Scope the endpoint policy and add an IAM aws:sourceVpce condition so a call only succeeds through the endpoint. Budget roughly 7 dollars per endpoint per AZ per month before data.Your default Bedrock traffic leaves the VPC
A VPC, short for virtual private cloud, is your own isolated network inside a Region. When code in that network calls an AWS service, the request needs a route to reach it. For most services the default route is public: the service has a public DNS name and a public IP range, and your subnet reaches it through an internet gateway or a NAT gateway. Nothing about that is insecure by itself. The bytes are encrypted and AWS authenticates the caller with SigV4 request signing. What you lose is a hard guarantee. You cannot tell an auditor that a Bedrock request is physically incapable of egressing to the internet, because with the default route it can.
An interface VPC endpoint changes the route. It puts an entry point for Bedrock directly inside your subnets, so the request never leaves the Amazon network. This matters for regulated workloads, for accounts that run with no NAT gateway at all, and for a data perimeter where you block public egress and only allow named endpoints. It builds on the shared responsibility split from Part 2: AWS secures the service, and the network boundary around your call is your job.
What an interface endpoint builds in your subnets
When you create an interface endpoint, AWS drops an elastic network interface, an ENI, into each subnet you select. An ENI is a virtual network card with a private IP from that subnet. Your DNS resolution for the Bedrock name points at those private IPs, so the traffic lands on the ENI and rides PrivateLink to the service. You pick which Availability Zones to cover by choosing one subnet per AZ. Cover three AZs and you get three ENIs for that one endpoint, which is both the resilience story and the cost story, because billing is per AZ.
Two things trip people here. First, the endpoint has its own security group. That group controls who may reach the ENI, and it needs to allow inbound TCP 443 from the security groups your callers use. Miss that and the call does not fail cleanly. It hangs. Second, Bedrock is an interface-only service. S3 offers a free gateway endpoint, and people assume Bedrock has one too. It does not. Every Bedrock endpoint is a paid interface endpoint.
ConnectTimeoutError. Engineers chase IAM for an hour because the word timeout does not sound like a permissions problem. Check the endpoint security group first: it must allow 443 inbound from the caller security group.Which Bedrock endpoints do you actually need?
Bedrock splits its API across separate PrivateLink service names, and each one is a separate endpoint you pay for. If you only run inference, one endpoint covers it. If your app also lists models or reads configuration, you need the control-plane endpoint too. Agents and Knowledge Bases pull in more. Here is the map I use when sizing an account.
| Service name suffix | What it carries | Private DNS name |
|---|---|---|
bedrock | Control plane: model access, list models, config | bedrock.region.amazonaws.com |
bedrock-runtime | Inference: InvokeModel, Converse, streaming | bedrock-runtime.region.amazonaws.com |
bedrock-agent | Agent and Knowledge Base build-time | bedrock-agent.region.amazonaws.com |
bedrock-agent-runtime | Agent invocation, retrieve for RAG | bedrock-agent-runtime.region.amazonaws.com |
bedrock-runtime-fips | FIPS 140 validated inference endpoint | bedrock-runtime-fips.region.amazonaws.com |
The FIPS endpoints exist in us-east-1, us-east-2, us-west-2, ca-central-1, us-gov-east-1, and us-gov-west-1. A Knowledge Base also needs an S3 gateway endpoint, which is free, plus an interface endpoint for the vector store such as OpenSearch Serverless.
My default for a plain inference workload is two endpoints, bedrock and bedrock-runtime. Skip the control-plane one only if your app never touches control-plane calls, which is rarer than it sounds because SDK helpers and health checks often list models on startup.
Private DNS, on or off
When you create the endpoint you get one switch that matters more than any other: enable private DNS name. Turn it on and the standard Regional DNS name, bedrock-runtime.us-east-1.amazonaws.com, resolves to the endpoint ENI inside your VPC. Your application code does not change. The same boto3 client that worked before now routes privately, because the name it already uses points somewhere new. Leave it off and you must pass an explicit endpoint URL in every client, which means touching every service that calls Bedrock. I turn it on in almost every case.
The one time to leave it off is when the VPC already needs the public Bedrock name to resolve publicly for some other reason, because private DNS is VPC-wide and hijacks that name for everything in the VPC. You also cannot enable private DNS on two endpoints for the same service in the same VPC. The second one will refuse.
Lock the policy to your VPC
An endpoint with private DNS routes traffic privately, but by default it still allows every Bedrock action for every principal. Two controls tighten that. The first is the endpoint policy, a resource policy attached to the endpoint that says which principals may do which actions through it. The second is an IAM policy on your caller with an aws:sourceVpce condition, which says the caller may reach Bedrock only when the request arrives through your endpoint. Use both. The endpoint policy is the gate on the endpoint. The IAM condition stops the same role from calling Bedrock over some other path.
A least-privilege endpoint policy for a single app role and a single model looks like this.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/genai-app" },
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-4-6-v1"
}]
}The matching IAM policy on the role pins access to the endpoint. Replace the value with your real endpoint ID.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["bedrock:InvokeModel"],
"Resource": "*",
"Condition": {
"StringEquals": { "aws:sourceVpce": "vpce-029dea71225152fde" }
}
}]
}
My take
Ship the endpoint policy as Allow with the exact actions and model ARNs you use, not a wildcard. It is a second, network-anchored place to enforce which models an app may call, and it survives even if someone loosens the IAM role by mistake. I have caught an over-broad role because the endpoint policy still said no. Defense in depth is only real when the two layers disagree at the right moment.
What does private access cost?
Interface endpoints are billed two ways: a charge for each hour the endpoint is provisioned in each Availability Zone, and a charge for data processed. As of mid 2026, PrivateLink lists 0.01 dollars per AZ per hour and 0.01 dollars per GB for the first petabyte in a Region. A partial hour bills as a full hour, and you pay for every AZ whether or not traffic flows through it. Spread one endpoint across three AZs and you are paying for three ENIs around the clock. That is the number people forget when they scatter endpoints across dev, test, and staging accounts.
| Component | Rate | Quantity | Monthly |
|---|---|---|---|
| bedrock-runtime endpoint | 0.01 per AZ-hour | 3 AZ x 730 hr | 21.90 |
| bedrock control-plane endpoint | 0.01 per AZ-hour | 3 AZ x 730 hr | 21.90 |
| Data processed | 0.01 per GB | 300 GB | 3.00 |
| Total | 46.80 |
All figures in US dollars, us-east-1 list price, 730 hours per month. Model inference tokens are billed separately by Bedrock and are not part of this table.
Worked example
A team runs the same two endpoints in four accounts: prod, staging, dev, and a sandbox. Each account covers three AZs. That is 4 accounts x 2 endpoints x 3 AZs = 24 ENIs, at 7.30 dollars each per month, so 175.20 dollars a month in endpoint-hours before any data or inference. The sandbox and dev endpoints sit idle most of the week.
Cutting dev and sandbox to a single AZ, or to the runtime endpoint only, drops those two accounts from 43.80 to about 7.30 dollars each. New monthly total near 102 dollars. Same private guarantee where it counts, 73 dollars a month back.
Failure modes that look like something else
Most private Bedrock incidents I have seen are not Bedrock problems. They are network problems wearing a Bedrock error message. Three recur.
The connection that hangs. Covered above: the endpoint security group does not allow 443 from the caller. The symptom is a timeout, not a denial. Fix the inbound rule.
The call that works but still egresses. You created the endpoint but never enabled private DNS, and you did not pass an endpoint URL either, so the default name still resolves publicly. The app works, which is why nobody notices, and the compliance guarantee you thought you bought is not there. Confirm resolution from inside the subnet: the Bedrock name should return a private IP, not a public one.
The cross-Region surprise. If you use a cross-Region inference profile from Part 8, Bedrock may route the request to a second Region for capacity. Your interface endpoint is Regional. It does not follow the request. For a strict private posture you either avoid cross-Region profiles or stand up endpoints in every Region the profile can reach. Decide this on purpose, because the routing is invisible until an audit asks where a given request ran.
Building and testing the endpoint
Here is the whole thing end to end. Create the runtime endpoint across three subnets with private DNS on, then prove the path from a private instance.
aws ec2 create-vpc-endpoint --vpc-endpoint-type Interface --vpc-id vpc-0abc123 --service-name com.amazonaws.us-east-1.bedrock-runtime --subnet-ids subnet-0aa subnet-0bb subnet-0cc --security-group-ids sg-0privatelink --private-dns-enabled # State starts as pending. It flips to available in about 2 minutes.
From a Lambda or an EC2 host in a private subnet, this call now rides the endpoint. No endpoint_url needed once private DNS is on.
import boto3
client = boto3.client('bedrock-runtime', region_name='us-east-1')
resp = client.converse(
modelId='anthropic.claude-sonnet-4-6-v1',
messages=[{'role': 'user', 'content': [{'text': 'ping'}]}],
)
print(resp['output']['message']['content'][0]['text'])
# Expected: a short model reply printed to stdout.
# Failure mode: if the endpoint security group blocks 443,
# this hangs, then raises botocore ConnectTimeoutError.
# That is a network fix, not an IAM fix.To confirm the path is actually private, resolve the name from inside the subnet and check the IP is in your VPC CIDR, or set the endpoint policy to Deny for a moment and watch the same call fail with an access error. When it denies, you have proof the traffic is going through the endpoint and nowhere else.
Two endpoints, private DNS on, policy scoped to the VPC
For a real inference workload I stand up two interface endpoints, bedrock and bedrock-runtime, across two or three AZs depending on how much the app matters. I enable private DNS so no code changes. I attach an Allow endpoint policy scoped to the exact actions and model ARNs, and I pin the caller role with an aws:sourceVpce condition. In non-production accounts I cut to one AZ or the runtime endpoint alone, because idle endpoint-hours are the quiet line item that adds up across accounts. That is the whole recommendation, and it costs about 44 dollars a month in the shape most teams actually run.
Private networking gets your bytes off the internet. It does not encrypt your data at rest or manage who holds the keys. That is Part 10, on data residency, KMS, and the rest of the security story. Go turn on private DNS in a test VPC today and resolve the Bedrock name from a private subnet. If it returns a private IP, you have already done the hard part.
References
• AWS docs: interface VPC endpoints for Amazon Bedrock
• AWS PrivateLink pricing
• AWS ML blog: private access to Amazon Bedrock with PrivateLink
• Related on this site: the vendor-neutral GenAI guide


DrJha