No Certificate or NLB Needed! I Tried Exposing a Public-Subnet-less ECS Express Mode with API Gateway (VPC Link v2)

No Certificate or NLB Needed! I Tried Exposing a Public-Subnet-less ECS Express Mode with API Gateway (VPC Link v2)

I tested connecting API Gateway (REST/HTTP API) to an ECS Express Mode environment in a VPC with no public subnets via VPC Link v2. It exposes the service securely without an NLB. A CloudFormation template is included.
2026.03.03

This page has been translated by machine translation. View original

Previously, I introduced a configuration that builds an ECS Express Mode environment in a public-subnet-less VPC and exposes it to the internet using a CloudFront VPC Origin.

https://dev.classmethod.jp/articles/ecs-express-mode-public-subnet-less/

That time I used CloudFront, but with API Gateway you can also expose a public-subnet-less environment to the internet by using VPC Link v2.

With the November 2025 update, you can now specify an ALB directly in REST API private integrations as well. VPC Link v2 is used behind the scenes, and the same VPC Link can be shared between REST APIs and HTTP APIs.

https://dev.classmethod.jp/articles/api-gateway-rest-apis-integration-load-balancer/

This time, I had the opportunity to connect to an ECS Express Mode (Internal ALB) in a public-subnet-less VPC from both an API Gateway REST API and an HTTP API via VPC Link v2 and verify connectivity, so I'd like to share it.

The main differences from the older v1 are as follows.

VPC Link v1 VPC Link v2
API type REST API only Both REST API / HTTP API
Target NLB required ALB, NLB, CLB, private IP
Underlying mechanism PrivateLink (VPC endpoint service) ENI (created directly in the subnet)
Resource type AWS::ApiGateway::VpcLink AWS::ApiGatewayV2::VpcLink
Pricing Paid ($0.01/hour + data processing charges) Free
Behavior when unused Retained ENI automatically deleted after 60 days of non-use

VPC Link v2 itself incurs no additional cost. The NLB that was required with v1 is also unnecessary.

Cost item v1 configuration (conventional) v2 configuration (this time)
VPC Link $0.01/hour (about $7.3/month) Free
NLB About $16/month and up (fixed cost + LCU) Not required
API Gateway REST API: $3.50/1M req REST API: $3.50/1M req
HTTP API: $1.00/1M req
Total fixed cost About $23/month and up $0

Architecture

The configuration this time is as follows.

Comparison with the previous article:

  • Previous: Client → CloudFront → VPC Origin → Internal ALB → ECS
  • This time: Client → API Gateway → VPC Link v2 → Internal ALB → ECS

CloudFormation Template Walkthrough

I used a three-stack configuration with cross-stack integration via Export/ImportValue.

cfn/
  01-vpc.yaml              # Stack 1: Public-subnet-less VPC
  02-ecs-express.yaml      # Stack 2: ECS Express Mode (Nginx)
  03-apigw-vpclink.yaml    # Stack 3: REST API + HTTP API + VPC Link v2

Stack 1: VPC (01-vpc.yaml)

This is the same public-subnet-less configuration as in the previous article. It creates three private subnets, a Regional NAT Gateway, and an Egress-Only IGW.

Stack 2: ECS Express Mode (02-ecs-express.yaml)

I deployed an Nginx container with AWS::ECS::ExpressGatewayService. This time I used an Nginx image (public.ecr.aws/nginx/nginx:stable-alpine).

The following are exported in Outputs.

  • ServiceEndpoint: The HTTPS endpoint automatically assigned by ECS Express Mode (ap-xxxxx.ecs.region.on.aws)
  • LoadBalancerArn: The ARN of the Internal ALB
  • ListenerArn: The ARN of the ALB listener (used in the HTTP API integration)

This time, the REST API and HTTP API share a single VPC Link v2.

VpcLinkV2:
  Type: AWS::ApiGatewayV2::VpcLink
  Properties:
    Name: !Sub '${ProjectName}-vpclink'
    SecurityGroupIds:
      - !Ref VpcLinkSecurityGroup
    SubnetIds:
      Fn::Split:
        - ','
        - Fn::ImportValue: !Sub '${VPCStackName}-private-subnets'

For both HTTP APIs and REST APIs, the resource type is AWS::ApiGatewayV2::VpcLink.

