Automating EC2 Patching with AWS Systems Manager Patch Manager

I moved an Ubuntu EC2 instance off manual update checks and onto a repeatable patching workflow in AWS Systems Manager. First I made sure Systems Manager could manage the instance. Then I ran a scan with the AWS baseline and built my own Ubuntu baseline. Last, I scheduled it with a Maintenance Window.

It uses several Systems Manager features together:

Maintenance Window
        |
        v
Run Command task: AWS-RunPatchBaseline
        |
        v
Patch baseline + tagged managed nodes
        |
        v
Compliance results and optional S3 logs

I used the classic flow with a baseline, a patch group, and a Maintenance Window. AWS also has patch policies now. Compare the current options before you standardize on one for production.

Times in the console and logs are in UTC. The Maintenance Window schedule uses America/New_York.

Part 1: Prepare the managed instance

1. Launching the Ubuntu instance

I launched an Ubuntu Server 24.04 instance. The AWS image already had SSM Agent, so I didn’t need to install it.

Launching the Ubuntu EC2 instance used for patch testing

Launching the Ubuntu EC2 instance used for patch testing

How the agent is packaged depends on the OS and AMI. I still checked it on the server instead of assuming.

2. Creating the EC2 role

I opened IAM and created a role with EC2 as its trusted service.

Opening IAM to create the EC2 role

Opening IAM to create the EC2 role

I attached the AWS-managed policy:

AmazonSSMManagedInstanceCore
The EC2 role includes AmazonSSMManagedInstanceCore

The EC2 role includes AmazonSSMManagedInstanceCore

SSM Agent uses this instance profile. If you send output to S3 or logs to CloudWatch, add only the exact bucket, prefix, KMS, or log permissions you need.

3. Attaching the role

On the instance page I went to Actions → Security → Modify IAM role and attached the new profile.

Modifying the IAM role assigned to the EC2 instance

Modifying the IAM role assigned to the EC2 instance

4. Checking SSM Agent

On this Ubuntu image the agent runs as a Snap service. I checked that it was enabled and active:

snap services amazon-ssm-agent
sudo systemctl status snap.amazon-ssm-agent.amazon-ssm-agent.service
SSM Agent is active and running on the Ubuntu instance

SSM Agent is active and running on the Ubuntu instance

I also confirmed the instance showed up as a managed node. The agent needs outbound HTTPS to the Systems Manager endpoints. That can go through internet or NAT access, or through VPC interface endpoints.

Part 2: Running a manual scan

5. Opening Patch Manager

I opened AWS Systems Manager from the AWS console.

Opening AWS Systems Manager

Opening AWS Systems Manager

Under Node Tools, I selected Patch Manager.

Patch Manager in the Systems Manager navigation

Patch Manager in the Systems Manager navigation

On the Patch Manager page I clicked Patch now.

Starting an on-demand Patch Manager operation

Starting an on-demand Patch Manager operation

6. Scanning without installing

For the first run, I chose Scan and targeted only the test instance.

Configuring a scan-only operation for one target instance

Configuring a scan-only operation for one target instance

A scan checks compliance but doesn’t install anything. That made it a safe first test of connectivity, permissions, and baseline choice.

Since it’s Ubuntu, Patch Manager used the AWS Ubuntu baseline.

The AWS-managed default Ubuntu patch baseline

The AWS-managed default Ubuntu patch baseline

The execution completed successfully.

Successful Patch Manager scan execution

Successful Patch Manager scan execution

Success only meant the command ran. I still checked the compliance result to see what was missing.

Part 3: Building a custom Ubuntu baseline

7. Creating the baseline

In the Patch baselines tab I clicked Create patch baseline.

Creating a custom patch baseline

Creating a custom patch baseline

I named it and picked Ubuntu as the OS. I didn’t make it the account default.

Creating an Ubuntu-specific custom baseline

Creating an Ubuntu-specific custom baseline

8. Setting approval rules

I set which Ubuntu products, sections, and priorities get auto-approved. I also chose whether to include non-security updates and how matching patches show up in compliance.

Defining the Ubuntu patch approval rule

Defining the Ubuntu patch approval rule

This rule is for testing, not a production policy. In production I’d work out approval delays, rejections, exceptions, and severity reporting with the security and app owners. I’d also push patches through test and staging groups first.

9. Tagging the instance into a patch group

