AWS EventBridge Scheduler: Starting and Stopping EC2 on a Schedule

AWS EventBridge Scheduler: Starting and Stopping EC2 on a Schedule

Starting and stopping EC2 instances on a cron schedule with EventBridge Scheduler calling the EC2 API directly, no Lambda involved.

Takahiro Iwasa
3 min read

EventBridge Scheduler can start and stop EC2 instances on a cron schedule without any Lambda function in between.

Building

Start an EC2 instance for the test.

Terminal window
aws ec2 run-instances \
--image-id ami-0f9fe1d9214628296 \
--count 1 \
--instance-type t2.micro \
--security-group-ids <SECURITY_GROUP_IDS> \
--subnet-id <SUBNET_ID>

The ScheduleStartExpression and ScheduleStopExpression parameters on lines 11-16 define the schedule using cron expression.

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 scheduler
Parameters:
Prefix:
Type: String
Default: ec2-scheduler
InstanceIds:
Type: String
Description: '"i-1234567890abcdefg", "..."'
ScheduleStartExpression:
Type: String
Default: 'cron(0 7 * * ? *)'
ScheduleStopExpression:
Type: String
Default: 'cron(0 22 * * ? *)'
ScheduleTimezone:
Type: String
Description: IANA timezone identifier. For the full list, refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
Default: UTC
AllowedValues:
- UTC
- Japan
Resources:
ScheduleStart:
Type: AWS::Scheduler::Schedule
Properties:
Name: !Sub ${Prefix}-schedule-start
ScheduleExpression: !Ref ScheduleStartExpression
ScheduleExpressionTimezone: !Ref ScheduleTimezone
FlexibleTimeWindow:
Mode: 'OFF'
State: ENABLED
Target:
Arn: arn:aws:scheduler:::aws-sdk:ec2:startInstances
Input: !Sub |-
{
"InstanceIds": [${InstanceIds}]
}
RoleArn: !GetAtt Role.Arn
ScheduleStop:
Type: AWS::Scheduler::Schedule
Properties:
Name: !Sub ${Prefix}-schedule-stop
ScheduleExpression: !Ref ScheduleStopExpression
ScheduleExpressionTimezone: !Ref ScheduleTimezone
FlexibleTimeWindow:
Mode: 'OFF'
State: ENABLED
Target:
Arn: arn:aws:scheduler:::aws-sdk:ec2:stopInstances
Input: !Sub |-
{
"InstanceIds": [${InstanceIds}]
}
RoleArn: !GetAtt Role.Arn
Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${Prefix}-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- scheduler.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: ec2
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:StartInstances
- ec2:StopInstances
Resource: !Sub arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/i-*

Deploy the CloudFormation stack with the following command:

Terminal window
aws cloudformation deploy \
--template-file ./template.yaml \
--stack-name ec2-scheduler \
--parameter-overrides InstanceIds='"<INSTANCE_ID1>", "<INSTANCE_ID2>"' \
--capabilities CAPABILITY_NAMED_IAM

Testing

The provisioned EventBridge Scheduler will automatically start the EC2 instance at 7:00 AM and stop it at 10:00 PM.

Cleaning Up

Clean up all the AWS resources provisioned during this example with the following command:

Terminal window
aws ec2 terminate-instances --instance-ids <INSTANCE_IDS>
aws cloudformation delete-stack --stack-name ec2-scheduler

Conclusion

Setting up two EventBridge Schedules with cron expressions got an EC2 instance starting at 7 AM and stopping at 10 PM automatically. Targeting the EC2 API directly through arn:aws:scheduler:::aws-sdk:ec2:startInstances is what keeps this setup free of any Lambda function — EventBridge Scheduler can call the AWS SDK directly, so the schedule and the IAM role granting ec2:StartInstances/ec2:StopInstances are the entire solution. The one setting worth double-checking before relying on this for cost savings is ScheduleExpressionTimezone: since it defaults to UTC in the template, a schedule meant to align with business hours in another timezone needs that parameter set explicitly, or instances will start and stop several hours off from what’s expected.

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.