REST API integration settings

RootMethod:
  Type: AWS::ApiGateway::Method
  Properties:
    RestApiId: !Ref RestApi
    ResourceId: !GetAtt RestApi.RootResourceId
    HttpMethod: ANY
    AuthorizationType: NONE
    Integration:
      Type: HTTP_PROXY
      IntegrationHttpMethod: ANY
      ConnectionType: VPC_LINK
      ConnectionId: !Ref VpcLinkV2
      IntegrationTarget:
        Fn::ImportValue: !Sub '${ProjectName}-alb-arn'
      Uri: !Sub
        - 'https://${Endpoint}/'
        - Endpoint:
            Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
      RequestParameters:
        integration.request.header.Host: !Sub
          - "'${Endpoint}'"
          - Endpoint:
              Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'

There are three key points.

  1. Specify the ALB ARN directly in IntegrationTarget
  2. Specify VPC Link v2 with ConnectionType: VPC_LINK + ConnectionId
  3. Overwrite the Host header with the ServiceEndpoint using integration.request.header.Host

Why overriding the Host header is necessary

By default, API Gateway sends its own hostname (xxx.execute-api.region.amazonaws.com) to the backend as the Host header. Because the ALB in ECS Express Mode distributes to services through host-based routing, a Host header that does not match the ServiceEndpoint fails to match the routing rules and a 404 is returned.

The syntax for overriding the Host header differs between REST API and HTTP API.

# REST API — overwrite with RequestParameters
RequestParameters:
  integration.request.header.Host: "'ap-xxxxx.ecs.region.on.aws'"

# HTTP API — overwrite with overwrite:header.Host
RequestParameters:
  overwrite:header.Host: ap-xxxxx.ecs.region.on.aws

Note that because the Host header is overwritten with the .on.aws value, the backend application can no longer obtain the original domain that the client accessed (the API Gateway URL) from the Host header. If you need the original domain, for example to generate redirect URLs, consider using the X-Forwarded-Host header.

HTTP API integration settings

HttpApiIntegration:
  Type: AWS::ApiGatewayV2::Integration
  Properties:
    ApiId: !Ref HttpApi
    IntegrationType: HTTP_PROXY
    IntegrationMethod: ANY
    ConnectionType: VPC_LINK
    ConnectionId: !Ref VpcLinkV2
    IntegrationUri:
      Fn::ImportValue: !Sub '${ProjectName}-listener-arn'
    RequestParameters:
      overwrite:header.Host:
        Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
    TlsConfig:
      ServerNameToVerify:
        Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
    PayloadFormatVersion: '1.0'

For the HTTP API, I specified the ARN of the ALB listener in IntegrationUri. The hostname for TLS verification is also aligned with the ServiceEndpoint via TlsConfig.ServerNameToVerify. PayloadFormatVersion is set to 1.0. The default for HTTP APIs is 2.0, but 1.0 is required for HTTP proxy integrations via VPC Link. There is only one route, $default, and I attached a stage with AutoDeploy: true.

About security groups

I created an SG for VPC Link v2. The ALB SG of ECS Express Mode is managed automatically by AWS, and in this verification connectivity worked without any additional configuration.

Connectivity Check

I used the ap-northeast-3 (Osaka) region for verification. After deployment (steps are in the reference information at the end), I retrieved the API Gateway endpoints and checked them with curl.

aws cloudformation describe-stacks \
  --stack-name apigw-vpclink-test-apigw \
  --query 'Stacks[0].Outputs' --output table \
  --region ap-northeast-3

The REST API is accessed through the /test stage, and the HTTP API through the $default stage (no path).

$ curl -s https://f5s2s3u41l.execute-api.ap-northeast-3.amazonaws.com/test
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
Endpoint URL Status
REST API (root) https://f5s2s3u41l.execute-api.ap-northeast-3.amazonaws.com/test 200 OK
HTTP API (root) https://4j83booagl.execute-api.ap-northeast-3.amazonaws.com 200 OK

Both the REST API and the HTTP API returned the Nginx Welcome page.

Observations and Discussion

Because the ALB in ECS Express Mode is automatically given an HTTPS endpoint and certificate, it satisfies the HTTPS requirement of VPC Link v2 as is. With a typical ALB + ECS configuration you need to manage certificates yourself, but with ECS Express Mode this can be omitted.