I added this tag to the managed instance:

Key:   Patch Group
Value: CustomPatchTest
Adding the CustomPatchTest patch-group tag

Adding the CustomPatchTest patch-group tag

The key and value have to match the baseline association exactly.

10. Linking the group to the baseline

I selected my baseline and opened Actions → Modify patch groups.

Opening the custom baseline’s patch-group configuration

Opening the custom baseline’s patch-group configuration

I added CustomPatchTest as the patch-group value.

Associating CustomPatchTest with the custom Ubuntu baseline

Associating CustomPatchTest with the custom Ubuntu baseline

The Patch groups tab then showed the group linked to the baseline.

The patch group registered to the custom baseline

The patch group registered to the custom baseline

11. Scanning with the custom baseline

I ran another scan against the patch group. It finished successfully on the tagged targets.

Successful scan using the custom patch group

Successful scan using the custom patch group

I sent the command output to an S3 prefix called PatchLinux.

Patch Manager stdout and stderr objects stored in S3

Patch Manager stdout and stderr objects stored in S3

The output showed the package manager activity, baseline ID, snapshot ID, patch group, and compliance result.

Patch scan details in the stored stdout log

Patch scan details in the stored stdout log

Patch logs can expose hostnames, package versions, and environment details. So I kept the bucket private and encrypted. I limited access and added a lifecycle policy.

Part 4: Scheduling it

12. Creating a Maintenance Window

I opened Maintenance Windows and clicked Create maintenance window.

Creating a Systems Manager Maintenance Window

Creating a Systems Manager Maintenance Window

I gave it a clear name and left Allow unregistered targets off.

Restricting the Maintenance Window to registered targets

Restricting the Maintenance Window to registered targets

13. Setting the schedule and limits

I set the schedule, time zone, duration, and cutoff.

Configuring the Maintenance Window schedule

Configuring the Maintenance Window schedule

I used a short daily schedule for testing. In production I’d use an approved change window with enough time for patching and reboots. I’d set a cutoff so tasks can’t start late. I’d also coordinate with load balancers, Auto Scaling, backups, and health checks.

14. Registering the target

On the Targets tab I clicked Register target.

Registering a target with the Maintenance Window

Registering a target with the Maintenance Window

I targeted instances with the same Patch Group=CustomPatchTest tag.

Targeting managed instances with the patch-group tag

Targeting managed instances with the patch-group tag

Tag targeting scales well. It also means adding that tag to a server now has real consequences.

15. Registering the Run Command task

Under Tasks I clicked Register Run command task.

Registering a Run Command task in the Maintenance Window

Registering a Run Command task in the Maintenance Window

I named it ScanTask and kept the invocation cutoff on.

Naming the scheduled patch task

Naming the scheduled patch task

I selected the AWS-owned AWS-RunPatchBaseline document.

Selecting the AWS-RunPatchBaseline command document

Selecting the AWS-RunPatchBaseline command document

I picked the registered target.

Assigning the registered target to the task

Assigning the registered target to the task

With only one instance, I set concurrency to one and stopped after one error.

Limiting task concurrency and the error threshold

Limiting task concurrency and the error threshold

The task parameters have to set the operation:

Operation = Scan

To install patches instead, use Operation = Install. Pick the reboot behavior on purpose and test in a non-production group first. A successful scan does not mean anything got installed.

The instance profile and the Maintenance Window task role are separate identities. I kept AmazonSSMManagedInstanceCore on the instance. If the console or API asks for a task role, give it its own least-privilege service role. I didn’t reuse the instance profile for that.

16. Checking the scheduled run

After the next window, the History tab showed a successful run.

Successful scheduled Maintenance Window execution

Successful scheduled Maintenance Window execution

I opened the details and checked the target count, command status, compliance result, and logs. For install runs I’d also test the app after any reboot. I’d alert on failed, timed out, or non-compliant nodes.

Result

The Ubuntu instance now runs on a repeatable patch workflow. SSM Agent and the EC2 role make it manageable. The custom baseline decides what gets approved. The patch group tag picks the policy. The Maintenance Window runs AWS-RunPatchBaseline on schedule.

Before scaling this up, I’d add separate dev, staging, and production patch groups. I’d stagger rollouts and add failure alarms. I’d check backup and rollback steps and report compliance in one place. And I’d make sure an automated install never patches the whole fleet at once.