Deploy a Health Monitor to EC2
Push live CPU, memory, and disk metrics to CloudWatch from EC2.
Introduction
30 Second Summary
Imagine you're on vacation and want to check if everything is fine at home. A security camera lets you see what's happening from your phone, but without one, you'd have to drive all the way back.
In this project, you will deploy a bash health monitor script to a live cloud server and automate it to run on its own. You'll push the server's metrics to Amazon CloudWatch so you can check server health from any browser, no SSH required.
What You'll Build
Picture opening a CloudWatch dashboard in your browser and seeing live CPU, memory, and disk graphs from a cloud server, updating automatically every 5 minutes while your laptop sits closed.
By the end of this project, you'll have:
- A health monitor script running on a live EC2 instance, collecting real CPU, memory, and disk data every 5 minutes via cron without any manual intervention.
- Custom CloudWatch metrics graphed in your browser, letting you check server health from anywhere without SSH-ing into the server.
- A repeatable, CLI-driven deployment workflow where you can launch, configure, deploy, monitor, and tear down every resource with a few commands.
- An optional Secret Mission to push your skills even further.
Are there any prerequisites?
This project builds on Part 1 of the System Health Monitor series, so you should be comfortable with bash scripting basics like reading /proc, awk, and pipes.
You'll also need an AWS account, as this project uses paid resources that cost approximately $0.02 to complete.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters for your cloud engineering skills.
Set Up AWS CLI and SSH on Windows
Your health monitor needs to run on a real cloud server, not just your local machine. To create and manage that server from your terminal, you need two tools: the AWS CLI for sending commands to AWS, and SSH for connecting directly to the server once it is running.
In this step, you will install the AWS CLI, connect it to your AWS account, and make sure SSH is ready to go on your Windows machine.
In this step, get ready to:
- Install and configure AWS CLI v2 on Windows.
- Verify that OpenSSH is available in PowerShell.
Check if AWS CLI is installed
The AWS CLI lets you create, configure, and tear down AWS resources directly from your terminal. Before installing anything, check whether it is already on your machine.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
- Check if the AWS CLI is installed by running this command:
aws --version
✔️ I see aws-cli/2.x.x
You already have AWS CLI v2 installed. Skip ahead to the next substep where you configure your credentials.
ⓧ I see aws-cli/1.x.x
You have an older version of the AWS CLI. Version 2 is required for this project. Upgrade by downloading and running the latest installer.
- Download the AWS CLI v2 installer from this link:
- https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the prompts to install.
- Close PowerShell completely and reopen it.
- Run aws --version again. You should now see aws-cli/2.x.x in the output.
ⓧ Command not found
The AWS CLI is not installed yet. Download and install version 2.
- Download the AWS CLI v2 installer from this link:
- https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the prompts to install.
- Close PowerShell completely and reopen it.
- Run aws --version again. You should now see aws-cli/2.x.x in the output.
Still not seeing aws-cli/2.x.x?
Make sure you closed and reopened PowerShell after the install. The new aws command is not available in sessions that were open during installation.
If you see a permission error during install, right-click the .msi file and choose Run as administrator.
Help me troubleshoot my AWS CLI installation on Windows.
Configure your AWS credentials
Now that the CLI is installed, you need to connect it to your AWS account. This tells the CLI which account to create resources in.
Where do I find my access keys?
You need an access key ID and secret access key from your AWS account. To create one, sign in to the AWS Management Console, click your username in the top right, and choose Security credentials.
Under Access keys, choose Create access key. Select Command Line Interface (CLI), then Next, then Create access key. Copy both values before closing the dialog.
- Start the configuration wizard by running:
aws configure
What will this command ask me?
This command opens an interactive prompt with four questions. Have your access key ready before you continue.
- AWS Access Key ID: paste your access key ID.
- AWS Secret Access Key: paste your secret access key.
- Default region name: type us-east-1 (or your preferred region).
- Default output format: type json.
- Enter each value when prompted, pressing Enter after each one.
Before you run the next command, what do you think it will show if your credentials are set up correctly?
- Verify that the CLI is connected to your account by running:
aws sts get-caller-identity
You should see a JSON response containing your AWS account ID, user ID, and ARN. It looks something like this:
{
"UserId": "AIDASAMPLEUSERID",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/YourUserName"
}
What does this output mean?
The Account field is your 12-digit AWS account ID. The Arn field is the full Amazon Resource Name of the IAM user whose credentials you configured. Seeing this response confirms the CLI can talk to AWS on your behalf.
Seeing an error instead of JSON?
If you see a credentials error, double-check that you pasted the access key ID and secret access key correctly during aws configure. Run aws configure again to re-enter them.
If you see a region error, make sure you entered a valid region like us-east-1 (not a display name like "US East").
Help me fix my AWS CLI credentials configuration.
Verify OpenSSH is available
You will use SSH to connect to your EC2 instance later. Windows 10 and 11 include an OpenSSH client as a built-in optional feature. Most machines have it enabled by default.
Before you run this next command, do you think SSH will be recognized or will you get an error?
- Test that SSH is available by running:
ssh
✔️ I see usage information
You see a block of text starting with usage: ssh and listing options like [-i identity_file]. OpenSSH is ready. You are all set for this step.
ⓧ Command not found
OpenSSH is not enabled on your machine. You can enable it through Windows Settings.
- Open Settings by pressing the Windows key and typing Optional Features.
- Click Optional Features in the search results.
- Look for OpenSSH Client in the installed features list. If it is already there, it should be working. Try closing and reopening PowerShell.
- If OpenSSH Client is not listed, click Add a feature (Windows 10) or View features (Windows 11).
- Search for OpenSSH Client, select it, and click Install.
- Close and reopen PowerShell, then run ssh again. You should now see the usage information.
Still not working after enabling?
If the feature will not install, try the PowerShell approach instead. Open PowerShell as administrator and run:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
After it completes, close and reopen PowerShell, then try ssh again.
Help me enable OpenSSH Client on my Windows machine.
Your tools are ready. Next up, you will use the AWS CLI to launch a real cloud server and SSH into it.
Deploy and Run Your Health Monitor
You're connected to a live cloud server. In Part 1 of this series, your health monitor script ran on your local machine and logged to a local file.
Now it's time to deploy that same monitoring concept to a real EC2 instance. You'll create the script directly on the server, run it, and see live system metrics from a machine running in the cloud.
In this step, get ready to:
- Create the health monitor script on EC2 using a cat heredoc.
- Run the script and see live CPU, memory, and disk metrics from a cloud server.
- Verify the log file was created with timestamped entries.
Create the health monitor script on EC2
Your EC2 instance is a fresh server with no files on it yet. You need to create the health monitor script directly on the instance.
Instead of opening a text editor, you'll use a cat heredoc to write the file contents in one go from your terminal. The script is split across two paste-able blocks to keep things manageable.
- In your EC2 SSH session, create the first part of the script (setup and CPU function) by running this command:
cat > health_monitor.sh << 'EOF'
#!/bin/bash
# System Health Monitor (local logging only, no CloudWatch yet)
LOG_DIR="/home/ec2-user/health-monitor-logs"
LOG_FILE="$LOG_DIR/health_$(date +%Y%m%d).log"
mkdir -p "$LOG_DIR"
get_cpu_usage() {
local stat1=$(head -1 /proc/stat)
sleep 1
local stat2=$(head -1 /proc/stat)
local idle1=$(echo "$stat1" | awk '{print $5}')
local total1=$(echo "$stat1" | awk '{printf "%d", $2+$3+$4+$5+$6+$7+$8}')
local idle2=$(echo "$stat2" | awk '{print $5}')
local total2=$(echo "$stat2" | awk '{printf "%d", $2+$3+$4+$5+$6+$7+$8}')
local total_d=$((total2 - total1))
local idle_d=$((idle2 - idle1))
if [ "$total_d" -eq 0 ]; then
echo 0
else
echo $(( (total_d - idle_d) * 100 / total_d ))
fi
}
EOF
What does this code do?
cat > health_monitor.sh << 'EOF' writes everything between the two EOF markers into a new file. The single quotes around EOF prevent the shell from expanding variables like $LOG_DIR during creation. They stay as literal variable references in the script.
The get_cpu_usage() function reads /proc/stat twice (one second apart) and compares the idle vs total CPU time. This gives you the current CPU utilization as a percentage rather than a cumulative average since boot.
- Confirm the file was created by running:
ls -la health_monitor.sh
You should see health_monitor.sh listed with its file size and timestamp.
- Append the remaining functions and logging logic to the script by running this command:
cat >> health_monitor.sh << 'EOF'
get_memory_usage() {
local total=$(grep MemTotal /proc/meminfo | awk '{print $2}')
local available=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
local used=$((total - available))
echo $((used * 100 / total))
}
get_disk_usage() {
df / | tail -1 | awk '{print $5}' | tr -d '%'
}
# Collect metrics
CPU_USAGE=$(get_cpu_usage)
MEM_USAGE=$(get_memory_usage)
DISK_USAGE=$(get_disk_usage)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Log locally
echo "[$TIMESTAMP] CPU: ${CPU_USAGE}% | Memory: ${MEM_USAGE}% | Disk: ${DISK_USAGE}%"
echo "[$TIMESTAMP] CPU: ${CPU_USAGE}% | Memory: ${MEM_USAGE}% | Disk: ${DISK_USAGE}%" >> "$LOG_FILE"
EOF
What does this second part do?
- cat >> appends to the existing file instead of overwriting it.
- get_memory_usage() reads /proc/meminfo and calculates the percentage of memory in use by comparing total vs available.
- get_disk_usage() runs df / to get the disk usage percentage for the root filesystem.
- The final lines call all three functions, print the results to the terminal, and append them to a dated log file.
- Verify the complete script looks correct by checking the last few lines:
tail -5 health_monitor.sh
You should see the two echo lines that print and log the metrics, plus the >> "$LOG_FILE" redirect that writes to the log file.
File looks wrong or empty?
- If tail shows content from the first part only, the second cat >> command may not have run. Paste it again and make sure you see EOF on its own line at the end.
- If the file is empty, you may have accidentally used > (overwrite) instead of >> (append) for the second command. Run both commands again starting from cat > (part 1).
Need more help? Ask: why does my health_monitor.sh file look incomplete or empty on EC2?
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file by confirming tail -5 health_monitor.sh shows the logging lines.
ⓧ I'd like to double check the full code
The full health_monitor.sh file is built from the two chunks above. Scroll up and confirm you have both pieces in order:
- Part 1 (cat >): shebang, log directory setup, and the get_cpu_usage() function.
- Part 2 (cat >>): get_memory_usage(), get_disk_usage(), metric collection, and the two echo lines for printing and logging.
If anything is missing, delete the file with rm health_monitor.sh and run both cat commands again from the start.
Make the script executable and run it
Linux requires explicit permission before it will execute a script file. You need to mark your script as executable before you can run it.
- Make the script executable by running:
chmod +x health_monitor.sh
Before you run the script, think about this: what do you expect the output to look like? What kind of CPU, memory, and disk numbers would a freshly launched server show?
- Run the health monitor by executing:
./health_monitor.sh
After about 1 second (the script pauses to measure CPU), you should see output like this:
[2026-08-21 14:30:05] CPU: 2% | Memory: 15% | Disk: 8%
Your exact numbers will differ, but the format matches. These are real metrics from your cloud server, not your local machine.
Why is CPU so low?
A freshly launched t3.micro instance with no workload typically shows CPU around 0-5%. Memory usage is also low because Amazon Linux 2023 is lightweight. These numbers prove the script is reading real system data, not placeholders.
Seeing a permission denied error?
- Make sure you ran chmod +x health_monitor.sh before executing the script. You can confirm with ls -la health_monitor.sh. The permissions should start with -rwx.
- If you see a syntax error instead, the heredoc may have been pasted incorrectly. Delete the file with rm health_monitor.sh and re-run both cat commands from the previous substep.
Still stuck? Ask: why is my health_monitor.sh script failing to run on EC2?
Check the log file
The script doesn't just print metrics to the terminal. It also appends them to a dated log file so you have a record of every reading.
- Check that the log directory was created by running:
ls -la /home/ec2-user/health-monitor-logs/
You should see a file named something like health_20260821.log (with today's date).
- Read the log file contents by running:
cat /home/ec2-user/health-monitor-logs/health_$(date +%Y%m%d).log
You should see the same timestamped line that printed to your terminal earlier. This confirms the script is both displaying and saving metrics.
Log file is missing or empty?
- If the health-monitor-logs directory does not exist, the script's mkdir -p line may have been pasted incorrectly. Run head -8 health_monitor.sh and confirm you see the LOG_DIR, LOG_FILE, and mkdir -p lines.
- If the directory exists but the file is empty, check that the echo line with >> "$LOG_FILE" is present at the end of your script. The >> redirect is what writes to the log.
Need help? Ask: why is my health monitor log file missing or empty on EC2?
Your health monitor is alive on a real cloud server. You can run it any time you SSH in and see exactly how the instance is doing. Next up, you'll automate it with cron so it runs every 5 minutes without you lifting a finger.
Automate with Cron and Hit the Wall
Your health monitor script is alive on a real cloud server, collecting live CPU, memory, and disk data. But right now, you have to SSH in and manually run the script every single time you want a reading. A production server needs to monitor itself continuously, even when nobody is logged in.
In this step, you'll set up a cron job so your script runs every 5 minutes without any manual intervention. Along the way, you'll also uncover a fundamental limitation of this approach.
In this step, get ready to:
- Set up a cron job to run your health monitor every 5 minutes.
- Verify that cron is collecting metrics automatically without you running the script.
- Discover the limitation of server-trapped monitoring data.
Install cron and schedule the health monitor
Cron is a Linux job scheduler that runs commands on a repeating schedule. You define a schedule (like "every 5 minutes") and cron handles the rest, running your script in the background whether you're logged in or not.
Why cron instead of a loop?
You could run your script in an infinite loop with a sleep between iterations. But if the script crashes, the loop dies and monitoring stops silently. Cron is a separate system service that triggers your script independently. If one run fails, cron still fires the next one on schedule.
Amazon Linux 2023 does not include cron by default. You need to install the cronie package first.
- In your SSH session on the EC2 instance, install cronie by running:
sudo dnf install cronie -y
- Start the cron daemon and enable it to run on boot by running:
sudo systemctl enable --now crond
- Verify the cron service is running by running:
sudo systemctl status crond
You should see active (running) in green text. Press q to exit the status view.
Service not running?
- If the install failed, make sure you included sudo at the start of the command.
- If the status shows inactive, try running sudo systemctl start crond separately.
- Help me troubleshoot the crond service on my EC2 instance
Now you can add your health monitor script to the cron schedule. The crontab -e command opens a text editor where you define which commands to run and when.
- Open the crontab editor by running:
crontab -e
What will you see?
You may see a message saying no crontab for ec2-user - using an empty one. This is normal. It means you have no existing scheduled jobs, so cron is starting with a blank file.
The file opens in the nano text editor. You will see an empty file or a cursor at the top. Type the cron entry shown below.
- Type this exact line into the editor:
*/5 * * * * /home/ec2-user/health_monitor.sh >> /home/ec2-user/health-monitor-logs/cron.log 2>&1
What does this cron entry mean?
- */5 * * * * is the schedule. The five fields represent minute, hour, day-of-month, month, and day-of-week. */5 in the minute field means "every 5 minutes."
- /home/ec2-user/health_monitor.sh is the full path to your script. Cron does not know your working directory, so the path must be absolute.
- >> /home/ec2-user/health-monitor-logs/cron.log 2>&1 captures both normal output and errors into cron.log. This helps you debug if a cron run fails silently.
- Press Ctrl+O to save the file.
- Press Enter to confirm the filename.
- Press Ctrl+X to exit the editor.
- Confirm the cron entry was saved by running:
crontab -l
You should see the line you just typed printed back to you. This confirms cron has registered your scheduled job.
Cron entry not showing up?
- If crontab -l shows nothing, you may not have saved the file in nano. Run crontab -e again and make sure you press Ctrl+O then Enter before pressing Ctrl+X.
- Double check the cron line starts with */5 (asterisk, forward slash, five). A typo like *5 or /5 will cause a parsing error.
- Help me fix my crontab entry on EC2
Verify automatic data collection
Your cron job fires every 5 minutes. You need to wait for at least one cycle to confirm that cron is running your script automatically.
While you wait
This is a good time to review what you've built so far. Your script reads CPU from /proc/stat, memory from /proc/meminfo, and disk from df /. Cron now triggers this automatically, writing results to a dated log file. Wait about 5-10 minutes before continuing.
Before you check the log, think about this: you ran the script manually once in the previous step, and cron has now been running for at least 5 minutes. How many entries do you expect to see in the log file?
- Check the dated log file for automatic entries by running:
cat /home/ec2-user/health-monitor-logs/health_$(date +%Y%m%d).log
You should see multiple timestamped entries. The first one is from your manual run in Step 3. The newer ones appeared automatically, triggered by cron, without you doing anything.
What if I also want to check cron's output?
The cron redirect also captures output in cron.log. You can check it with cat /home/ec2-user/health-monitor-logs/cron.log. This file captures both stdout and stderr from each cron run, which is useful for debugging if something goes wrong later.
No new entries in the log?
- Make sure at least 5 minutes have passed since you saved the crontab. Cron fires at the next 5-minute boundary (e.g. :00, :05, :10), not 5 minutes from the moment you saved.
- Check that the crond service is still running with sudo systemctl status crond.
- Verify the script path is correct by running ls -la /home/ec2-user/health_monitor.sh. The file must exist and be executable (-rwx permissions).
- Help me debug why my cron job is not running on EC2
Disconnect and discover the limitation
Your monitor is running automatically now. Cron fires every 5 minutes, the script collects metrics, and the log file keeps growing. But here is the real question: what happens when you are not connected to the server?
- Disconnect from your EC2 instance by running:
exit
You are back in PowerShell on your local machine. Your cron job is still running on the EC2 instance, collecting metrics every 5 minutes in the background. Now try to answer this question: is the server healthy right now?
You cannot tell. The monitoring data is sitting in a log file on the server, and the only way to read it is to SSH back in. You have automated the collection, but the data is trapped.
Why is this a problem?
Imagine you manage 10 servers. To check if any of them is having a problem, you would need to SSH into each one, read the log file, and mentally compare the numbers. At 3 AM when something is down, that is not practical.
Production monitoring needs a central place to view metrics from any browser, without touching SSH. That is exactly the problem CloudWatch solves.
Your monitor runs automatically. But the data is locked on the server. In the next step, you will break it free by pushing metrics to Amazon CloudWatch, where you can check server health from any browser without ever touching SSH.
Push Metrics to CloudWatch
Your cron job is humming along, collecting metrics every 5 minutes without you lifting a finger. But in the last step you discovered the fundamental problem: that data is trapped on the server.
Every time you want to check server health, you have to SSH back in and read log files. CloudWatch makes those metrics visible from any browser. That is how production monitoring actually works.
In this step, get ready to:
- Update the health monitor script to push metrics to CloudWatch using IMDSv2.
- Create an IAM role that gives your EC2 instance permission to call CloudWatch.
- Verify your custom metrics appear as graphs in the CloudWatch console.
Update the script with CloudWatch integration
Your script currently collects metrics and logs them to a file. You need to add two things: a way for the script to know which instance it is running on, and commands that push those metrics to CloudWatch.
- Connect to your EC2 instance by running this command in PowerShell:
ssh -i health-monitor-key.pem ec2-user@[[EC2_PUBLIC_IP="your EC2 public IP address"]]
Retrieve your public IP if needed
If you do not remember your instance's public IP, run this in a separate PowerShell window before connecting:
aws ec2 describe-instances `
--filters "Name=key-name,Values=health-monitor-key" "Name=instance-state-name,Values=running" `
--query "Reservations[0].Instances[0].PublicIpAddress" `
--output text
Once your prompt changes to [ec2-user@ip-... ~]$, you are on the EC2 instance.
- Open the health monitor script in nano by running:
nano health_monitor.sh
Nano not found?
If nano is not installed on your instance, install it by running sudo dnf install nano -y, then try opening the file again. Help me install a text editor on my EC2 instance.
- Find the first comment line at the top of the file that says:
# System Health Monitor (local logging only, no CloudWatch yet)
- Replace that line with these two comment lines:
# System Health Monitor with CloudWatch Integration
# Collects CPU, memory, and disk metrics, logs locally, and pushes to CloudWatch
- Now scroll down to the mkdir -p "$LOG_DIR" line.
- Place your cursor on a new blank line directly below it and add the following code:
# Get instance metadata using IMDSv2
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id)
REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/placement/region)
What is IMDSv2?
Every EC2 instance can ask a special internal address (169.254.169.254) for information about itself. This is called the Instance Metadata Service.
IMDSv2 is the secure version. It requires a session token before it answers any requests. That is why the script first requests a TOKEN, then uses that token to ask for the instance ID and region. Amazon Linux 2023 disables the older IMDSv1 by default.
- Now scroll all the way down to the bottom of the file.
- Find the last line that starts with echo (the one that appends to $LOG_FILE).
- Place your cursor on a new blank line below it and add the following code:
# Push metrics to CloudWatch
aws cloudwatch put-metric-data \
--namespace "SystemHealthMonitor" \
--metric-name "CPUUsage" \
--value "$CPU_USAGE" \
--unit "Percent" \
--dimensions "InstanceId=$INSTANCE_ID" \
--region "$REGION"
aws cloudwatch put-metric-data \
--namespace "SystemHealthMonitor" \
--metric-name "MemoryUsage" \
--value "$MEM_USAGE" \
--unit "Percent" \
--dimensions "InstanceId=$INSTANCE_ID" \
--region "$REGION"
aws cloudwatch put-metric-data \
--namespace "SystemHealthMonitor" \
--metric-name "DiskUsage" \
--value "$DISK_USAGE" \
--unit "Percent" \
--dimensions "InstanceId=$INSTANCE_ID" \
--region "$REGION"
echo "Metrics pushed to CloudWatch namespace: SystemHealthMonitor"
What do these commands do?
Each aws cloudwatch put-metric-data command sends one metric to CloudWatch. You are pushing three: CPUUsage, MemoryUsage, and DiskUsage.
The --namespace flag groups your metrics under SystemHealthMonitor so they do not get mixed up with AWS's built-in metrics. The --dimensions flag tags each data point with the instance ID, so you can tell which server the data came from.
- Save the file by pressing Ctrl+O, then Enter to confirm the filename.
- Exit nano by pressing Ctrl+X.
Before you run the script, think about this: the EC2 instance does not have any AWS credentials configured on it. No access key, no secret key. What do you think will happen when the script tries to call aws cloudwatch put-metric-data?
- Run the updated script to see what happens:
./health_monitor.sh
You see the local metrics line printed, but then the aws cloudwatch put-metric-data commands fail with a credentials or permissions error. That is expected. The EC2 instance has no way to authenticate with AWS yet.
Why did the script fail?
Your local Windows machine has AWS credentials because you ran aws configure in Step 1. But your EC2 instance has no credentials at all. It cannot call any AWS service.
You could copy your access keys onto the server, but that is a security risk. The production-safe way is to attach an IAM role to the instance. The role grants temporary credentials automatically, with no keys stored on disk.
Create an IAM role and attach it to the instance
You need to give your EC2 instance permission to call CloudWatch. You will create an IAM role on your local machine (where you have credentials) and attach it to the running instance.
- Disconnect from the EC2 instance by running:
exit
You are back in your local PowerShell prompt.
- Create the IAM role with a trust policy that allows EC2 to assume it by running:
aws iam create-role `
--role-name health-monitor-role `
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
You should see JSON output showing the role's ARN and creation date.
- Add an inline policy that grants the cloudwatch:PutMetricData permission by running:
aws iam put-role-policy `
--role-name health-monitor-role `
--policy-name CloudWatchPutMetrics `
--policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"cloudwatch:PutMetricData","Resource":"*"}]}'
What is an instance profile?
EC2 does not attach roles directly. It uses a container called an instance profile that wraps the role. When you create a role in the AWS console, the console creates the instance profile automatically. With the CLI, you create them separately.
- Create the instance profile and add the role to it by running these two commands:
aws iam create-instance-profile `
--instance-profile-name health-monitor-profile
aws iam add-role-to-instance-profile `
--instance-profile-name health-monitor-profile `
--role-name health-monitor-role
- Retrieve your instance ID by running:
$INSTANCE_ID = (aws ec2 describe-instances `
--filters "Name=key-name,Values=health-monitor-key" "Name=instance-state-name,Values=running" `
--query "Reservations[0].Instances[0].InstanceId" `
--output text)
Write-Output $INSTANCE_ID
You should see your instance ID printed, something like i-0abc123def456789.
- Attach the instance profile to your running EC2 instance by running:
aws ec2 associate-iam-instance-profile `
--instance-id $INSTANCE_ID `
--iam-instance-profile Name=health-monitor-profile
You should see JSON output with "State": "associating" confirming the profile is being attached.
Why create the role from your local machine?
Your EC2 instance has no AWS credentials yet. It cannot create its own IAM role. You create the role from your local machine (which has credentials from aws configure) and attach it remotely. Once attached, the instance gets temporary credentials automatically.
- Wait about 10 seconds for the instance profile to propagate, then move on to the next substep.
Seeing an error about the instance profile?
If associate-iam-instance-profile fails, check that the $INSTANCE_ID variable is not empty by running Write-Output $INSTANCE_ID. If it is empty, your instance might not be in the running state or the key name filter does not match.
Help me troubleshoot attaching an IAM instance profile to my EC2 instance.
Re-run the script and check CloudWatch
Your EC2 instance now has an IAM role that grants CloudWatch permissions. Time to see if the fix works.
- SSH back into your EC2 instance by running:
ssh -i health-monitor-key.pem ec2-user@[[EC2_PUBLIC_IP="your EC2 public IP address"]]
What do you expect to happen this time when the script tries to push metrics?
- Run the updated script:
./health_monitor.sh
This time you see the local metrics line AND the message Metrics pushed to CloudWatch namespace: SystemHealthMonitor with no errors. The IAM role gave your instance the credentials it needed.
Still seeing a credentials or permissions error?
The instance profile can take up to 30 seconds to propagate. Wait a bit and try running the script again.
If the error persists, verify the instance profile is attached by running this from a separate local PowerShell window:
aws ec2 describe-iam-instance-profile-associations
You should see health-monitor-profile listed with "State": "associated". If not, re-run the associate-iam-instance-profile command from the previous substep.
Help me troubleshoot why my EC2 script cannot push metrics to CloudWatch.
Now verify that the metrics actually arrived in CloudWatch.
- Open the AWS Management Console in your browser.
- Search for CloudWatch in the search bar and open the CloudWatch service.
- In the left sidebar, click All metrics.
- Under Custom namespaces, click SystemHealthMonitor.
- Click InstanceId to see the dimension.
- Select the checkboxes next to CPUUsage, MemoryUsage, and DiskUsage to graph them.
You should see your three metrics graphed. Each data point represents one run of the script. The cron job will add a new data point every 5 minutes automatically from now on.
Why does my graph only have one data point?
You have only run the updated script once so far. Each run pushes one data point per metric. As your cron job runs every 5 minutes, the graph fills in over time. Check back in 15-20 minutes to see a line forming.
Don't see SystemHealthMonitor in custom namespaces?
Custom metrics can take 2-3 minutes to appear in the console after the first push. Refresh the page and check again.
Also confirm you are looking at the same region in the console that your instance is running in. Check the region dropdown in the top-right corner of the console.
Help me find my custom CloudWatch metrics.
✔️ Awesome, I can see my metrics graphed
Your health monitor is now pushing live data to CloudWatch. The cron job keeps running in the background, so fresh data points appear every 5 minutes without you doing anything.
ⓧ I'd like to double check the full script
Scroll up and confirm your health_monitor.sh file includes each of these sections in order:
- The shebang line and two comment lines mentioning CloudWatch Integration.
- The LOG_DIR, LOG_FILE, and mkdir lines.
- The IMDSv2 block: TOKEN, INSTANCE_ID, and REGION retrieved via curl.
- The three functions: get_cpu_usage, get_memory_usage, and get_disk_usage.
- The metrics collection block (CPU_USAGE, MEM_USAGE, DISK_USAGE, TIMESTAMP).
- The two echo lines for local logging.
- The three aws cloudwatch put-metric-data commands for CPUUsage, MemoryUsage, and DiskUsage.
- The final echo line printing the namespace confirmation.
You have gone from a script that logs silently to a file, to a full monitoring pipeline that pushes live metrics to the cloud. Close your laptop, come back in an hour, and your CloudWatch graph will have a dozen new data points. No SSH required.
Secret mission
Set Up a CloudWatch Alarm
Your CloudWatch dashboard shows real-time metrics, but you have to open it and look. What if CloudWatch could watch the metrics for you and raise an alert when something goes wrong? In this secret mission, create a CloudWatch alarm that monitors disk usage and fires when it crosses a danger threshold.
Clean Up Your Resources
Your EC2 instance costs approximately $0.0104/hour and the public IPv4 address costs $0.005/hour. Left running, that adds up to roughly $11/month. If you are done with this project, delete everything to stop all charges immediately.
Nice Work!
Nice Work!
Nice work! You just deployed a production-style system health monitor to a live EC2 instance, automated it with cron, and made its metrics visible from anywhere through Amazon CloudWatch.
You've learned how to:
- Launched an EC2 instance from the CLI, configured SSH access, and deployed a bash health monitor that reads real CPU, memory, and disk metrics from a live cloud server.
- Automated the monitor with a cron job that runs every 5 minutes, then solved the problem of server-trapped data by pushing custom metrics to CloudWatch where you can check them from any browser.
- Used IAM roles and IMDSv2 to securely grant your EC2 instance permission to call AWS services without hardcoded credentials.
- Completed an optional Secret Mission to push your cloud monitoring skills further.
Ready to quiz yourself?