Choosing between CloudFront and API Gateway

ECS Express Mode is commonly combined with a CloudFront VPC Origin, but if you need API Gateway's authentication/authorization or API management features, consider connecting via VPC Link v2.

  • HTTP API: JWT authentication, Lambda authorizers, automatic CORS handling
  • REST API: In addition to the above, API keys + usage plans, direct WAF integration, request transformation
ECS Express Mode で公開したい
  ├─ API キー管理・使用量プラン・リクエスト変換が必要?
  │    └─ Yes → REST API + VPC Link v2
  ├─ JWT 認証・Lambda オーソライザーが必要?
  │    └─ Yes → HTTP API + VPC Link v2(低コスト)
  ├─ WAF・キャッシュ・地理的制限が必要?
  │    └─ Yes → CloudFront VPC Origin(前編の構成)
  └─ 上記いずれも不要
       └─ CloudFront VPC Origin(ファーストチョイス)

Summary

I confirmed that an ECS Express Mode environment in a public-subnet-less VPC can be reached via VPC Link v2 from both an API Gateway REST API and an HTTP API. If you adopt ECS Express Mode for workloads that need API Gateway features such as authentication and authorization, give VPC Link v2 a try.

Reference Information:

The full CloudFormation templates used in this article, along with the deployment and teardown steps.

Deploy and Teardown

Deploy (about 10 minutes)

REGION="ap-northeast-3"
PROFILE="your-profile"
PROJECT="apigw-vpclink-test"

# Stack 1: VPC (about 2 min)
aws cloudformation deploy \
  --template-file cfn/01-vpc.yaml \
  --stack-name ${PROJECT}-vpc \
  --region ${REGION} --profile ${PROFILE} \
  --no-fail-on-empty-changeset

# Stack 2: ECS Express Mode (about 5-7 min)
aws cloudformation deploy \
  --template-file cfn/02-ecs-express.yaml \
  --stack-name ${PROJECT}-ecs \
  --capabilities CAPABILITY_NAMED_IAM \
  --region ${REGION} --profile ${PROFILE} \
  --no-fail-on-empty-changeset

# Stack 3: API Gateway + VPC Link v2 (about 2-3 min)
aws cloudformation deploy \
  --template-file cfn/03-apigw-vpclink.yaml \
  --stack-name ${PROJECT}-apigw \
  --region ${REGION} --profile ${PROFILE} \
  --no-fail-on-empty-changeset

Teardown (reverse order)

aws cloudformation delete-stack --stack-name ${PROJECT}-apigw --region ${REGION} --profile ${PROFILE}
aws cloudformation wait stack-delete-complete --stack-name ${PROJECT}-apigw --region ${REGION} --profile ${PROFILE}

aws cloudformation delete-stack --stack-name ${PROJECT}-ecs --region ${REGION} --profile ${PROFILE}
aws cloudformation wait stack-delete-complete --stack-name ${PROJECT}-ecs --region ${REGION} --profile ${PROFILE}

aws cloudformation delete-stack --stack-name ${PROJECT}-vpc --region ${REGION} --profile ${PROFILE}
aws cloudformation wait stack-delete-complete --stack-name ${PROJECT}-vpc --region ${REGION} --profile ${PROFILE}
Full CloudFormation Templates

cfn/01-vpc.yaml

<details><summary>Click to expand</summary>

AWSTemplateFormatVersion: '2010-09-09'
Description: 'パブリックサブネットレスVPC with Regional NAT Gateway'

