API Gateway V2 stages should have access logging configured
API Gateway V2 access logs capture every request hitting your HTTP or WebSocket API: caller IP, request path, latency, and integration response status. Without them, you have no visibility into who called what endpoint, when, or how the backend responded. Incident investigation, abuse detection, and capacity analysis all depend on this data.
Access logs are also the primary source for spotting anomalous traffic patterns and unauthorized access attempts against your APIs. Disabling them saves a trivial amount on CloudWatch Logs costs while creating a real observability gap that you will notice only when something goes wrong.
Retrofit consideration
Enabling access logging on existing stages means creating or referencing a CloudWatch Logs log group and updating the stage resource. No downtime, but on a busy API the log volume hits immediately. Check your expected ingestion rate against CloudWatch Logs costs before rolling this out across all stages at once.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "apigateway_v2" {
source = "pcidss.compliance.tf/terraform-aws-modules/apigateway-v2/aws"
version = ">=6.0.0"
create_certificate = false
create_domain_name = false
create_domain_records = false
name = "abc123"
protocol_type = "HTTP"
}
module "apigateway_v2" {
source = "acscessentialeight.compliance.tf/terraform-aws-modules/apigateway-v2/aws"
version = ">=6.0.0"
create_certificate = false
create_domain_name = false
create_domain_records = false
name = "abc123"
protocol_type = "HTTP"
}
If you use terraform-aws-modules/apigateway-v2/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "apigateway_v2" {
source = "terraform-aws-modules/apigateway-v2/aws"
version = ">=6.0.0"
create_certificate = false
create_domain_name = false
create_domain_records = false
name = "abc123"
protocol_type = "HTTP"
}
Use AWS provider resources directly. See docs for the resources involved: aws_apigatewayv2_stage.
resource "aws_apigatewayv2_api" "this" {
name = "pofix-abc123"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_stage" "this" {
access_log_settings {
destination_arn = "arn:aws:logs:us-east-1:123456789012:log-group:example-log-group"
format = "$context.requestId"
}
api_id = "abc123"
name = "example"
}
What this control checks
The aws_apigatewayv2_stage resource must include an access_log_settings block with destination_arn set to a CloudWatch Logs log group ARN. It fails when the block is omitted entirely, or when destination_arn is empty or null. The referenced log group should be managed separately via aws_cloudwatch_log_group. The access_log_settings block also accepts a format argument for customizing log output using context variables (e.g., $context.requestId, $context.identity.sourceIp), but the control validates only that a destination is configured, not the format string itself.
Common pitfalls
Missing API Gateway CloudWatch Logs role permissions
Log delivery silently fails if the account-level API Gateway CloudWatch Logs role is missing or misconfigured. The
destination_arnlooks correct in Terraform and the stage deploys without error, but no entries appear in the log group. For same-account logging, attach the managed policyAmazonAPIGatewayPushToCloudWatchLogsto the role configured in the API Gateway account settings.Default stage auto-deploy overwriting settings
When
auto_deploy = trueis set on anaws_apigatewayv2_stagewith stage name$default, redeployments triggered by route or integration changes recreate the stage configuration. Ifaccess_log_settingsis defined in Terraform this is fine, but any manual console edits to the log format will be overwritten on the next deploy. Keep all configuration in Terraform to prevent drift.Log group naming and retention not set
Creating
aws_cloudwatch_log_groupwithoutretention_in_daysmeans indefinite retention. On a high-traffic API, storage costs accumulate fast. Set an explicit retention period. The convention/aws/apigateway/{api-id}/{stage-name}makes log groups easy to identify across accounts and regions.Empty format string passes Terraform apply but produces no useful logs
A
destination_arnalone satisfies the compliance check. But ifformatis empty or minimal, the resulting entries may lack the fields you need during an incident. Include at minimum$context.requestId,$context.identity.sourceIp,$context.httpMethod,$context.routeKey, and$context.status.
Audit evidence
Expect to see AWS Config rule evaluations showing all API Gateway V2 stages as compliant, confirming access log settings are defined. Pair that with the CloudWatch Logs log groups actually receiving entries, which shows logging is functional, not just configured. Console screenshots of the stage's "Logging" tab with a destination ARN and format string add supporting confirmation.
For ongoing assurance, the IncomingLogEvents metric on the log group shows continuous ingestion. If the organization forwards API Gateway access logs to a SIEM, dashboards or alerts built on that data round out the evidence package.
Framework-specific interpretation
PCI DSS v4.0: Requirements 10.2.1 and 10.2.2 call for audit trail generation covering access to system components. For APIs that process or transmit cardholder data, V2 access logs are the per-request record that satisfies this. Requirement 10 also covers monitoring, so these logs feed into whatever log aggregation or SIEM is in scope for the cardholder data environment.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
gatewayv2_stage_access_logging_enabledAWS Config Managed Rule:
API_GWV2_ACCESS_LOGS_ENABLEDCheckov Check:
CKV_AWS_76Powerpipe Control:
aws_compliance.control.gatewayv2_stage_access_logging_enabledProwler Check:
apigatewayv2_api_access_logging_enabledAWS Security Hub Control:
APIGateway.9KICS Query:
1b6799eb-4a7a-4b04-9001-8cceb9999326Trivy Check:
AWS-0001
Last reviewed: 2026-03-09