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:
- Go to CloudWatch > Logs > Log groups
- Click Create log group
- Enter a name (e.g.,
/aws/apigateway/my-http-api) - Set a retention period (e.g., 30 days)
- Click Create
- 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
- Sign in to the AWS Management Console
- Navigate to API Gateway
- In the left navigation under APIs, find and select your HTTP API or WebSocket API
- In the left navigation, click Stages
- Select the stage you want to configure (e.g.,
$default,prod,dev) - Scroll down to the Access logging section
- Toggle Access logging to On
- 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
- Example:
- 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"} - 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:
- Go to API Gateway > select your HTTP/WebSocket API > Stages
- Select your stage and confirm Access logging shows as enabled
- Make a test API call to your endpoint
- Go to CloudWatch > Logs > Log groups and find your log group
- 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
- Setting up logging for HTTP APIs
- Setting up logging for WebSocket APIs
- API Gateway security monitoring
- $context variables for access logging
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
$contextvariables. Common fields includerequestId,ip,httpMethod,status, andresponseLength. - 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
$defaultstage. Make sure to enable logging on this stage if your API uses it. - WebSocket differences: WebSocket APIs have additional context variables like
$context.connectionIdand$context.eventTypethat you may want to include in your log format.