Parameters:
  ProjectName:
    Type: String
    Default: 'apigw-vpclink-test'

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '192.168.0.0/18'
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Ref ProjectName

  IPv6CidrBlock:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref VPC
      AmazonProvidedIpv6CidrBlock: true

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: '192.168.0.0/20'
      AvailabilityZone: !Select [0, !GetAZs '']
      Ipv6CidrBlock: !Select [0, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 3, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-1a'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: '192.168.16.0/20'
      AvailabilityZone: !Select [1, !GetAZs '']
      Ipv6CidrBlock: !Select [1, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 3, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-1c'

  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: '192.168.32.0/20'
      AvailabilityZone: !Select [2, !GetAZs '']
      Ipv6CidrBlock: !Select [2, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 3, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-1d'

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-igw'

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  EgressOnlyIGW:
    Type: AWS::EC2::EgressOnlyInternetGateway
    Properties:
      VpcId: !Ref VPC

  NATGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-nat-eip'

  RegionalNATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AvailabilityMode: regional
      ConnectivityType: public
      VpcId: !Ref VPC
      AvailabilityZoneAddresses:
        - AvailabilityZone: !Select [0, !GetAZs '']
          AllocationIds:
            - !GetAtt NATGatewayEIP.AllocationId
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-regional-nat-gw'

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-rt'

  PrivateRouteIPv4:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      NatGatewayId: !Ref RegionalNATGateway

  PrivateRouteIPv6:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationIpv6CidrBlock: '::/0'
      EgressOnlyInternetGatewayId: !Ref EgressOnlyIGW

  PrivateSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet3Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet3
      RouteTableId: !Ref PrivateRouteTable

Outputs:
  VPCId:
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}-vpc-id'

  PrivateSubnets:
    Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2, !Ref PrivateSubnet3]]
    Export:
      Name: !Sub '${AWS::StackName}-private-subnets'

</details>

cfn/02-ecs-express.yaml

<details><summary>Click to expand</summary>

AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Express Mode Service - Nginx public image'

Parameters:
  ProjectName:
    Type: String
    Default: 'apigw-vpclink-test'

  VPCStackName:
    Type: String
    Default: 'apigw-vpclink-test-vpc'

Resources:
  ECSLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ecs/default/${ProjectName}-service'
      RetentionInDays: 7

  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-task-execution-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-task-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole

  ECSInfrastructureRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-infrastructure-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSInfrastructureRoleforExpressGatewayServices

  ExpressModeService:
    Type: AWS::ECS::ExpressGatewayService
    DependsOn: ECSLogGroup
    Properties:
      ServiceName: !Sub '${ProjectName}-service'
      Cluster: 'default'
      ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
      InfrastructureRoleArn: !GetAtt ECSInfrastructureRole.Arn
      TaskRoleArn: !GetAtt ECSTaskRole.Arn
      PrimaryContainer:
        Image: 'public.ecr.aws/nginx/nginx:stable-alpine'
        ContainerPort: 80
        AwsLogsConfiguration:
          LogGroup: !Sub '/aws/ecs/default/${ProjectName}-service'
          LogStreamPrefix: 'ecs'
      NetworkConfiguration:
        Subnets:
          Fn::Split:
            - ','
            - Fn::ImportValue: !Sub '${VPCStackName}-private-subnets'
      Cpu: '256'
      Memory: '512'
      HealthCheckPath: '/'
      ScalingTarget:
        MinTaskCount: 1
        MaxTaskCount: 1
        AutoScalingMetric: 'AVERAGE_CPU'
        AutoScalingTargetValue: 70

Outputs:
  ServiceEndpoint:
    Value: !GetAtt ExpressModeService.Endpoint
    Export:
      Name: !Sub '${ProjectName}-service-endpoint'

  LoadBalancerArn:
    Value: !GetAtt ExpressModeService.ECSManagedResourceArns.IngressPath.LoadBalancerArn
    Export:
      Name: !Sub '${ProjectName}-alb-arn'

  ListenerArn:
    Value: !GetAtt ExpressModeService.ECSManagedResourceArns.IngressPath.ListenerArn
    Export:
      Name: !Sub '${ProjectName}-listener-arn'

</details>

cfn/03-apigw-vpclink.yaml

<details><summary>Click to expand</summary>

AWSTemplateFormatVersion: '2010-09-09'
Description: 'API Gateway REST API + HTTP API + VPC Link v2 → ECS Express Internal ALB'

Parameters:
  ProjectName:
    Type: String
    Default: 'apigw-vpclink-test'

  VPCStackName:
    Type: String
    Default: 'apigw-vpclink-test-vpc'

  ECSStackName:
    Type: String
    Default: 'apigw-vpclink-test-ecs'

