PrivateLink to Reach a Model Endpoint Without a Public IP
10 min read · updated August 11, 2026
An interface VPC endpoint puts an elastic network interface with a private address from your own subnet in front of an AWS service, so calls to that service leave your VPC and go nowhere else. For a model endpoint that removes the NAT gateway, the internet gateway and the public path in one step — provided the model is behind a service that has published a PrivateLink endpoint at all.
What PrivateLink can and cannot reach
Start here, because this is where people lose an afternoon. PrivateLink connects your VPC to a service that has been published as a PrivateLink endpoint service — an AWS service, or a third-party service whose vendor has created a VpcEndpointService and shared it with your account. It is not a general mechanism for making arbitrary public APIs private. There is no interface endpoint for api.openai.com unless OpenAI has published one; the same is true of any provider.
On AWS, Amazon Bedrock publishes several. Its interface VPC endpoints documentation lists the service names, of which the one that matters for inference is com.amazonaws.region.bedrock-runtime:
com.amazonaws.<region>.bedrock # control plane com.amazonaws.<region>.bedrock-runtime # InvokeModel, Converse com.amazonaws.<region>.bedrock-agent # agent build-time com.amazonaws.<region>.bedrock-agent-runtime # agent invocation com.amazonaws.<region>.bedrock-runtime-fips # FIPS, limited Regions
If your provider is not on AWS, the equivalents are Google’s Private Service Connect — Private Service Connect for Vertex AI — and Azure Private Endpoint. If the provider offers none of these, the honest answer is a NAT gateway and an egress policy, covered in reaching a model provider from a private subnet.
The three prerequisites
AWS lists these on the interface endpoint page and each of them, when missing, produces a failure that does not name itself.
- Both VPC DNS attributes must be true. AWS states that “to use private DNS, you must enable DNS hostnames and DNS resolution for your VPC”. Without them, private DNS on the endpoint silently does not take effect and your SDK carries on resolving the public address.
- The endpoint’s security group must allow inbound 443. This is the single most common mistake, because it is backwards from how people think about endpoints. The security group is attached to the endpoint’s network interfaces, and your workload is the client connecting to them — so the rule you need is inbound HTTPS from the workload’s security group, not outbound anything. The default VPC security group is attached if you do not specify one, and it usually does not permit this.
- One subnet per Availability Zone. You may select one subnet per AZ and not two from the same one. Pick the AZs your workload actually runs in; an endpoint with no interface in a workload’s AZ means that workload’s traffic either crosses zones or does not resolve at all.
aws ec2 describe-vpc-attribute --vpc-id vpc-0abc --attribute enableDnsSupport aws ec2 describe-vpc-attribute --vpc-id vpc-0abc --attribute enableDnsHostnames aws ec2 modify-vpc-attribute --vpc-id vpc-0abc --enable-dns-hostnames
Creating the endpoint
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.bedrock-runtime \
--subnet-ids subnet-0private1a subnet-0private1b \
--security-group-ids sg-0endpoint \
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=bedrock-runtime}]'With --private-dns-enabled there is no application change at all. AWS notes that requests using the standard service DNS name — bedrock-runtime.us-east-1.amazonaws.com — resolve to the endpoint’s private addresses from inside the VPC. Existing code and existing SDK configuration keep working, which is the whole appeal.
Without private DNS you must direct the client explicitly, which is also how you test the endpoint before flipping DNS for everything:
import boto3
client = boto3.client(
"bedrock-runtime",
region_name="us-east-1",
endpoint_url="https://vpce-029dea71225152fde-abc123.bedrock-runtime.us-east-1.vpce.amazonaws.com",
)
client.converse(
modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
messages=[{"role": "user", "content": [{"text": "ping"}]}],
)On price, Amazon’s AWS PrivateLink pricing page bills each endpoint network interface per hour it stays provisioned in each Availability Zone, plus a per-GB data processing charge that starts at $0.01 per GB and tiers down with volume. Two AZs means two interfaces and twice the hourly charge. Against a NAT gateway at $0.045 per GB, the per-GB comparison favours PrivateLink substantially; the hourly comparison depends on how many AZs you span.
Restricting it with an endpoint policy
The default endpoint policy allows full access to the service through the endpoint. Since the point of the exercise is usually a compliance boundary rather than a latency improvement, narrowing it is worth the five minutes. An endpoint policy is a resource policy evaluated in addition to your IAM policies, so it can only remove permissions, not grant them.
{
"Version": "2012-10-17",
"Statement": [
{
"Principal": { "AWS": "arn:aws:iam::111122223333:role/inference-worker" },
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.*"
}
]
}That policy says: through this endpoint, only this role, only inference, only these models. Model customization, agent invocation and every control-plane action are refused at the network boundary regardless of what IAM would have allowed — which is a materially different guarantee from an IAM policy that a future change might relax.
When it does not work
- Timeouts to the endpoint address. Almost always the security group missing inbound 443 from the client’s security group. Check the endpoint’s SG, not the workload’s.
- Ping does not work and that is expected. AWS states that interface endpoints do not respond to ping requests, and suggests
ncornmapinstead. Usenc -vz <endpoint-dns> 443. - The SDK still resolves the public address. Either private DNS is not enabled on the endpoint, or one of the two VPC DNS attributes is false. Confirm with
dig bedrock-runtime.us-east-1.amazonaws.comfrom inside the subnet — you should get a private address in your own CIDR. AccessDeniedExceptionthat IAM does not explain. The endpoint policy is denying it. It is evaluated separately and it is easy to forget it exists once it is written.- Works in one AZ, fails in another. You did not select a subnet in that AZ when creating the endpoint. Add it with
modify-vpc-endpoint --add-subnet-ids.