AWS CloudFormation helper scripts are a set of Python-based utilities that automate software installation and service configuration on EC2 instances launched through CloudFormation stacks. They bridge the gap between declaring resources in a template and actually configuring the operating system inside those instances.
The four helper scripts – cfn-init, cfn-signal, cfn-hup, and cfn-get-metadata – handle everything from installing packages and writing config files to signaling stack completion and detecting live metadata changes. This guide covers installation on any Linux distribution (RHEL, Ubuntu, Debian, Amazon Linux), template configuration with AWS::CloudFormation::Init, signaling with CreationPolicy, and troubleshooting common issues.
Prerequisites
- An AWS account with permissions to create CloudFormation stacks and EC2 instances
- EC2 instances running a supported Linux distribution – Amazon Linux 2/2023, RHEL 9/10, Rocky Linux 9/10, AlmaLinux 9/10, Ubuntu 22.04/24.04, or Debian 12/13
- Python 3.4 or later installed on non-Amazon Linux instances
- AWS CLI installed and configured (for stack deployment and testing)
- An IAM role attached to EC2 instances with permissions to read CloudFormation stack metadata
- Security group allowing outbound HTTPS (port 443) to reach AWS API endpoints
Step 1: Install CloudFormation Helper Scripts on Linux
The helper scripts ship in the aws-cfn-bootstrap package. Installation varies by distribution. On Amazon Linux, they come preinstalled at /opt/aws/bin. For all other Linux distributions, install them manually using one of the methods below.
Method 1: Install from tarball (all Linux distributions)
This method works on any Linux system with Python 3. Download the latest Python 3 compatible package directly from AWS:
curl -O https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
Install the package using pip. If pip is not installed, install it first with your distribution’s package manager (python3-pip on most systems):
sudo pip3 install aws-cfn-bootstrap-py3-latest.tar.gz
Create the standard /opt/aws/bin directory and symlink the helper scripts so CloudFormation templates can find them at the expected path:
sudo mkdir -p /opt/aws/bin
sudo ln -sf /usr/local/bin/cfn-init /opt/aws/bin/cfn-init
sudo ln -sf /usr/local/bin/cfn-signal /opt/aws/bin/cfn-signal
sudo ln -sf /usr/local/bin/cfn-hup /opt/aws/bin/cfn-hup
sudo ln -sf /usr/local/bin/cfn-get-metadata /opt/aws/bin/cfn-get-metadata
Verify the installation by checking the version:
cfn-init --version
You should see the aws-cfn-bootstrap version printed:
cfn-init 2.0
Method 2: Install on RHEL/Rocky/AlmaLinux (RPM-based)
On RHEL-family distributions, make sure Python 3 and pip are available, then install from the tarball. The process is the same as Method 1, but ensure the prerequisites are in place first:
sudo dnf install -y python3 python3-pip
sudo pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
Create the symlinks:
sudo mkdir -p /opt/aws/bin
sudo ln -sf /usr/local/bin/cfn-init /opt/aws/bin/cfn-init
sudo ln -sf /usr/local/bin/cfn-signal /opt/aws/bin/cfn-signal
sudo ln -sf /usr/local/bin/cfn-hup /opt/aws/bin/cfn-hup
sudo ln -sf /usr/local/bin/cfn-get-metadata /opt/aws/bin/cfn-get-metadata
Method 3: Install on Ubuntu/Debian (DEB-based)
On Debian-family distributions, install Python 3 and pip, then use the same tarball method:
sudo apt update
sudo apt install -y python3 python3-pip
sudo pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
Set up the cfn-hup init script for Ubuntu/Debian systems:
sudo mkdir -p /opt/aws/bin
sudo ln -sf /usr/local/bin/cfn-init /opt/aws/bin/cfn-init
sudo ln -sf /usr/local/bin/cfn-signal /opt/aws/bin/cfn-signal
sudo ln -sf /usr/local/bin/cfn-hup /opt/aws/bin/cfn-hup
sudo ln -sf /usr/local/bin/cfn-get-metadata /opt/aws/bin/cfn-get-metadata
Method 4: Install on Amazon Linux (preinstalled)
On Amazon Linux 2 and Amazon Linux 2023, the helper scripts are already installed. To ensure you have the latest version, run:
sudo yum install -y aws-cfn-bootstrap
The scripts are available at /opt/aws/bin/ and ready to use immediately.
Step 2: Configure cfn-init in a CloudFormation Template
The cfn-init script reads the AWS::CloudFormation::Init metadata key from your template and executes the instructions. This is the primary way to declaratively configure an EC2 instance – installing packages, writing files, and starting services without writing shell scripts.
Here is a template snippet that installs and configures Nginx using cfn-init:
Resources:
WebServer:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
nginx: []
files:
/etc/nginx/conf.d/app.conf:
content: |
server {
listen 80;
server_name _;
root /var/www/html;
index index.html;
}
mode: "000644"
owner: root
group: root
/var/www/html/index.html:
content: |
<h1>CloudFormation deployed this server</h1>
mode: "000644"
owner: root
group: root
services:
sysvinit:
nginx:
enabled: true
ensureRunning: true
files:
- /etc/nginx/conf.d/app.conf
The packages section installs packages using the system package manager. The files section writes configuration files with specified ownership and permissions. The services section enables and starts services, with automatic restart when watched files change.
To trigger cfn-init, add it to the instance’s UserData:
Properties:
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
The --resource flag must match the logical name of the resource that contains the AWS::CloudFormation::Init metadata (in this case, WebServer).
Step 3: Use cfn-signal with WaitCondition
By default, CloudFormation marks an EC2 instance as CREATE_COMPLETE as soon as the instance launches – not when your software is actually configured. The cfn-signal script tells CloudFormation to wait until your setup finishes before proceeding.
Add a WaitCondition and WaitConditionHandle to your template:
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
DependsOn: WebServer
Properties:
Handle: !Ref WaitHandle
Timeout: "600"
Count: 1
Then signal success (or failure) at the end of UserData:
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? \
--stack ${AWS::StackName} \
--resource WaitCondition \
--region ${AWS::Region}
The -e $? flag passes the exit code of the previous command. If cfn-init fails, cfn-signal sends a failure signal, and CloudFormation rolls back the stack. The Timeout value of 600 gives the instance 10 minutes to complete setup before CloudFormation times out.
Step 4: Configure cfn-hup for Live Config Updates
The cfn-hup daemon runs on the instance and polls CloudFormation for metadata changes at a configurable interval. When it detects changes to the template metadata, it runs the actions you define – typically re-running cfn-init to apply updated configuration without replacing the instance.
cfn-hup needs two configuration files. Add them both to the files section of your AWS::CloudFormation::Init metadata:
files:
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
interval=5
mode: "000400"
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.WebServer.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
runas=root
mode: "000400"
owner: root
group: root
The cfn-hup.conf file tells cfn-hup which stack to monitor and how often to check (every 5 minutes in this example). The cfn-auto-reloader.conf hook triggers cfn-init to re-run whenever the Init metadata on the WebServer resource changes.
Add cfn-hup to the services section so it starts automatically and restarts if its config changes:
services:
sysvinit:
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
Step 5: Use CreationPolicy Instead of WaitCondition
CreationPolicy is the modern, simpler alternative to WaitCondition for signaling instance readiness. It attaches directly to the EC2 resource – no separate WaitConditionHandle needed. AWS recommends CreationPolicy for new templates.
Add the CreationPolicy attribute to your EC2 instance resource:
WebServer:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT10M
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
nginx: []
The Timeout: PT10M uses ISO 8601 duration format – 10 minutes in this case. The Count: 1 means CloudFormation waits for exactly one success signal before marking the resource as complete.
Signal the resource directly in UserData:
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
Notice the difference from WaitCondition – with CreationPolicy, cfn-signal points to the instance resource itself (WebServer), not a separate WaitCondition resource.
Step 6: Complete CloudFormation Template with Helper Scripts
Here is a full working template that combines all the helper scripts – cfn-init for configuration, cfn-signal with CreationPolicy for completion signaling, and cfn-hup for ongoing updates. This template deploys an Nginx web server on an Amazon Linux 2023 instance:
AWSTemplateFormatVersion: "2010-09-09"
Description: EC2 instance with CloudFormation helper scripts
Parameters:
InstanceType:
Type: String
Default: t3.micro
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: SSH key pair name
VpcId:
Type: AWS::EC2::VPC::Id
SubnetId:
Type: AWS::EC2::Subnet::Id
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and SSH
VpcId: !Ref VpcId
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
WebServer:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT10M
Metadata:
AWS::CloudFormation::Init:
configSets:
full_install:
- install_packages
- configure_app
- configure_cfn_hup
install_packages:
packages:
yum:
nginx: []
configure_app:
files:
/usr/share/nginx/html/index.html:
content: |
<h1>Deployed with CloudFormation</h1>
<p>This server was configured using cfn-init helper scripts.</p>
mode: "000644"
owner: root
group: root
services:
sysvinit:
nginx:
enabled: true
ensureRunning: true
configure_cfn_hup:
files:
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
interval=5
mode: "000400"
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.WebServer.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--configsets full_install \
--region ${AWS::Region}
runas=root
mode: "000400"
owner: root
group: root
services:
sysvinit:
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
ImageId: !Ref LatestAmiId
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref InstanceSecurityGroup
IamInstanceProfile: !Ref InstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource WebServer \
--configsets full_install \
--region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? \
--stack ${AWS::StackName} \
--resource WebServer \
--region ${AWS::Region}
InstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: CloudFormationDescribe
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- cloudformation:DescribeStackResource
- cloudformation:SignalResource
Resource: "*"
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref InstanceRole
Outputs:
PublicIP:
Description: Public IP of the web server
Value: !GetAtt WebServer.PublicIp
WebURL:
Description: URL of the web server
Value: !Sub http://${WebServer.PublicDnsName}
Deploy this template using the AWS CLI:
aws cloudformation create-stack \
--stack-name web-server-demo \
--template-body file://template.yaml \
--parameters \
ParameterKey=KeyName,ParameterValue=my-key \
ParameterKey=VpcId,ParameterValue=vpc-0123456789abcdef0 \
ParameterKey=SubnetId,ParameterValue=subnet-0123456789abcdef0 \
--capabilities CAPABILITY_IAM
Monitor the stack creation progress:
aws cloudformation describe-stack-events \
--stack-name web-server-demo \
--query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \
--output table
Step 7: Troubleshoot CloudFormation Helper Scripts
When cfn-init fails, the stack rolls back and you lose SSH access to the instance by default. The most effective troubleshooting approach is to disable rollback during development so you can inspect logs on the failed instance.
Create the stack with rollback disabled:
aws cloudformation create-stack \
--stack-name debug-stack \
--template-body file://template.yaml \
--disable-rollback \
--capabilities CAPABILITY_IAM
Once you SSH into the instance, the helper scripts write detailed logs to /var/log/. Check these files in order:
View the cfn-init log for package installation and file creation errors:
sudo cat /var/log/cfn-init.log
Check the cfn-init command output log for detailed error messages:
sudo cat /var/log/cfn-init-cmd.log
Review the cloud-init output to see if UserData executed at all:
sudo cat /var/log/cloud-init-output.log
If cfn-hup is not picking up changes, check its log:
sudo cat /var/log/cfn-hup.log
Common issues and fixes:
- cfn-init not found – the helper scripts are not installed or not symlinked to
/opt/aws/bin/. Verify the installation path in UserData - Timeout waiting for signal – cfn-signal never ran, usually because cfn-init failed and the script exited before reaching the signal command. Check
/var/log/cfn-init.logfor the root cause - Access denied errors in cfn-init – the EC2 instance IAM role is missing
cloudformation:DescribeStackResourceorcloudformation:SignalResourcepermissions - Package installation fails – the instance cannot reach package repositories. Check that the security group allows outbound traffic and that the subnet has internet access (NAT gateway or public subnet)
- cfn-hup not detecting changes – verify the
pathin the hooks config matches the exact resource path in your template. The path is case-sensitive
Step 8: CloudFormation Helper Scripts Reference
This table summarizes all four CloudFormation helper scripts, what they do, and when to use each one:
| Script | Purpose | When to Use |
|---|---|---|
| cfn-init | Reads AWS::CloudFormation::Init metadata to install packages, write files, create users, and manage services | Every stack that configures EC2 instances – this is the primary helper script |
| cfn-signal | Sends a success or failure signal to CloudFormation to indicate resource readiness | With CreationPolicy or WaitCondition to prevent premature CREATE_COMPLETE status |
| cfn-hup | Daemon that polls for metadata changes and runs hooks when updates are detected | Long-running instances that need to pick up template changes without replacement |
| cfn-get-metadata | Retrieves all or part of a resource’s metadata from the CloudFormation stack | Custom scripts that need to read stack metadata outside of cfn-init |
Key cfn-init flags for reference:
| Flag | Description |
|---|---|
--stack | Name or ID of the CloudFormation stack |
--resource | Logical name of the resource containing AWS::CloudFormation::Init metadata |
--region | AWS region where the stack was created |
--configsets | Comma-separated list of configSets to run (runs all configs if omitted) |
-v | Verbose mode – logs detailed output to /var/log/cfn-init.log |
Conclusion
CloudFormation helper scripts give you declarative instance configuration that integrates directly with stack lifecycle management. The combination of cfn-init for setup, cfn-signal with CreationPolicy for completion tracking, and cfn-hup for live updates covers the full lifecycle of an EC2 instance managed through CloudFormation.
For production deployments, combine helper scripts with infrastructure-as-code tools for multi-environment management, and use SSM Agent for ongoing instance management without SSH access. Store sensitive configuration values in AWS Systems Manager Parameter Store or Secrets Manager rather than hardcoding them in templates.