This check flags API Gateway stages where response caching is on but cache data is stored unencrypted. Cached responses can contain sensitive data, so enable cache encryption with one stage setting (cacheDataEncrypted=true) to protect data at rest.
API Gateway response caching is a quick win for performance. Turn it on for a stage and Gateway stores responses for a configurable TTL, cutting load on your backend and shaving latency off repeat requests. The catch is that those cached responses sit on disk, and by default that cache is not encrypted. If your API returns anything sensitive, you have just created a copy of that data in cleartext storage that lives outside your normal data controls.
The apigw_cacheencryption check looks for exactly this: a stage with caching enabled and cacheDataEncrypted set to false.
What this check detects
Every API Gateway stage can have a cache cluster attached. Caching is controlled per stage and can be overridden per method. The relevant flag is cacheDataEncrypted, part of the method settings on a stage.
This check fails when both of the following are true for a stage:
cachingEnabledistrue(a cache cluster is provisioned and serving responses)cacheDataEncryptedisfalse(cached responses are stored in plaintext)
If caching is off entirely, the check does not apply. The risk only exists once responses are actually being persisted in the cache.
Note: This applies to REST APIs (API Gateway v1). HTTP APIs and WebSocket APIs do not support response caching, so they are out of scope for this check.
Why it matters
Response caching does not know or care what is inside the payloads it stores. A GET on /users/me might return an email address, a phone number, or an internal account ID. A pricing endpoint might return contract terms. If that response gets cached without encryption, you have a plaintext copy of regulated or sensitive data sitting in AWS-managed cache storage.
Here is the practical risk:
- Data at rest exposure. Cache encryption uses an AWS-managed key to encrypt the stored response data. Without it, that layer of protection is simply missing.
- Compliance gaps. Frameworks like PCI DSS, HIPAA, and SOC 2 expect sensitive data to be encrypted at rest across every storage location. An unencrypted cache is an easy finding for an auditor and a hard one to explain.
- Inconsistent posture. Teams often encrypt the database, the S3 bucket, and the EBS volumes, then forget that the API cache is also a place data lands. It becomes the weakest link precisely because nobody is watching it.
Caching is a performance feature, but the moment it stores a response it becomes a data store. Treat it like one.
The fix costs nothing in dollars and almost nothing in latency, which makes leaving it off hard to justify.
How to fix it
You enable cache encryption at the stage level (applies to all methods) or per method. The setting is the same flag either way.
Console
- Open the API Gateway console and select your REST API.
- Go to Stages and pick the stage with caching enabled.
- Under Settings, find the Cache settings section.
- Check Encrypt cache data.
- Save changes, then redeploy the stage if prompted.
AWS CLI
Use update-stage with a JSON patch targeting the wildcard method path (*/*) to apply encryption to every cached method on the stage:
Warning: Changing cache settings invalidates the existing cache and may briefly cause a higher rate of backend requests while the cache repopulates. Roll this out during a low-traffic window if your backend is latency-sensitive.
aws apigateway update-stage \
--rest-api-id abc123xyz \
--stage-name prod \
--patch-operations \
op=replace,path=/*/*/caching/dataEncrypted,value=true
To encrypt a single method instead of the whole stage, target its specific path. The method path is URL-encoded, so GET /users becomes ~1users/GET:
aws apigateway update-stage \
--rest-api-id abc123xyz \
--stage-name prod \
--patch-operations \
op=replace,path=/~1users/GET/caching/dataEncrypted,value=true
Confirm the change took effect:
aws apigateway get-stage \
--rest-api-id abc123xyz \
--stage-name prod \
--query 'methodSettings.*.cacheDataEncrypted'
Terraform
If you manage cache settings through method settings, set cache_data_encrypted to true:
resource "aws_api_gateway_method_settings" "prod" {
rest_api_id = aws_api_gateway_rest_api.this.id
stage_name = aws_api_gateway_stage.prod.stage_name
method_path = "*/*"
settings {
caching_enabled = true
cache_data_encrypted = true
cache_ttl_in_seconds = 300
}
}
CloudFormation
ProdStage:
Type: AWS::ApiGateway::Stage
Properties:
RestApiId: !Ref MyRestApi
StageName: prod
CacheClusterEnabled: true
CacheClusterSize: "0.5"
MethodSettings:
- ResourcePath: "/*"
HttpMethod: "*"
CachingEnabled: true
CacheDataEncrypted: true
CacheTtlInSeconds: 300
Tip: If you have many stages to fix, script the get-stage and update-stage pair across your account. Pull all REST APIs with aws apigateway get-rest-apis, loop over their stages, and patch any stage where caching is on but encryption is off. Doing it in IaC instead means it stays fixed on the next deploy.
How to prevent it from happening again
Fixing the stage by hand solves today's problem. The next stage someone creates will repeat it unless you put a guardrail in place.
Policy as code in CI/CD
Catch unencrypted caches before they merge. With Checkov, the relevant rule is built in:
checkov -d . --check CKV_AWS_120
CKV_AWS_120 verifies that API Gateway caching is encrypted. Wire it into your pull request pipeline so a plan with an unencrypted cache fails the build.
If you prefer OPA/Conftest, a Rego policy against your Terraform plan can enforce the same rule:
package apigateway.cache
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_api_gateway_method_settings"
settings := resource.change.after.settings[_]
settings.caching_enabled == true
settings.cache_data_encrypted == false
msg := sprintf("API Gateway cache must be encrypted: %s", [resource.address])
}
Detective controls
For resources created outside your pipeline (console clicks, emergency changes), add a continuous check. AWS Config supports a managed rule, and Lensix flags this drift automatically across every account and region so a manually created stage does not slip through unnoticed.
Tip: Pair a preventive gate (CI/CD policy) with a detective control (continuous scanning). The gate stops new IaC, the scanner catches everything else. Relying on only one leaves a gap.
Best practices
- Encrypt every cache by default. Make
cache_data_encrypted = truethe standard in your Terraform modules so engineers cannot accidentally ship an unencrypted cache. - Cache deliberately, not broadly. Avoid caching endpoints that return per-user or sensitive data unless you genuinely need to. The safest cache is one that only holds non-sensitive, shared responses.
- Mind the cache key. If a cached endpoint varies by user, make sure authorization headers or user identifiers are part of the cache key. Otherwise one user can receive another user's cached response, which is a far worse problem than encryption alone.
- Set sane TTLs. A shorter TTL limits how long any cached data lingers. Match the TTL to how fresh the data needs to be.
- Review caching alongside data classification. When an endpoint starts returning new fields, revisit whether it should still be cached at all.
Danger: Never enable caching on authenticated endpoints without a cache key that includes the caller identity. An unkeyed cache can serve one user's private response to a different user. Encrypting the cache does nothing to stop this, since the leak happens through correct decryption to the wrong person.
Cache encryption is the kind of setting that is trivial to turn on and easy to forget. Bake it into your modules, gate it in CI, and scan for drift, and this check should stay green without anyone thinking about it again.

