Mohammad Ali

Business & Digital Consultant

IT & Cloud Consultant

Career Growth Mentor

Sales & Strategy Advisor

0

No products in the cart.

Mohammad Ali

Business & Digital Consultant

IT & Cloud Consultant

Career Growth Mentor

Sales & Strategy Advisor

Blog Post

Automating AWS EC2 Instance Scheduling with Lambda and CloudWatch to Reduce Costs

September 9, 2026 Uncategorized

Keeping AWS EC2 instances running 24/7 can quickly inflate cloud bills, especially for non-production workloads that are only needed during specific hours. Manual instance management is time-consuming and error-prone, but there is a practical solution: automating EC2 start and stop operations using AWS Lambda and CloudWatch Events. In this tutorial, you’ll learn how to build a reliable scheduling automation for EC2 instances that minimizes costs and streamlines operations—without manual intervention—using native AWS services. You’ll get hands-on with Lambda function code, CloudWatch event configuration, and security considerations, so you can easily adapt the automation for your own AWS environment.

What Is AWS EC2 Instance Scheduling?

AWS EC2 instance scheduling is the practice of automatically starting or stopping EC2 instances based on defined schedules, such as business hours or workload demands. Instead of leaving instances running around the clock, organizations use automation to ensure that development, testing, or staging environments are only active when actually needed—significantly reducing unnecessary costs.

This tutorial uses AWS Lambda automation (serverless code execution) triggered by CloudWatch Events (now called EventBridge) to perform EC2 start/stop actions on a schedule. This achieves EC2 cost optimization with minimal maintenance overhead.

Prerequisites

  • An AWS account with access to EC2, Lambda, and CloudWatch services
  • One or more EC2 instances (non-production recommended for testing)
  • IAM permissions to create Lambda functions, CloudWatch rules, and necessary IAM roles
  • Basic familiarity with the AWS Management Console or AWS CLI
  • Python 3.x knowledge (for Lambda example code)

Step-by-Step Implementation

1. Identify EC2 Instances to Schedule

Decide which EC2 instances you want to control through scheduling. A best practice is to use EC2 tags for easy targeting, such as:

  • Key: Schedule
  • Value: office-hours

You can add tags via the AWS Console or CLI:

aws ec2 create-tags --resources i-1234567890abcdef0 --tags Key=Schedule,Value=office-hours

2. Create the IAM Role for Lambda

Your Lambda function needs permissions to start and stop EC2 instances. Create a minimal-permission IAM role:

  1. Go to IAM > Roles > Create role.
  2. Choose AWS service as the trusted entity and select Lambda for the use case.
  3. Attach a policy allowing only ec2:StartInstances, ec2:StopInstances, and ec2:DescribeInstances. Example policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:DescribeInstances"
      ],
      "Resource": "*"
    }
  ]
}

Security tip: Further restrict permissions by specifying only the required Regions or instance ARNs if possible.

3. Write the Lambda Function

Create a new AWS Lambda function in Python 3.x. The function below searches for instances with a specific Schedule tag and starts or stops them, based on the desired action.

import boto3
import os

ec2 = boto3.resource('ec2')

SCHEDULE_TAG_KEY = 'Schedule'
SCHEDULE_TAG_VALUE = os.environ.get('SCHEDULE_TAG_VALUE', 'office-hours')
ACTION = os.environ.get('ACTION', 'stop')  # 'start' or 'stop'

def lambda_handler(event, context):
    filters = [{
        'Name': f'tag:{SCHEDULE_TAG_KEY}',
        'Values': [SCHEDULE_TAG_VALUE]
    },
    {
        'Name': 'instance-state-name',
        'Values': ['stopped' if ACTION == 'start' else 'running']
    }]
    
    instances = list(ec2.instances.filter(Filters=filters))
    ids = [instance.id for instance in instances]
    if not ids:
        print(f'No instances to {ACTION}')
        return
    
    print(f'{ACTION.capitalize()}ping instances: {ids}')
    if ACTION == 'start':
        ec2.instances.filter(InstanceIds=ids).start()
    elif ACTION == 'stop':
        ec2.instances.filter(InstanceIds=ids).stop()
  • Set ACTION as an environment variable (start or stop).
  • Keep the function generic by using tag-based targeting.
  • The function avoids starting instances that are already running, and vice versa.

4. Deploy the Lambda Function

  1. Create a new Lambda function:

    • Runtime: Python 3.x
    • IAM role: Choose the role created earlier
    • Paste the code above into the inline editor
    • Set environment variables:
      • SCHEDULE_TAG_VALUE: office-hours
      • ACTION: start or stop (create one Lambda per action, or reuse with different configs)
    • Save and Deploy

5. Schedule the Lambda Function with CloudWatch Events

Use CloudWatch Events (EventBridge) rules to trigger Lambda functions on your custom schedule. For example, to start instances at 8:00 AM and stop them at 6:00 PM on weekdays:

Action Schedule Expression Description
Start cron(0 8 ? * MON-FRI *) 8:00 AM, Monday to Friday
Stop cron(0 18 ? * MON-FRI *) 6:00 PM, Monday to Friday

Create two CloudWatch Event rules:

  1. Go to CloudWatch > Rules > Create rule.
  2. Select Schedule and enter the CRON expression (see table above).
  3. Set the Lambda function for the appropriate action (start or stop).
  4. Repeat for the second Lambda function or a differently configured environment variable.

For more on CRON expressions in AWS, check the AWS documentation.

6. Monitoring and Notifications (Optional but Recommended)

Use CloudWatch Logs to monitor Lambda execution. To receive alerts on failures, configure a CloudWatch Alarm or SNS notification for Lambda errors.

Common Troubleshooting and Pitfalls

  • Time zone confusion: AWS uses UTC for CRON expressions. Adjust your schedule accordingly.
  • Insufficient IAM permissions: Ensure your Lambda role has ec2:StartInstances, ec2:StopInstances, and ec2:DescribeInstances for the needed instances.
  • No instances targeted: Make sure instance tags exactly match those configured in Lambda.
  • Lambda timeout or memory issues: For a large number of instances, increase Lambda timeout or add batching logic.
  • Recent manual changes: Manual stops or starts might interfere with the expected state. Automation only acts on current states.

Security and Production Considerations

  • Follow the principle of least privilege for Lambda’s IAM role.
  • For sensitive workloads, add extra logic to check instance state before stopping to avoid data loss or service interruption.
  • Monitor Lambda logs and set up notifications on errors.
  • Document automation for team awareness.
  • Test schedule automation in a development environment to validate your CRON expressions and permissions before applying to production instances.

Extending and Customizing Your Automation

You can adapt this pattern to:

  • Target instances in multiple Regions
  • Add more complex logic (e.g., by environment, by owner, or by tags)
  • Trigger Slack, email, or webhook notifications as part of the Lambda function
  • Integrate with Infrastructure as Code (e.g., Terraform Lambda resources)

Conclusion

By automating AWS EC2 instance scheduling with Lambda and CloudWatch, you ensure that non-production instances run only when needed, significantly reducing unnecessary cloud spending and simplifying operations. The step-by-step approach outlined in this tutorial helps you set up efficient, maintainable automation with native AWS tools, unlocking valuable time savings and cost optimization for your DevOps and cloud engineering teams.

For more practical automation tutorials and cloud cost strategies, stay tuned to MohammadAli.tech.

Write a comment