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.
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.
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 schedules as cron expressions.
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:
aws cloudformation deploy \ --template-file ./template.yaml \ --stack-name ec2-scheduler \ --parameter-overrides InstanceIds='"<INSTANCE_ID1>", "<INSTANCE_ID2>"' \ --capabilities CAPABILITY_NAMED_IAMTesting
With the default UTC timezone in this template, EventBridge Scheduler starts the EC2 instance at 07:00 UTC and stops it at 22:00 UTC.
Cleaning Up
Clean up all the AWS resources provisioned during this example with the following command:
aws ec2 terminate-instances --instance-ids <INSTANCE_IDS>aws cloudformation delete-stack --stack-name ec2-schedulerConclusion
Two EventBridge Scheduler schedules start the EC2 instance at 07:00 and stop it at 22:00 in the timezone selected by ScheduleExpressionTimezone.
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.
Related posts
Sign in with Slack Using Cognito User Pools and OIDC
Federating Cognito user pools with Slack over OIDC and wiring "Sign in with Slack" into a Next.js app with Amplify.
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
Containerizing a FastAPI backend and deploying it to a single Lambda function with Lambda Web Adapter and AWS CDK.
API Gateway WebSocket: Implementing a Mock Integration
Building an API Gateway WebSocket API entirely with mock integrations, returning canned responses with no backend Lambda involved.
Uploading to S3 Through CloudFront Signed URLs
CloudFront signed URLs let you upload to S3 through a custom domain—useful when direct S3 pre-signed URLs are not an option.
From Monolith to Microservices in AWS: Three Migration Patterns
Inspired by Monolith to Microservices, this example explains monolith-to-microservices patterns on AWS.
