Skip to content

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_arn looks correct in Terraform and the stage deploys without error, but no entries appear in the log group. For same-account logging, attach the managed policy AmazonAPIGatewayPushToCloudWatchLogs to the role configured in the API Gateway account settings.

  • Default stage auto-deploy overwriting settings

    When auto_deploy = true is set on an aws_apigatewayv2_stage with stage name $default, redeployments triggered by route or integration changes recreate the stage configuration. If access_log_settings is 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_group without retention_in_days means 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_arn alone satisfies the compliance check. But if format is 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: gatewayv2_stage_access_logging_enabled

  • AWS Config Managed Rule: API_GWV2_ACCESS_LOGS_ENABLED

  • Checkov Check: CKV_AWS_76

  • Powerpipe Control: aws_compliance.control.gatewayv2_stage_access_logging_enabled

  • Prowler Check: apigatewayv2_api_access_logging_enabled

  • AWS Security Hub Control: APIGateway.9

  • KICS Query: 1b6799eb-4a7a-4b04-9001-8cceb9999326

  • Trivy Check: AWS-0001

Last reviewed: 2026-03-09