The nine commands (Command reference) are ordinary SPL, so anything you can express in a search you can run, save, schedule, and alert on. This page collects searches by goal and shows how to turn them into alerts that watch your accounts live. The bundled dashboards package many of these as ready-made panels - this page is for building your own.
Inventory and posture
Instances by type and state, across the estate:
| awsget kind=instances account=* region=*
| spath path=State.Name output=state
| stats count by account region InstanceType stateS3 buckets without a full public-access block - a posture check. A bucket is listed when any of the four block settings is off or missing, which is what the S3 dashboard marks REVIEW. It does not show that a bucket is public; that depends on the bucket’s policy and ACLs:
| awsget kind=buckets account=prod detail=true
| spath input=PublicAccessBlockConfiguration path=BlockPublicAcls output=b1
| spath input=PublicAccessBlockConfiguration path=BlockPublicPolicy output=b2
| spath input=PublicAccessBlockConfiguration path=IgnorePublicAcls output=b3
| spath input=PublicAccessBlockConfiguration path=RestrictPublicBuckets output=b4
| eval all_blocked=if(b1="true" AND b2="true" AND b3="true" AND b4="true", "true", "false")
| where all_blocked="false"
| table BucketName b1 b2 b3 b4Security groups that allow SSH from anywhere:
| awsget kind=security-groups region=us-east-1 detail=true
| spath path=SecurityGroupIngress{} output=rules
| mvexpand rules
| spath input=rules
| eval source=coalesce(CidrIp, CidrIpv6), lo=coalesce(FromPort, 0), hi=coalesce(ToPort, 65535)
| where (source="0.0.0.0/0" OR source="::/0") AND (IpProtocol="-1" OR IpProtocol="tcp") AND lo<=22 AND hi>=22
| table GroupId GroupName IpProtocol FromPort ToPort sourceLambda functions still on a deprecated runtime:
| awsget kind=lambdas account=* region=*
| where Runtime IN ("nodejs16.x", "python3.6", "python3.7", "python3.8", "python3.9", "ruby2.7", "go1.x", "java8", "dotnetcore3.1")
| table account region FunctionName RuntimeCloudWatch alarms that would notify nobody - actions switched off, or no alarm action at all. | awsget reads an alarm’s configuration; its current state (OK, ALARM) is not part of what the Cloud Control API returns, so this is a posture check, not a list of what is firing:
| awsget kind=alarms region=us-east-1
| spath input=_raw output=actions path="AlarmActions{}"
| eval action_count=coalesce(mvcount(actions), 0), actions_enabled=coalesce(ActionsEnabled, "true")
| where actions_enabled="false" OR action_count=0
| table AlarmName Namespace MetricName actions_enabled action_countAn inventory of IAM users, the starting point for an access review. IAM users list by name only, so detail=true fetches each one’s full record:
| awsget kind=users account=prod detail=true
| table UserName ArnA user’s record does not carry its access keys or their age. To see what one key has been doing, ask CloudTrail for it:
| awscloudtrail attribute=AccessKeyId value=AKIAIOSFODNN7EXAMPLE start=-7d account=prod
| stats count by event_source event_nameIncidents and errors
Lambda timeouts in the last hour, by function:
| awslogs groups=/aws/lambda/* filter="Task timed out" start=-1h region=us-east-1
| where isnull(notice)
| stats count by logGroup
| sort - countThese are counts of the newest events limit= allows each group, not of the whole window. When a group holds more than its share, the command adds a row with a notice field saying so; where isnull(notice) keeps that row out of the count. For a count over the whole window, use a Logs Insights query= instead.
Timeouts and crashes for one function, over time - a crash logs Process exited before completing or Runtime exited with error rather than a timeout, and a Logs Insights query counts both. This is the query behind the Lambda detail dashboard’s failures panel:
| awslogs groups=/aws/lambda/my-function query="stats sum(@message like /Task timed out/) as timeouts, sum(@message like /Process exited before completing|Runtime exited with error/) as crashes by bin(5m)" start=-1h region=us-east-1Errors for one function from CloudWatch:
| awsmetrics namespace=AWS/Lambda metric=Errors dimensions=FunctionName=payments-api stat=Sum period=300 start=-3h region=us-east-1
| timechart span=5m sum(value) as errorsErrors for every function at once, with a Metrics Insights query - one series per function, named in label:
| awsmetrics expression="SELECT SUM(Errors) FROM SCHEMA(\"AWS/Lambda\", FunctionName) GROUP BY FunctionName" period=300 start=-3h region=us-east-1
| stats sum(value) as errors by label
| where errors>0
| sort - errorsWhat changed before the incident - management events in the window, by who:
| awscloudtrail start=-2h end=-1h region=us-east-1
| where read_only="false"
| stats count values(event_name) as events by username event_sourceWho did what
Console logins and where from:
| awscloudtrail attribute=EventName value=ConsoleLogin start=-7d region=us-east-1
| spath path=sourceIPAddress output=source_ip
| table _time username source_ipEvery write action taken with one access key:
| awscloudtrail attribute=AccessKeyId value=AKIAEXAMPLE start=-30d region=us-east-1
| where read_only="false"
| table _time event_source event_nameThe CloudTrail - Event Explorer and Principal investigation dashboards package these with pickers; see Dashboards.
Cost
Cost Explorer bills per request, so these are searches to run on purpose, not to schedule every minute.
This month by service:
| awscost granularity=DAILY group_by=SERVICE start=@mon account=prod
| stats sum(UnblendedCost) as cost by group
| sort - costCost by team tag, month over month:
| awscost granularity=MONTHLY group_by=TAG:team start=-2mon@mon account=*
| chart sum(UnblendedCost) as cost over group by startTags and governance
Tagged resources missing the owner tag, by service:
| awstag account=prod region=us-east-1
| where isnull(tag_owner)
| rex field=arn "arn:aws:(?<service>[^:]+):"
| stats count by serviceThis covers resources that carry at least one tag, or did once: the Tagging API does not return a resource that was never tagged, so a count of zero here does not mean everything has an owner. For a complete answer about one kind of resource, list that kind and read its tags:
| awsget kind=instances account=prod region=us-east-1 detail=true
| spath path=Tags{}.Key output=tag_keys
| eval has_owner=if(isnotnull(mvfind(tag_keys, "^owner$")), "yes", "no")
| where has_owner="no"
| table InstanceId tag_keysEverything tagged for one environment:
| awstag tag="env=prod" account=* region=*
| stats count by account regionAthena and the catalog
Explore a database before querying it:
| awsglue resource=tables database=cloudtrail_logs region=us-east-1Then query it:
| awsathena database=cloudtrail_logs region=us-east-1
query="SELECT useridentity.arn, count(*) AS calls FROM cloudtrail WHERE eventtime > '2026-09-01' GROUP BY 1 ORDER BY 2 DESC LIMIT 25"Alerting on live AWS state
Turn any of these into an alert: build the search, choose Save As - Alert, set a schedule, and trigger on the result count (for example, “number of results is greater than 0”). Because each run queries AWS live, the alert evaluates the account’s current state every time it fires - ideal for “something is wrong right now” conditions: a bucket whose public-access block came off, a security group opened to the world, a function on a deprecated runtime, an alarm whose actions were switched off.
Three things follow from how scheduled searches run:
- An inventory search is a snapshot.
| awsgetsees the account at the moment it runs, not a window of history: “a security group allows 0.0.0.0/0 right now” is a good alert, “which groups were open last Tuesday” is a question for ingested data. The time-series commands are different -| awsmetrics,| awslogs,| awscloudtrail, and| awscosttakestart=andend=, or the search’s own time range - so a scheduled search can ask CloudWatch for last week’s errors and invocations without ingesting anything. - Know which credential an alert runs with. A scheduled search runs as its owner, so it uses the owner’s per-user credential for the account when they have one, and the account’s shared credential otherwise. For an identity that does not depend on who saved the alert, give alerts an owner with no per-user credential, and point them at an account whose shared credential is read-only and scoped to what the alert needs.
- Mind the cache and the bill. A result cached for a minute will not reflect a change from ten seconds ago; add
cache=0when timing matters. Do not schedule| awscostor Logs Insights queries on a tight cadence - both are billed per call or per GB scanned.
Example - alert when a security group opens SSH to the world. Use the security-group search above, save it as an alert, schedule it every few minutes with cache=0, and trigger when the result count is greater than zero. The triggered alert lists exactly which groups and rules matched, so the notification is actionable on its own.