Skip to main content

API Gateway V2 Access Logging Enabled

Overview

This check verifies that your API Gateway V2 (HTTP APIs and WebSocket APIs) stages have access logging enabled. Access logging captures detailed information about requests to your APIs, including who made the request, when, and how it was handled.

Risk

Without access logging enabled on your API Gateway V2 stages:

  • Attackers can operate undetected - credential misuse, endpoint probing, and data exfiltration leave no audit trail
  • Incident response is delayed - you cannot investigate security incidents or trace malicious activity
  • Anomalies go unnoticed - unusual traffic patterns or route abuse cannot be identified
  • Compliance gaps - many security frameworks require API activity logging for audit purposes

Remediation Steps

Prerequisites

  • AWS account access with permission to modify API Gateway settings
  • A CloudWatch Logs log group to receive the access logs (you can create one during setup)
Creating a CloudWatch Logs log group

You need a log group to store your access logs. You can create one via the console or CLI.

Console method:

  1. Go to CloudWatch > Logs > Log groups
  2. Click Create log group
  3. Enter a name (e.g., /aws/apigateway/my-http-api)
  4. Set a retention period (e.g., 30 days)
  5. Click Create
  6. Note the log group ARN for the next steps

CLI method:

aws logs create-log-group \
--log-group-name /aws/apigateway/my-http-api \
--region us-east-1

# Get the ARN
aws logs describe-log-groups \
--log-group-name-prefix /aws/apigateway/my-http-api \
--region us-east-1 \
--query 'logGroups[0].arn' \
--output text

AWS Console Method

  1. Sign in to the AWS Management Console
  2. Navigate to API Gateway
  3. In the left navigation under APIs, find and select your HTTP API or WebSocket API
  4. In the left navigation, click Stages
  5. Select the stage you want to configure (e.g., $default, prod, dev)
  6. Scroll down to the Access logging section
  7. Toggle Access logging to On
  8. For Log destination, enter the ARN of your CloudWatch Logs log group
    • Example: arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/my-http-api
  9. For Log format, use a structured JSON format like:
    {"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength"}
  10. Click Save

Repeat steps 5-10 for each stage that needs logging enabled.

AWS CLI (optional)

First, list your HTTP/WebSocket APIs to find the API ID:

aws apigatewayv2 get-apis --region us-east-1

List stages for a specific API:

aws apigatewayv2 get-stages --api-id <API_ID> --region us-east-1

Enable access logging on a stage:

aws apigatewayv2 update-stage \
--api-id <API_ID> \
--stage-name <STAGE_NAME> \
--access-log-settings '{
"DestinationArn": "arn:aws:logs:us-east-1:<ACCOUNT_ID>:log-group:/aws/apigateway/my-http-api",
"Format": "{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"requestTime\":\"$context.requestTime\",\"httpMethod\":\"$context.httpMethod\",\"routeKey\":\"$context.routeKey\",\"status\":\"$context.status\",\"protocol\":\"$context.protocol\",\"responseLength\":\"$context.responseLength\"}"
}' \
--region us-east-1

Shorthand syntax alternative:

aws apigatewayv2 update-stage \
--api-id <API_ID> \
--stage-name <STAGE_NAME> \
--access-log-settings DestinationArn=arn:aws:logs:us-east-1:<ACCOUNT_ID>:log-group:/aws/apigateway/my-http-api,Format='{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","status":"$context.status"}' \
--region us-east-1

Replace:

  • <API_ID> with your API's identifier
  • <STAGE_NAME> with the stage name (e.g., $default, prod)
  • <ACCOUNT_ID> with your AWS account ID
CloudFormation (optional)

This template creates an HTTP API with access logging enabled:

AWSTemplateFormatVersion: '2010-09-09'
Description: Enable access logging for API Gateway V2 HTTP API

Parameters:
ApiName:
Type: String
Description: Name for the HTTP API
Default: my-http-api
StageName:
Type: String
Description: Name of the stage
Default: prod
LogRetentionDays:
Type: Number
Description: Number of days to retain access logs
Default: 30

