Skip to main content

Schedule EC2 Instances to Stop and Start with the AWS CLI

Two API calls on a timer, plus the three checks that stop a schedule from breaking an instance you care about.

An EC2 schedule is StopInstances in the evening and StartInstances in the morning. This walkthrough does it with the AWS CLI and Amazon EventBridge Scheduler, then shows how to prove each transition happened. It only works for instances whose root volume is EBS: AWS does not let you stop an instance with an instance store root volume.

Three checks before the first stop

Root device type. Run this and look for ebs in the root column:

Terminal window
aws ec2 describe-instances \
--instance-ids i-0abc1234def567890 \
--query "Reservations[].Instances[].{id:InstanceId,type:InstanceType,root:RootDeviceType,state:State.Name,publicIp:PublicIpAddress}" \
--output table

Instance store volumes. AWS erases the data on any instance store volume when the instance stops. If the workload writes scratch files, caches or a local database to NVMe instance storage, copy what it needs to EBS or S3 first, or leave that instance off the schedule.

Public IPv4 address. An auto-assigned public IP is released on stop and a different one is assigned on start. Anything that allowlists the address or points DNS at it will break the next morning. The private IPv4 address stays with the network interface. If the address must survive, associate an Elastic IP, and note that AWS charges for every Elastic IP address whether it is in use or not.

IAM actions you need

For a person or pipeline running the commands by hand: ec2:DescribeInstances, ec2:StopInstances, ec2:StartInstances. To limit the blast radius, scope the stop and start actions with an aws:ResourceTag condition so only tagged instances qualify (see controlling who can start and stop resources).

Run one cycle by hand

Terminal window
aws ec2 stop-instances --instance-ids i-0abc1234def567890
aws ec2 wait instance-stopped --instance-ids i-0abc1234def567890
aws ec2 start-instances --instance-ids i-0abc1234def567890
aws ec2 wait instance-running --instance-ids i-0abc1234def567890

If the instance was launched with hibernation enabled, stop-instances --hibernate saves RAM to the EBS root volume instead of a clean shutdown; if hibernation fails, a normal shutdown happens.

Put it on a timer

EventBridge Scheduler can call the EC2 API directly through a universal target, so no Lambda function is needed.

  1. Create an execution role that EventBridge Scheduler can assume. Save this trust policy as trust.json:
Terminal window
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Principal": { "Service": "scheduler.amazonaws.com" }, "Action": "sts:AssumeRole" }
]
}
Terminal window
aws iam create-role --role-name SchedulerEc2StartStop \
--assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name SchedulerEc2StartStop \
--policy-name ec2-start-stop \
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ec2:StopInstances","ec2:StartInstances"],"Resource":"arn:aws:ec2:*:123456789012:instance/*"}]}'
  1. Create the evening stop. The cron format has six fields (minutes, hours, day-of-month, month, day-of-week, year), and the timezone flag takes an IANA name so the schedule follows local daylight saving:
Terminal window
aws scheduler create-schedule \
--name ec2-dev-stop-weeknights \
--schedule-expression "cron(0 19 ? * MON-FRI *)" \
--schedule-expression-timezone "Europe/London" \
--flexible-time-window Mode=OFF \
--target '{"Arn":"arn:aws:scheduler:::aws-sdk:ec2:stopInstances","RoleArn":"arn:aws:iam::123456789012:role/SchedulerEc2StartStop","Input":"{\"InstanceIds\":[\"i-0abc1234def567890\"]}"}'
  1. Create the morning start the same way with --name ec2-dev-start-weekdays, cron(0 7 ? * MON-FRI *) and the target ARN ending in ec2:startInstances.

To undo, delete both: aws scheduler delete-schedule --name ec2-dev-stop-weeknights.

What still bills while it is stopped

  • You are not charged for instance usage or data transfer while the instance is stopped.
  • Every attached EBS volume keeps billing for its provisioned storage.
  • Elastic IP addresses keep billing.
  • Each start begins per-second billing again, with a one-minute minimum per start.

So the saving is the compute rate for the hours it is off, not the instance’s whole line on the bill.

Prove it ran

Terminal window
aws ec2 describe-instances --instance-ids i-0abc1234def567890 \
--query "Reservations[].Instances[].State.Name" --output text
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=StopInstances

The first should print stopped after 19:00 and running after 07:00. The CloudTrail lookup covers the last 90 days and shows which principal made each call, which is how you tell the schedule’s stops apart from a colleague’s.

Doing the same in ZopNight

ZopNight starts and stops EC2 instances from a schedule that carries an IANA timezone and cron start and stop expressions, attached to single instances or to a resource group. Someone working late can set a force-on override with an expiry instead of editing the schedule. The account must be connected with read and write access; a read-only connection still gets discovery and recommendations but never issues a stop. The EC2 scheduling page covers what the schedule does to each instance.

Stop watching the waste.
Start cutting it.

See. Find. Fix. Automatic.

Connect your first cloud account in under 5 minutes. See your first remediation in under 7. No credit card required.

CDCR connect detect classify remediate
full audit every action traceable
read-only default access
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·