Skip to main content

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:

  1. Sign in to the AWS Management Console
  2. Navigate to API Gateway
  3. Select your REST API from the list
  4. In the left navigation, click API settings
  5. Under Endpoint configuration, click Edit
  6. Change the endpoint type from Regional or Edge optimized to Private
  7. Click Save changes
  8. 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-api in 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:

  1. Go to VPC > Endpoints > Create endpoint
  2. Search for and select the service com.amazonaws.us-east-1.execute-api
  3. Select your VPC
  4. Select the subnets where the endpoint should be available
  5. Select a security group that allows HTTPS (port 443) traffic
  6. Enable Private DNS names (recommended)
  7. 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:

  1. In API Gateway, select your API
  2. Click Resource policy in the left navigation
  3. 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>/*"
}
]
}
  1. Click Save changes
  2. 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:

  1. Go to API Gateway > select your API > API settings
  2. Confirm the endpoint type shows Private
  3. Verify the resource policy is in place under Resource policy
  4. 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

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.