Resources:
AccessLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/apigateway/${ApiName}
RetentionInDays: !Ref LogRetentionDays

HttpApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: !Ref ApiName
ProtocolType: HTTP

HttpApiStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref HttpApi
StageName: !Ref StageName
AutoDeploy: true
AccessLogSettings:
DestinationArn: !GetAtt AccessLogGroup.Arn
Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","integrationErrorMessage":"$context.integrationErrorMessage"}'

Outputs:
ApiEndpoint:
Description: HTTP API endpoint URL
Value: !Sub 'https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}'
LogGroupArn:
Description: ARN of the access log group
Value: !GetAtt AccessLogGroup.Arn

To add logging to an existing stage, modify your existing AWS::ApiGatewayV2::Stage resource to include the AccessLogSettings property.

Deploy the template:

aws cloudformation deploy \
--template-file apigatewayv2-logging.yaml \
--stack-name apigatewayv2-logging-stack \
--parameter-overrides \
ApiName=my-http-api \
StageName=prod \
LogRetentionDays=30 \
--region us-east-1
Terraform (optional)
# CloudWatch Log Group for access logs
resource "aws_cloudwatch_log_group" "api_access_logs" {
name = "/aws/apigateway/${var.api_name}"
retention_in_days = 30
}

# HTTP API
resource "aws_apigatewayv2_api" "example" {
name = var.api_name
protocol_type = "HTTP"
}

# Stage with access logging enabled
resource "aws_apigatewayv2_stage" "example" {
api_id = aws_apigatewayv2_api.example.id
name = "prod"
auto_deploy = true

access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_access_logs.arn
format = jsonencode({
requestId = "$context.requestId"
ip = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey = "$context.routeKey"
status = "$context.status"
protocol = "$context.protocol"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
})
}
}

variable "api_name" {
description = "Name of the HTTP API"
type = string
default = "my-http-api"
}

output "api_endpoint" {
description = "HTTP API endpoint URL"
value = aws_apigatewayv2_stage.example.invoke_url
}

To add logging to an existing stage, add the access_log_settings block to your existing aws_apigatewayv2_stage resource.

Verification

After enabling logging, verify the configuration:

  1. Go to API Gateway > select your HTTP/WebSocket API > Stages
  2. Select your stage and confirm Access logging shows as enabled
  3. Make a test API call to your endpoint
  4. Go to CloudWatch > Logs > Log groups and find your log group
  5. Open the latest log stream and verify request entries are appearing
CLI verification commands

Check the stage configuration:

aws apigatewayv2 get-stage \
--api-id <API_ID> \
--stage-name <STAGE_NAME> \
--region us-east-1

Look for AccessLogSettings in the output:

{
"AccessLogSettings": {
"DestinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/my-http-api",
"Format": "{\"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",...}"
},
"StageName": "prod",
...
}

If AccessLogSettings is missing or empty, logging is not enabled.

View recent log entries after making API calls:

aws logs tail /aws/apigateway/my-http-api --region us-east-1 --since 1h

Additional Resources

Notes

  • V2 vs REST APIs: This check applies to API Gateway V2 (HTTP APIs and WebSocket APIs), not REST APIs. REST APIs use a different logging configuration with CloudWatch execution logs.
  • No IAM role required: Unlike REST API logging, HTTP API access logging writes directly to CloudWatch Logs without requiring a separate IAM role configuration.
  • Log format flexibility: You can customize the log format to include any available $context variables. Common fields include requestId, ip, httpMethod, status, and responseLength.
  • Cost considerations: CloudWatch Logs charges for ingestion and storage. Set appropriate retention policies on your log groups to manage costs.
  • $default stage: Many HTTP APIs use a special $default stage. Make sure to enable logging on this stage if your API uses it.
  • WebSocket differences: WebSocket APIs have additional context variables like $context.connectionId and $context.eventType that you may want to include in your log format.