Skip to main content
In this lesson you’ll learn how to use CloudFormation’s cfn-init helper to configure an EC2 instance (install packages, enable services, etc.) by:
  • Declaring AWS::CloudFormation::Init metadata in your template, and
  • Invoking cfn-init from the instance UserData during boot.
Below I walk through the essential pieces (metadata, parameters, security group, UserData) and then provide a complete, minimal CloudFormation YAML example that combines them.
Make sure the EC2 instance has an IAM instance profile that gives it permission to read stack metadata (this is typically an Instance Profile containing an IAM role you created earlier).

How cfn-init works (high level)

  • The template embeds configuration instructions under the resource’s MetadataAWS::CloudFormation::Init section.
  • On instance boot, a UserData script calls /opt/aws/bin/cfn-init (or equivalent) to fetch the metadata from the stack and apply the configuration (install packages, write files, and start/enable services).
  • cfn-init uses the instance’s IAM role (Instance Profile) to retrieve stack metadata, so the instance must have the appropriate permissions.

1) cfn-init metadata (packages and services)

The AWS::CloudFormation::Init metadata describes how to configure the instance. The fragment below installs the httpd package using yum and ensures the httpd service is enabled and running using sysvinit:
Metadata:
  AWS::CloudFormation::Init:
    config:
      packages:
        yum:
          httpd: []
      services:
        sysvinit:
          httpd:
            enabled: true
            ensureRunning: true
Notes:
  • packages instructs cfn-init to install OS packages (here using yum for Amazon Linux).
  • services tells cfn-init which service manager to interact with. Use sysvinit for older AMIs or systemd for Amazon Linux 2 and most modern distributions.

2) Properties: instance type, AMI, security group, and instance profile

The EC2 Instance resource needs standard properties: InstanceType, ImageId, SecurityGroupIds, and IamInstanceProfile. Many templates use a region-based mapping to choose the ImageId. Example parameters and mapping:
Parameters:
  MyVPC:
    Type: AWS::EC2::VPC::Id
    Description: Select the VPC to launch the EC2 instance in

  MyInstanceType:
    Type: String
    Default: t3.micro
    Description: EC2 instance type

  MyCFNInstanceProfile:
    Type: AWS::IAM::InstanceProfile::Name
    Description: Name of an existing IAM Instance Profile to attach to the EC2 instance

Mappings:
  RegionMap:
    us-east-2:
      AMI: ami-0eb9d6fc9fab44d24
    eu-west-1:
      AMI: ami-0b3e7dd7b2a99b08d
    us-east-1:
      AMI: ami-0150ccaf51ab55a51

3) Security group

Create a security group that allows SSH (22) and HTTP (80) access and attach it to the instance via SecurityGroupIds:
MySecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupDescription: Allow SSH and HTTP access
    VpcId: !Ref MyVPC
    SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: 0.0.0.0/0

4) UserData: invoke cfn-init

UserData should invoke cfn-init on instance boot to apply the AWS::CloudFormation::Init configuration. Encode the script with Fn::Base64 and use Fn::Sub to allow CloudFormation pseudo-parameters to be expanded:
UserData:
  Fn::Base64: !Sub |
    #!/bin/bash
    /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
Practical tips:
  • The typical path for cfn-init on Amazon Linux is /opt/aws/bin/cfn-init. Ensure the AMI includes the aws-cfn-bootstrap package or otherwise provides cfn-init.
  • Use the -v flag for more verbose output when troubleshooting.
  • Wrap the script with Fn::Sub when embedding pseudo-parameters such as ${AWS::StackName} so substitutions happen before the Base64 encoding.
Confirm the AMI you use contains cfn-init (often provided by the aws-cfn-bootstrap package). Also choose the correct service manager key in metadata (sysvinit vs systemd) to match your AMI. Without the correct AMI and permissions, cfn-init will not be able to apply the configuration.

Full minimal template (combines the pieces)

This example ties together Parameters, Mappings, the security group, and the EC2 instance with AWS::CloudFormation::Init metadata and UserData that runs cfn-init.
AWSTemplateFormatVersion: '2010-09-09'
Description: Minimal template demonstrating cfn-init usage to install and start httpd

Parameters:
  MyVPC:
    Type: AWS::EC2::VPC::Id
    Description: Select the VPC to launch the EC2 instance in

  MyInstanceType:
    Type: String
    Default: t3.micro
    Description: EC2 instance type

  MyCFNInstanceProfile:
    Type: AWS::IAM::InstanceProfile::Name
    Description: Name of an existing IAM Instance Profile to attach to the EC2 instance

Mappings:
  RegionMap:
    us-east-2:
      AMI: ami-0eb9d6fc9fab44d24
    eu-west-1:
      AMI: ami-0b3e7dd7b2a99b08d
    us-east-1:
      AMI: ami-0150ccaf51ab55a51

Resources:
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH and HTTP access
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0

  MyInstance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              httpd: []
          services:
            sysvinit:
              httpd:
                enabled: true
                ensureRunning: true
    Properties:
      InstanceType: !Ref MyInstanceType
      ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
      IamInstanceProfile: !Ref MyCFNInstanceProfile
      SecurityGroupIds:
        - !Ref MySecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}

Outputs:
  InstanceId:
    Description: Instance ID of the created EC2 instance
    Value: !Ref MyInstance

Template components at a glance

Template sectionPurposeExample/key items
ParametersInputs to the templateMyVPC, MyInstanceType, MyCFNInstanceProfile
MappingsRegion-specific AMI selectionRegionMapAMI
Resources — Security GroupNetwork access rulesMySecurityGroup (SSH, HTTP)
Resources — EC2 InstanceInstance configuration & metadataMyInstance with MetadataAWS::CloudFormation::Init
UserDataBoot-time invocation of cfn-initFn::Base64 + Fn::Sub script calling /opt/aws/bin/cfn-init

Final checklist

  • Verify the MyCFNInstanceProfile Instance Profile exists and grants the instance permission to read CloudFormation stack metadata.
  • Confirm the chosen AMI includes cfn-init (or install aws-cfn-bootstrap) and uses the expected service manager (sysvinit vs systemd).
  • Use -v with cfn-init for verbose logs while debugging.
  • Ensure your security group allows the inbound traffic necessary for testing (SSH/HTTP in this example).
When deployed, this template causes the EC2 instance to call cfn-init at boot. cfn-init will read the embedded AWS::CloudFormation::Init instructions to install httpd and ensure the service is enabled and running.

Watch Video