# Unused KMS Key

> Customer-managed KMS keys bill $1 monthly whether or not anything encrypts under them. ZopNight nominates enabled keys with an alias count of 0 as unused candidates, but only when measured request activity or billing-backed cost can actually prove the silence, because deleting a key that still protects data destroys that data.

Source: https://zop.dev/integrations/aws/recommendations/unused-kms-key
Updated: 2026-08-19

---

## The most dangerous dollar in the directory

The saving here is $1 per key per month. The downside of getting it wrong is unbounded: a
KMS key still referenced by an EBS volume, an S3 bucket, or an RDS snapshot renders that
data permanently unreadable once the key is gone. That asymmetry shapes everything about
this rule. It is the most heavily guarded orphan check in the AWS set, and it deliberately
says "may be unused, verify" rather than "delete".

## Zero aliases is a proxy, and the rule admits it

KMS exposes per-key usage only through CloudTrail, which discovery does not join, so there
is no last-used timestamp to gate on. What discovery does emit is the alias count, and an
enabled customer-managed key with zero aliases (nothing human-facing points at it) is a
strong candidate. Candidate, not verdict: services can reference keys by raw ARN with no
alias involved.

## Proof, or abstention

Before firing, the rule demands the silence be measurable, through either of two independent
signals. A present per-key request metric showing zero requests per day is activity proof;
billing-backed cost sitting at the bare $1 base is cost proof, since [KMS adds $0.03 per
10,000 requests on top of that flat key fee](https://aws.amazon.com/kms/pricing/) and paid request charges mean the key is alive. Any measured activity,
or cost above the base, suppresses. And when activity could not be measured *and* the cost
figure is a rack-rate estimate rather than a real invoice, the rule abstains entirely. An
unprovable orphan stays unflagged.

## Do the verification the rule cannot

```bash
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue=<key-id> \
  --start-time "$(date -u -v-90d +%Y-%m-%dT%H:%M:%SZ)" --max-results 20
```

Ninety days of no Encrypt/Decrypt/GenerateDataKey events is the confirmation worth having.

## Deletion is scheduled, never instant

KMS refuses immediate deletion: you schedule it with a 7–30 day waiting period, during which
the key is disabled and any surviving dependency surfaces as an error you can still undo. Use
the full 30 days for anything with history. Disabling the key first and waiting a billing
cycle is the even gentler path: reversible, and it flushes out the annual batch job nobody
remembered.
