API Gateway REST API Endpoint is Private
Overview
This check verifies that your API Gateway REST APIs use private endpoints instead of public (internet-accessible) endpoints. A private API is only accessible from within your Amazon VPC through an interface VPC endpoint, keeping traffic off the public internet entirely.
Risk
When your REST API has a public endpoint:
- Data exposure - Misconfigured or unauthenticated methods could leak sensitive data to anyone on the internet
- Unauthorized access - Attackers can probe and potentially exploit your API endpoints
- DDoS and abuse - Public endpoints are vulnerable to denial-of-service attacks and bot traffic, increasing costs and consuming capacity
- Compliance issues - Many security frameworks require internal APIs to be network-isolated
Remediation Steps
Prerequisites
- AWS account access with permission to modify API Gateway settings
- If converting to private: A VPC with an interface VPC endpoint for API Gateway (or the ability to create one)
Understanding your options
You have two approaches to address this check:
Option A: Convert to Private Endpoint (Recommended)
- API is only accessible from within your VPC
- Requires a VPC endpoint for
execute-api - Best for internal APIs that don't need public access
Option B: Keep Public but Add Controls
- Add resource policies to restrict access to specific VPCs or IP ranges
- Implement authentication (IAM, Cognito, or custom authorizers)
- Add AWS WAF, throttling, and usage plans
- Suitable when external access is legitimately required
AWS Console Method
To convert your API to a private endpoint:
- Sign in to the AWS Management Console
- Navigate to API Gateway
- Select your REST API from the list
- In the left navigation, click API settings
- Under Endpoint configuration, click Edit
- Change the endpoint type from
RegionalorEdge optimizedto Private - Click Save changes
- You must redeploy your API for changes to take effect - go to Deploy > Deploy API
Important: After converting to private, you need to:
- Create a VPC endpoint for
execute-apiin your VPC (if you haven't already) - Add a resource policy allowing access from your VPC endpoint
- Update any clients to call the API through the VPC endpoint
Creating a VPC endpoint for API Gateway
Before your private API can be accessed, you need an interface VPC endpoint:
- Go to VPC > Endpoints > Create endpoint
- Search for and select the service com.amazonaws.us-east-1.execute-api
- Select your VPC
- Select the subnets where the endpoint should be available
- Select a security group that allows HTTPS (port 443) traffic
- Enable Private DNS names (recommended)
- Click Create endpoint
The endpoint will be created with DNS names that allow your VPC resources to call private APIs.
Adding a resource policy for private API access
After converting to private, add a resource policy to allow access from your VPC endpoint:
- In API Gateway, select your API
- Click Resource policy in the left navigation
- Add a policy like this (replace placeholders):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<API_ID>/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "<VPC_ENDPOINT_ID>"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<API_ID>/*"
}
]
}
- Click Save changes
- Redeploy your API
AWS CLI (optional)
List your REST APIs to find the API ID:
aws apigateway get-rest-apis --region us-east-1
Convert an API to private:
aws apigateway update-rest-api \
--rest-api-id <REST_API_ID> \
--patch-operations op=replace,path=/endpointConfiguration/types/0,value=PRIVATE \
--region us-east-1
Add a resource policy to allow VPC endpoint access:
aws apigateway update-rest-api \
--rest-api-id <REST_API_ID> \
--patch-operations "op=replace,path=/policy,value='{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"execute-api:Invoke\",\"Resource\":\"arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<REST_API_ID>/*\",\"Condition\":{\"StringNotEquals\":{\"aws:sourceVpce\":\"<VPC_ENDPOINT_ID>\"}}},{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"execute-api:Invoke\",\"Resource\":\"arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<REST_API_ID>/*\"}]}'" \
--region us-east-1
Create a new deployment to apply changes:
aws apigateway create-deployment \
--rest-api-id <REST_API_ID> \
--stage-name <STAGE_NAME> \
--region us-east-1
Create a VPC endpoint for API Gateway:
aws ec2 create-vpc-endpoint \
--vpc-id <VPC_ID> \
--service-name com.amazonaws.us-east-1.execute-api \
--vpc-endpoint-type Interface \
--subnet-ids <SUBNET_ID_1> <SUBNET_ID_2> \
--security-group-ids <SECURITY_GROUP_ID> \
--private-dns-enabled \
--region us-east-1
CloudFormation (optional)
This template creates a private REST API with a VPC endpoint:
AWSTemplateFormatVersion: '2010-09-09'
Description: Private API Gateway REST API with VPC endpoint
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the endpoint will be created
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Subnets for the VPC endpoint
SecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
Description: Security group for the VPC endpoint
Resources:
ApiGatewayVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcId
ServiceName: !Sub com.amazonaws.${AWS::Region}.execute-api
VpcEndpointType: Interface
SubnetIds: !Ref SubnetIds
SecurityGroupIds:
- !Ref SecurityGroupId
PrivateDnsEnabled: true
PrivateRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: MyPrivateAPI
Description: Private REST API accessible only from VPC
EndpointConfiguration:
Types:
- PRIVATE
VpcEndpointIds:
- !Ref ApiGatewayVpcEndpoint
Policy:
Version: '2012-10-17'
Statement:
- Effect: Deny
Principal: '*'
Action: execute-api:Invoke
Resource: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*
Condition:
StringNotEquals:
aws:sourceVpce: !Ref ApiGatewayVpcEndpoint
- Effect: Allow
Principal: '*'
Action: execute-api:Invoke
Resource: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*
Outputs:
ApiId:
Description: ID of the private REST API
Value: !Ref PrivateRestApi
VpcEndpointId:
Description: ID of the VPC endpoint
Value: !Ref ApiGatewayVpcEndpoint
Deploy the template:
aws cloudformation deploy \
--template-file private-api.yaml \
--stack-name private-api-stack \
--parameter-overrides \
VpcId=<VPC_ID> \
SubnetIds=<SUBNET_ID_1>,<SUBNET_ID_2> \
SecurityGroupId=<SECURITY_GROUP_ID> \
--region us-east-1
Terraform (optional)
# VPC Endpoint for API Gateway
resource "aws_vpc_endpoint" "api_gateway" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.us-east-1.execute-api"
vpc_endpoint_type = "Interface"
subnet_ids = var.subnet_ids
security_group_ids = [var.security_group_id]
private_dns_enabled = true
tags = {
Name = "api-gateway-endpoint"
}
}
# Private REST API
resource "aws_api_gateway_rest_api" "private" {
name = "my-private-api"
description = "Private REST API accessible only from VPC"
endpoint_configuration {
types = ["PRIVATE"]
vpc_endpoint_ids = [aws_vpc_endpoint.api_gateway.id]
}
}
# Resource policy to restrict access to VPC endpoint
resource "aws_api_gateway_rest_api_policy" "private" {
rest_api_id = aws_api_gateway_rest_api.private.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Deny"
Principal = "*"
Action = "execute-api:Invoke"
Resource = "${aws_api_gateway_rest_api.private.execution_arn}/*"
Condition = {
StringNotEquals = {
"aws:sourceVpce" = aws_vpc_endpoint.api_gateway.id
}
}
},
{
Effect = "Allow"
Principal = "*"
Action = "execute-api:Invoke"
Resource = "${aws_api_gateway_rest_api.private.execution_arn}/*"
}
]
})
}
# Variables
variable "vpc_id" {
description = "VPC ID where the endpoint will be created"
type = string
}
variable "subnet_ids" {
description = "Subnet IDs for the VPC endpoint"
type = list(string)
}
variable "security_group_id" {
description = "Security group ID for the VPC endpoint"
type = string
}
Verification
After converting to a private endpoint:
- Go to API Gateway > select your API > API settings
- Confirm the endpoint type shows Private
- Verify the resource policy is in place under Resource policy
- Test access from within your VPC to confirm the API is reachable
CLI verification commands
Check the endpoint type:
aws apigateway get-rest-api \
--rest-api-id <REST_API_ID> \
--region us-east-1
Look for the endpoint configuration in the output:
{
"endpointConfiguration": {
"types": ["PRIVATE"],
"vpcEndpointIds": ["vpce-1234567890abcdef0"]
}
}
Verify the resource policy:
aws apigateway get-rest-api \
--rest-api-id <REST_API_ID> \
--query 'policy' \
--output text \
--region us-east-1 | python3 -m json.tool
List VPC endpoints to find your API Gateway endpoint:
aws ec2 describe-vpc-endpoints \
--filters Name=service-name,Values=com.amazonaws.us-east-1.execute-api \
--region us-east-1
Additional Resources
- Creating a private API in Amazon API Gateway
- Private API tutorial with CloudFormation
- Resource policies for API Gateway
- Interface VPC endpoints (AWS PrivateLink)
Notes
- REST APIs only: Private endpoints are only available for REST APIs, not HTTP APIs.
- Cannot convert to edge-optimized: Once an API is private, you cannot convert it to an edge-optimized endpoint. You can convert between private and regional.
- Redeployment required: After changing endpoint configuration or resource policies, you must redeploy your API for changes to take effect.
- Private DNS: Enable private DNS on your VPC endpoint so clients can use the standard API Gateway URL format. Without it, you need to use the VPC endpoint URL directly.
- Security groups: The security group on your VPC endpoint must allow inbound HTTPS (port 443) traffic from the sources that need to call your API.
- Multi-region: Each region requires its own VPC endpoint for
execute-api. - Cost considerations: VPC endpoints have hourly charges and data processing fees. Review AWS PrivateLink pricing.