Resources:
  # VPC Link v2 用セキュリティグループ
  VpcLinkSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: 'Security group for VPC Link v2'
      GroupName: !Sub '${ProjectName}-vpclink-sg'
      VpcId:
        Fn::ImportValue: !Sub '${VPCStackName}-vpc-id'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-vpclink-sg'

  # VPC Link v2
  VpcLinkV2:
    Type: AWS::ApiGatewayV2::VpcLink
    Properties:
      Name: !Sub '${ProjectName}-vpclink'
      SecurityGroupIds:
        - !Ref VpcLinkSecurityGroup
      SubnetIds:
        Fn::Split:
          - ','
          - Fn::ImportValue: !Sub '${VPCStackName}-private-subnets'

  # REST API
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub '${ProjectName}-api'
      Description: 'REST API with VPC Link v2 to ECS Express Internal ALB'
      EndpointConfiguration:
        Types:
          - REGIONAL

  # /{proxy+} リソース
  ProxyResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: '{proxy+}'

  # ルート ANY メソッド
  RootMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !GetAtt RestApi.RootResourceId
      HttpMethod: ANY
      AuthorizationType: NONE
      Integration:
        Type: HTTP_PROXY
        IntegrationHttpMethod: ANY
        ConnectionType: VPC_LINK
        ConnectionId: !Ref VpcLinkV2
        IntegrationTarget:
          Fn::ImportValue: !Sub '${ProjectName}-alb-arn'
        Uri: !Sub
          - 'https://${Endpoint}/'
          - Endpoint:
              Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
        RequestParameters:
          integration.request.header.Host: !Sub
            - "'${Endpoint}'"
            - Endpoint:
                Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'

  # プロキシ ANY メソッド
  ProxyMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref ProxyResource
      HttpMethod: ANY
      AuthorizationType: NONE
      RequestParameters:
        method.request.path.proxy: true
      Integration:
        Type: HTTP_PROXY
        IntegrationHttpMethod: ANY
        ConnectionType: VPC_LINK
        ConnectionId: !Ref VpcLinkV2
        IntegrationTarget:
          Fn::ImportValue: !Sub '${ProjectName}-alb-arn'
        Uri: !Sub
          - 'https://${Endpoint}/{proxy}'
          - Endpoint:
              Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
        RequestParameters:
          integration.request.header.Host: !Sub
            - "'${Endpoint}'"
            - Endpoint:
                Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
          integration.request.path.proxy: method.request.path.proxy

  # デプロイ
  Deployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - RootMethod
      - ProxyMethod
    Properties:
      RestApiId: !Ref RestApi

  # ステージ
  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref Deployment
      StageName: 'test'

  # ===========================================
  # HTTP API (API Gateway v2)
  # ===========================================

  HttpApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: !Sub '${ProjectName}-http-api'
      Description: 'HTTP API with VPC Link v2 to ECS Express Internal ALB'
      ProtocolType: HTTP

  # VPC Link 統合
  HttpApiIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref HttpApi
      IntegrationType: HTTP_PROXY
      IntegrationMethod: ANY
      ConnectionType: VPC_LINK
      ConnectionId: !Ref VpcLinkV2
      IntegrationUri:
        Fn::ImportValue: !Sub '${ProjectName}-listener-arn'
      RequestParameters:
        overwrite:header.Host:
          Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
      TlsConfig:
        ServerNameToVerify:
          Fn::ImportValue: !Sub '${ProjectName}-service-endpoint'
      PayloadFormatVersion: '1.0'

  # デフォルトルート
  HttpApiRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref HttpApi
      RouteKey: '$default'
      Target: !Sub 'integrations/${HttpApiIntegration}'

  # ステージ(自動デプロイ)
  HttpApiStage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      ApiId: !Ref HttpApi
      StageName: '$default'
      AutoDeploy: true

Outputs:
  RestApiEndpoint:
    Description: 'REST API Invoke URL'
    Value: !Sub 'https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/test'

  HttpApiEndpoint:
    Description: 'HTTP API Invoke URL'
    Value: !Sub 'https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com'

  VpcLinkId:
    Value: !Ref VpcLinkV2

  RestApiId:
    Value: !Ref RestApi

  HttpApiId:
    Value: !Ref HttpApi

  SecurityGroupId:
    Value: !Ref VpcLinkSecurityGroup

Share this article

AWSのお困り事はクラスメソッドへ