GuardDuty SSH Brute Force Finding: TARGET vs ACTOR Fix
An UnauthorizedAccess:EC2/SSHBruteForce finding from Amazon GuardDuty (AWS's threat-detection service) means someone, usually an automated bot, has been hammering port 22 on your EC2 server (your rented virtual computer in AWS) trying to guess SSH passwords. Before you touch anything, open the finding and read one field: Resource role. If it says TARGET, your server is being attacked, and the fix is to stop letting the whole internet reach port 22. If it says ACTOR, your server is doing the attacking, and that one is the emergency. Here is the surprise: one finding name covers both, and the scary-sounding version is the low-severity one.
Jake runs a small phone shop. His repair-ticket tracker lives on one modest Linux server in AWS. At 11:47 p.m., the hour when customers remember their screen has been cracked for a week, an email arrived with the words "brute force" in the subject. He opened the console with a racing heart and found himself hovering over the Terminate button, with thirty phones' worth of repair tickets and a busy Saturday queue on that disk.
Ethan: "Hands off the button, Jake. That alert has two possible meanings, and only one of them is an emergency. Reading it takes two minutes. Deleting the wrong server takes your whole weekend."
Read the finding first: the fields that decide everything
A GuardDuty finding is simply GuardDuty's word for one alert: a notification that something looks like a potential security issue. Every finding has a details panel, and for this one a handful of fields do almost all the work. Open the GuardDuty console, choose Findings, select the brute force finding, and read these before you change a single setting.
| Field | What you will see | What it tells you |
|---|---|---|
| Resource role | TARGET or ACTOR | Whether your instance was attacked (TARGET) or carried out the attack (ACTOR) |
| Severity | Low or High | Low when the attack is aimed at your instance, High when your instance is performing it |
| Connection direction (under Action) | INBOUND, OUTBOUND or UNKNOWN | INBOUND means a remote host started a connection to a port on your instance. OUTBOUND means your instance started it. |
| Actor or Target section | IP address, location, organization, port | An Actor section appears when your instance is the TARGET. A Target section appears when your instance is the ACTOR. |
| Count, Created at, Updated at | A number and two timestamps | If Created at and Updated at differ, the activity has happened more than once and is ongoing |
| Blocked (under Action) | True or false | Whether the targeted port is blocked |
| Instance details | Instance ID, security group name and ID, tags, image ID, launch time | Which server, which firewall group, and whether it is one you tagged as a bastion |
| Account ID and Region | An AWS account number and a Region name | The account where the activity took place and the Region the finding was generated in. This is where you go to fix it. |
Two small traps here. First, timestamps in the console appear in your local time zone, while JSON exports and CLI output use UTC. If you are matching the finding against server logs, convert one to the other before deciding that the times don't line up. Second, instance details can be missing if the instance was already stopped or terminated, so write down the instance ID, security group ID and the attacker's IP address before you change anything. Once the instance is gone, those details may not come back.
The finding's title also reads like a sentence. A typical title looks like "an IP address is performing RDP brute force attacks against i-99999999" (or the SSH version for Linux). The title names the affected instance, the direction and the address in one line, which is often all you need to answer the diagnostic question in a few sections.
What an SSH brute force finding actually means
SSH (Secure Shell) is the standard way to log in to a Linux server from a distance and type commands into it. It listens on port 22. A port is like a numbered door on the server, and each kind of service knocks on its own door. A brute force attack is the crudest kind of break-in: try passwords over and over until one works. Nobody sits at a keyboard doing this. Bots do it, all day, against any address that answers on port 22.
AWS's description of this finding says an EC2 instance in your environment was involved in a brute force attack aimed at obtaining passwords to SSH services on Linux-based systems, and that this can indicate unauthorized access to your AWS resources. The data source is VPC flow logs. A flow log is a record of network connections going to and from your instance: who talked to whom, on which port. That has a consequence worth understanding. GuardDuty is watching connection patterns, and the finding is not a login record.
Think of it like the door-to-door salespeople who work your whole neighborhood, ringing every bell on the street. The finding says a lot of doorbell ringing happened at your house. It doesn't say whether anyone was let in. The instance's login records answer that, and the login records section below shows how to approach them.
These attacks typically come from bots looking for instances to break into. It also names the usual root cause: a security group rule of type SSH that allows connections from all sources on port 22. A security group is the virtual firewall attached to your instance, a list of rules saying who may connect on which port. A rule whose source is 0.0.0.0/0 means "any IPv4 address on the internet." Its IPv6 twin is ::/0.
🙋♂️ Jake's Reality Check
"The category says UnauthorizedAccess. Doesn't that mean they already got in?"
The straight answer. Not by itself. The name is a category label, and the activity can indicate unauthorized access. Its severity guide describes the Low level as attempted suspicious activity that did not compromise your environment. But a finding built from connection patterns can't promise that nobody logged in, so you confirm it yourself in the login records. Treat "attempts" as the working assumption and "success" as the thing you rule out.
One more piece of context prevents a lot of confusion. The finding is only generated by monitoring traffic on port 22. That detail matters later, in the section on why moving SSH to another port is not a fix.
The one-question diagnostic: TARGET or ACTOR?
Every GuardDuty finding about an instance carries a Resource role. TARGET means your resource was the target of suspicious activity. ACTOR means your resource was the actor carrying it out. For this finding type, that single word flips the whole meaning.
AWS's severity notes for the finding say it is Low if a brute force attack is aimed at one of your instances, and High if your instance is being used to perform the brute force attack. That is why one finding name can show up as a shrug in one account and a fire alarm in another.
| TARGET | ACTOR | |
|---|---|---|
| Default severity | Low | High |
| What is happening | Someone is guessing SSH passwords against your instance | Your instance is guessing passwords against someone else's |
| Section of the finding to read | Actor (who is knocking) | Target (who your instance is knocking on) |
| AWS's remediation advice | Secure the SSH port to only trusted IPs through security groups, network ACLs or firewalls | Unless the instance has a legitimate reason to contact that address, assume it is compromised and follow the compromised-instance steps |
| Urgency | Fix today, and rule out a successful login | Act now, before anything else |
| Go to | Route A | Route B |
You can also read the direction from the command line. Two queries against a finding tell you what you need. Replace the detector ID and finding ID with your own; both appear in the finding's JSON.
aws guardduty get-findings --detector-id your-detector-id --finding-ids your-findings-id --query 'Findings[].Service.Action.NetworkConnectionAction.ConnectionDirection' aws guardduty get-findings --detector-id your-detector-id --finding-ids your-findings-id --query 'Findings[].Service.Action.NetworkConnectionAction.RemoteIpDetails.IpAddressV4'
The first returns INBOUND or OUTBOUND. The second returns the remote IPv4 address. A detector is just GuardDuty's per-Region on-switch for your account, and its ID is how the CLI (command line interface) knows which GuardDuty you mean.
Ethan: "Direction first, always. Hardening a firewall against an attack that turns out to be your own server misbehaving is a very efficient way to lose an evening. Read the role, then pick your route."
Route A: your instance is the TARGET
This is the common case, and it is Jake's case. The good news is that the fix is usually one firewall rule. The steps below are arranged from cheapest and least disruptive to most involved.
- Write down the evidence. Copy the finding ID, instance ID, security group ID, the Actor IP address, the Count, and the Created at and Updated at times. If you later terminate or replace the instance, instance details may be missing from findings for terminated instances.
- Confirm the direction is INBOUND. Use the console or the CLI query above. INBOUND with a TARGET role is the classic "bots knocking on my port 22" picture.
- Look at the security group's SSH rule. In the EC2 console, open your instance, find the security groups attached to it, and read the inbound rules. A rule of type SSH, port 22, with a source of
0.0.0.0/0or::/0is the open door. AWS's guide on authorizing SSH traffic says opening SSH to all addresses is acceptable for a short time in a test environment but unsafe for production. - Check the instance's login records for a successful login, using the approach in the next section. Do this before you decide the story is over, and ideally before you change anything on the instance itself.
- Replace the open rule with a narrow one. AWS's instructions say to set the rule's source to your computer's public IP address in CIDR notation, or the whole range if your company allocates addresses from a block. CIDR notation is a compact way to write an address or a range of addresses. A single address looks like
203.0.113.25/32(an example address, not a real one). Add the narrow rule and confirm a second connection works before you remove the wide one. - If you find a successful login you can't explain, stop and switch to Route B. A brute force finding plus an unknown successful login is a different problem from a brute force finding on its own.
- Watch the finding afterward. An aggregated finding is updated with the latest occurrence of the activity, so the Updated at time is a useful thing to watch after your change.
⚠️ What this can break
You can lock yourself out. AWS's guide says that if you connect from behind an ISP without a static IP address, you need to work out the range of addresses your client computers use. If your home or shop IP changes tomorrow, a rule tied to today's address stops matching. Set up an alternative way in (see Session Manager) before you tighten the rule, not after.
Security groups are shared. When you add or remove rules, the change is applied automatically to every instance the group is assigned to. Check which instances use that group before you edit it.
Two more notes on Route A. AWS's finding page says that for a TARGET-role finding, the remediation is securing the SSH port to only trusted IPs "through Security Groups, ACLs, or firewalls." A network ACL is a second, subnet-level filter, and an on-host firewall is one running inside the server. The security group is normally the simplest place to start. And what about the password guessing itself? AWS frames the attack as password guessing, and whether your instance accepts passwords at all is decided inside its SSH server configuration, which your Linux distribution documents. That is a good thing to review while you are in there, but it is not a substitute for closing the door.
Ethan: "Jake, you're going to want to delete the whole rule and start over. Don't. Add the narrow rule, keep one session open, and test from a second window before you remove the wide one. Boring beats clever here."
Reading the login records: did anyone actually get in?
The finding tells you someone knocked. The instance's own records tell you whether the door opened. This is the step most quick-fix guides skip, and it is the one that separates "annoying" from "incident." Here is how to approach it without guessing at formats that vary from one Linux distribution to another.
- Get your time window from the finding. Created at is when the activity first appeared, and Updated at is the most recent occurrence. Remember the time-zone rule from earlier: the console shows your local time, and JSON or CLI output shows UTC.
- Know where the records live. On an Amazon Linux AMI, the SSH login attempts are in
/var/log/secure, and those logs contain all the authentication attempts to connect to the instance. Other distributions may keep them somewhere else, so check your distribution's documentation. AWS also suggests reviewing the Linux logs in Amazon CloudWatch if you have set up the CloudWatch Logs agent. - Search by time and by success, not only by IP. This is the subtle part. When the same finding is aggregated, it is updated with the latest source and older information is replaced. So the Actor IP you see today may not be the address that was knocking three days ago. If you search only for that one address, you can miss an earlier one.
- Look for what should not be there. Any successful login from an address you don't recognize, and any successful login at a time nobody on your team was working, deserves a closer look. What a success and a failure look like in the file is defined by your distribution's SSH server, so use its documentation to learn the wording.
There are three honest outcomes. If the records cover the whole window and show no successful login you can't explain, you can reasonably treat this as a Route A exposure and fix the door. If you find a successful login you can't explain, move to Route B. And if the records don't cover the window at all, because they weren't kept or were never sent anywhere, you can't confirm either way. In that case, lean toward the replacement option in Route B rather than away from it. That's my opinion, not an AWS rule, but "I can't prove it's clean" is a weak place to run a business server from.
🕐 Where individual attempts are recorded
- GuardDuty keeps one aggregated finding per issue, not a line for every attempt.
- Complete information about individual attempts is still available in your CloudTrail logs or VPC Flow Logs, and GuardDuty's own copy of flow-log data isn't made available to you.
- What that means for you: if you want a searchable history, turn on your own VPC Flow Logs and keep the instance's login records somewhere they will survive a rebuild.
Use AWS Config to find every open SSH door
GuardDuty told you which instance got knocked on. It didn't promise that this is the only unlocked door in your account. That's the job of AWS Config, a service that records how your resources are configured and checks them against rules. A managed rule is a ready-made check written by AWS, so you don't have to author anything. GuardDuty says "someone is knocking." Config says "these are the doors that were left unlocked."
The rule to add: restricted-ssh
Add the managed rule named restricted-ssh and then read the list of non-compliant security groups. One naming quirk is worth knowing: for this rule, the rule identifier (INCOMING_SSH_DISABLED) and the rule name (restricted-ssh) are different. Search for either. Here is what the rule does:
- It evaluates security groups (the resource type is
AWS::EC2::SecurityGroup). - A security group is COMPLIANT if the incoming SSH addresses are restricted, meaning the CIDR is something other than
0.0.0.0/0or::/0. Otherwise it is NON_COMPLIANT. - Its trigger type is "configuration changes and periodic," so it isn't a one-time check.
- It takes no parameters.
From the command line, the AWS CLI reference shows how to list the resources that fail a rule. For this rule:
aws configservice get-compliance-details-by-config-rule --config-rule-name restricted-ssh --compliance-types NON_COMPLIANT
Each result names a security group ID. Those are the groups to fix, in the same way as in Route A. Wait for the rule to move from "Evaluating" to "noncompliant resource(s)" and then open the rule to see the list. The console labels have shifted over the years, so if AWS Config asks you to finish some one-time setup first, follow its prompts and come back to the rule.
The rule that covers every other port: vpc-sg-open-only-to-authorized-ports
This second managed rule is broader. It checks security groups that allow unrestricted incoming traffic (0.0.0.0/0 or ::/0) and reports NON_COMPLIANT if they open a TCP or UDP port that isn't in your approved list. You supply that list with the optional authorizedTcpPorts and authorizedUdpPorts parameters, for example 443,1020-1025. If a security group has neither of those two "open to the world" sources, the rule returns NOT_APPLICABLE, which means "nothing to judge here," not "this is fine." Use it when you want to answer a bigger question than SSH: which of my groups are open to everyone, on which ports, and did I mean that?
✅ Why pair the two tools
A GuardDuty finding is a snapshot of one instance on one bad night. An AWS Config rule watches the configuration over time. Fix the instance the finding named, then let the rule tell you whether the same mistake exists on the next five servers. One is the alarm, the other is the inspection.
There's an edge case that most guides skip. EC2 findings that use VPC flow logs as their data source don't support IPv6 traffic. The restricted-ssh rule explicitly treats ::/0 as an unrestricted source. So a security group that leaves SSH open to the IPv6 internet is something the Config rule can flag, even where the GuardDuty finding is not built to see IPv6 traffic. If you have IPv6 enabled anywhere, don't let a quiet GuardDuty dashboard reassure you.
Route B: your instance is the ACTOR
If the Resource role is ACTOR and the severity is High, your instance is the one sending the brute force traffic. AWS's severity guide says a High finding indicates the resource is compromised and actively being used for unauthorized purposes, and recommends treating it as a priority: clean up or terminate the instance, or rotate the IAM credentials. The finding page itself says that unless the instance has a legitimate reason to be contacting the Target IP address, you should assume it is compromised.
The word "compromised" is a hard one to read, so a plain translation: someone other than you is probably running software on your server. AWS's step-by-step page, Remediating a potentially compromised Amazon EC2 instance, is the authority here, and the order below follows it.
- Record the details. Instance ID, security groups, the Target address, the times, and what the instance was used for. Do this before you change anything, because terminated instances can lose details in findings.
- Identify the instance and investigate it for malware. You can use the on-demand malware scan in GuardDuty, or look at partner products in AWS Marketplace.
- Isolate it. Create a dedicated Isolation security group that only allows inbound and outbound access from specific IP addresses, with no rule that allows traffic for
0.0.0.0/0 (0-65535). Associate it with the instance and remove every other security group association. - Deal with the connections that are already open. See the warning box below, because this is the step people miss.
- Find and stop the source of the activity. This may require closing open ports, changing access policies, and upgrading applications to correct vulnerabilities.
- If you can't identify and stop it, replace the instance. I recommend terminating the compromised EC2 instance and launching a new one as needed.
- Get help. If you're stuck, ask on AWS re:Post, or open a technical support case if you have premium support. It also links its Security Incident Response Technical Guide.
⚠️ Isolation does not cut connections that are already open
AWS's note on the isolation step says existing tracked connections won't be terminated when you change security groups. Only future traffic is blocked by the new group. To block traffic from suspicious existing connections, AWS points to enforcing network ACLs based on network indicators of compromise, described in its Incident Response Playbook. In plain terms: changing the security group is necessary, but it is not proof the attacker is gone.
If the instance had an IAM role attached (permissions the server can use to call AWS services), think about what that role could reach. For a different finding, the metadata DNS rebind one, the advice is to revoke the session associated with the instance. If you suspect someone had control of the machine, it's a reasonable extra step to look up. Your account's real exposure is whatever that role was allowed to do.
Ethan: "When it's ACTOR, resist the urge to reboot and hope. A reboot changes the story without ending it. Isolate first, then decide whether this server is worth saving or whether a clean replacement is cheaper than trusting it again. Most of the time, the replacement is cheaper."
Why you may never have received an email for this finding
Plenty of people find this finding by accident, while browsing the console, and wonder why nothing warned them. The answer is in how GuardDuty notifications are usually wired up. GuardDuty publishes findings as events to Amazon EventBridge, an event bus that routes events to targets such as an Amazon SNS topic (which can send email or chat messages) or an AWS Lambda function. You create an EventBridge rule that says which events to forward.
Here is the catch. The example rule pattern most people copy creates an alert for medium, high and critical findings, and it lists severity values from 4 upward. AWS defines the Low range as 1.0 to 3.9. This finding, when your instance is the TARGET, is Low. So if you followed that example, a TARGET-role brute force finding would sit in the console without sending you anything, while an ACTOR-role finding (High) would match and reach you. Neither behavior is a bug. The design assumes attempted intrusions are background noise and compromised servers are what wake people up.
Jake: "So my missing email was a feature?"
Ethan: "A feature, yes. Whether it's the feature you want is your call. If you'd rather hear about SSH probing even at Low, write a rule that matches this finding type on purpose."
You can build a rule from any property in the finding's JSON. Its own examples filter on severity, and its email template reads the finding type from detail.type. Following that structure, a pattern for this one finding type would look like this. Treat it as a starting point to adapt in the EventBridge console, not as AWS-printed text:
{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"],
"detail": {
"type": ["UnauthorizedAccess:EC2/SSHBruteForce"]
}
}
How often the alerts come
- A brand-new finding: GuardDuty sends its notification in near real-time, and you can't change that.
- More activity on an existing finding: subsequent occurrences within a 6-hour interval are combined into one event by default. An administrator account can change that to 15 minutes or 1 hour. A member account can't change it for itself.
- Findings you archive by hand still send their notifications. Findings archived by a suppression rule do not. That difference matters, and it is covered in the next section.
- Retention: GuardDuty stores findings for 90 days. AWS supports exporting findings to Amazon S3 if you want to keep them longer.
If your alert setup is new, one more suggestion. Send to a place someone will actually see it. A quiet inbox that nobody watches makes the whole exercise decorative. Email, Slack and Amazon Chime all work as routes via an SNS topic.
False positives: bastions, trusted IPs, and suppression rules
A bastion host (also called a jump box) is a hardened server you deliberately expose so that you can SSH into it and then hop to the private servers behind it. By design, it faces the internet on port 22, which means it collects brute force traffic all day. If the target is a bastion host, the finding may represent expected behavior for your environment. In that case I recommend a suppression rule, a saved filter that automatically archives matching findings.
The rule AWS describes uses two filter criteria. The first is Finding type equal to UnauthorizedAccess:EC2/SSHBruteForce. The second identifies the bastion, either by Instance image ID or by Tag value. AWS's example uses an instance tag value of devops. Because the second criterion is what keeps the rule narrow, don't skip it.
What suppression does, and what it hides
- New matching findings are automatically archived. GuardDuty still generates them, and the archived findings are stored in GuardDuty for 90 days, viewable by selecting Archived in the findings table.
- Suppressed findings are not sent to AWS Security Hub CSPM, Amazon S3, Amazon Detective or Amazon EventBridge. If your alerting or ticketing runs off those, a suppressed finding never reaches them.
- Archived findings are not used by Extended Threat Detection when it correlates events into attack sequences, so keep suppression rules focused on specific behaviors.
- In a multi-account setup, only the GuardDuty administrator can create suppression rules.
- My advice is to build suppression rules reactively, only for findings where you have repeatedly identified false positives in your environment.
Trusted IP lists: the blunter tool
Trusted sources are entries you trust for secure communication with your environment, and GuardDuty does not generate findings for entries listed there. Entity lists (which can hold IP addresses, domains, or both) are the recommended approach, and legacy IP lists remain supported. You can have one trusted entity list and one trusted IP address list per account per Region. The list must be uploaded in the same Region as the findings, must be activated, and must be reactivated after you change it. It also says that adding a domain name, a private IP address, or an IPv6 address to a trusted IP list doesn't stop findings.
Which one to use? A suppression rule keeps a complete history, because GuardDuty still generates the finding and marks it archived. A trusted list stops the finding from being generated for that source at all. If your own office address is on a trusted list, a brute force attempt from that address will never appear. That is a fine trade for an address you truly control, and a bad trade for a whole ISP range. For a bastion, the narrow suppression rule is the safer default.
⚠️ The suppression trap
Do not create a suppression rule for this finding type across your whole account just to quiet an inbox. If a non-bastion instance later becomes the ACTOR in a High-severity version, a broad rule would archive it and keep it out of EventBridge and Security Hub alerts. Add the second criterion, the tag or image ID, every time.
Why the Count keeps rising, and what aggregation hides
Many people expect a new email for every attack. That isn't how it works. GuardDuty updates existing findings when it detects new activity for the same security issue, using this exact finding as its example. Multiple access attempts against your instance are aggregated into the same finding ID, and the Count goes up. AWS's reasoning is that the finding represents one security issue, that the SSH port on the instance isn't properly secured, and not each individual attempt.
Three consequences follow from that page:
- The IP address you see is the latest one. If your instance is targeted by a new actor, the finding is updated with the most recent source and the older information is replaced. If you copied the Actor IP yesterday, it may not match the one in front of you today.
- A new instance creates a new finding. SSH activity aimed at a different instance in your environment gets a unique finding ID.
- The full record of individual attempts lives elsewhere. Complete information about each attempt is still available in your CloudTrail logs or VPC Flow Logs.
That last point deserves a plain explanation, because it surprises people. GuardDuty reads its own independent copy of the flow-log data, and you don't need to create or configure VPC Flow Logs for GuardDuty to work. It also says GuardDuty extracts fields for profiling and then discards the logs, and that it doesn't make them accessible to you. So if you want a searchable history of who connected to port 22 last Tuesday, you have to switch on VPC Flow Logs yourself. GuardDuty's independence works in your favor for detection and against you for investigation.
🕐 Same finding, different labels
- GuardDuty's own Low label is not always what you see downstream.
- In the sample AWS publishes on its Security Hub integration page, a GuardDuty SSH brute force finding with a GuardDuty severity of 2 appears with a label of MEDIUM in Security Hub.
- What that means for you: when you compare severities across tools, compare like for like, and open the original finding before you decide how worried to be.
The port 22 blind spot: moving SSH is not a fix
Somewhere in every forum thread about this finding, somebody suggests changing the SSH port. It is tempting, because the alerts stop. Here is why they stop, and why that should worry you rather than reassure you. This finding is generated only through monitoring traffic on port 22, and if your SSH services are configured to use other ports, the finding is not generated.
Read that carefully. Moving SSH to another port doesn't fix the exposure. It removes the smoke detector while leaving the same door open. A silent dashboard now means "GuardDuty isn't looking at that port for this finding," not "nobody is trying."
🙋♂️ Jake's Reality Check
"So I can't just move SSH to some random port and call it done?"
The straight answer. You can, but it changes what you can see, not who can reach you. The real fix is controlling who is allowed to connect at all, with a narrow security group rule, or by not exposing SSH in the first place. That is the next section.
The same logic applies to the second Config rule from earlier. Because vpc-sg-open-only-to-authorized-ports looks at any TCP or UDP port open to the world, it can catch an SSH service you moved to an unusual port, because it flags open ports that aren't on your approved list. For a nonstandard SSH port, that is the rule that keeps you honest.
Close the door instead: Session Manager
The best practice is to allow SSH only from specific sources you own, such as bastion hosts. There's a step beyond that. AWS Systems Manager Session Manager lets you open a shell on your server without any inbound port at all. It gives you secure node management without the need to open inbound ports, maintain bastion hosts, or manage SSH keys. Access is controlled through IAM policies (AWS's permission system) and you can get logs with details of who accessed which node.
The security argument is direct: leaving inbound SSH ports and remote PowerShell ports open on your managed nodes greatly increases the risk of entities running unauthorized or malicious commands, and Session Manager lets you close those ports. If your team still needs ordinary SSH tooling, you can run SSH connections through Session Manager using the AWS CLI, over secure WebSocket connections, so that no inbound port has to be opened on the instance. "Managed node" here means the instance is registered with Systems Manager, which involves the SSM Agent and the right permissions. AWS's setup pages walk through it, and you should complete that before you delete your SSH rule.
✅ Why this is the one to use
If nothing listens for the public on port 22, there's nothing for a bot to guess against. It also removes the lockout problem from Route A, because your way in no longer depends on your home IP address staying put. Set it up, confirm you can get a session, and only then remove the SSH rule from the security group.
Ethan: "The best SSH security group rule is the one that doesn't exist. I keep a bastion only when a team genuinely needs plain SSH tooling. For a shop like Jake's, one person managing one server, Session Manager is simpler and there's less to babysit."
A fair warning: Session Manager needs a bit of setup, and it doesn't rescue an instance that already shows signs of compromise. If you're on Route B, isolate and investigate first. Session Manager is a way to run the replacement, not a shortcut past the investigation.
Multiple accounts and Regions: who fixes what
Jake has one account, which keeps his life simple. If you work at a company with several AWS accounts, the alert may reach you in a different place from the server it is about. GuardDuty supports a delegated administrator account, a central account that sees the findings from the other member accounts. The practical effects for this finding are worth spelling out.
- Read the Account ID first. A finding in the administrator's console may describe an instance in a member account. You can identify the originating member account from the
accountIdfield of the finding's JSON. The person who can fix the security group might be in a different team. - Notifications follow the administrator. EventBridge rules in the administrator account trigger on applicable findings from member accounts, and you can build a rule for specific member accounts by matching on
accountId. - Some settings are the administrator's alone. Only the administrator can create suppression rules, and only the administrator can change the EventBridge notification frequency for subsequent occurrences. Changing it applies to all member accounts.
- Regions are separate. Every finding shows the Region it was generated in. AWS also recommends enabling GuardDuty in all Regions available to your account, even ones where you don't run anything, because attackers can try to create resources in Regions where you have limited presence.
When you hand the fix to someone else, hand them the essentials: account ID, Region, instance ID, security group ID, and the direction. That is the whole message. Everything else can follow.
Sibling findings you will meet next
Once you have opened one of these findings, the neighbors start to look familiar. They use the same TARGET and ACTOR logic in several cases, and mixing them up leads to the wrong response. All of the details below come from AWS's EC2 finding types page.
| Finding | Default severity | What it means |
|---|---|---|
UnauthorizedAccess:EC2/RDPBruteForce |
Low if targeted, High if your instance is the actor | The Windows twin: brute force aimed at RDP passwords. If TARGET, secure the RDP port to trusted IPs. |
Impact:EC2/WinRMBruteForce |
Low if targeted, High if your instance is the actor | Your instance is performing an outbound Windows Remote Management brute force attack. If unexpected, it may be compromised. |
Recon:EC2/PortProbeUnprotectedPort |
Low (High if the probed port is used by Elasticsearch, 9200 or 9300) | A port that no security group, ACL or on-host firewall blocks is being probed by a known malicious host. Not generated for ports 80 and 443. |
Recon:EC2/Portscan |
Medium | Your instance is performing outbound port scans. Can be a false positive if you run vulnerability assessment tools. |
Impact:EC2/PortSweep |
High | Your instance is probing a port on a large number of publicly routable IP addresses. |
The one most likely to show up alongside the SSH finding is Recon:EC2/PortProbeUnprotectedPort. The advice for it echoes this post: if the unprotected port is 22 or 3389, you can still limit exposure by allowing access only from the IP addresses in your corporate network. The pair tells the story of an instance whose firewall is too permissive. One finding says the door was probed, the other says the door was tried.
Automation, sample findings, and practice runs
Auto-remediation, with a caution
Once you are comfortable fixing this by hand, you may want AWS Config to fix it for you. There is a Systems Manager automation runbook named AWS-DisablePublicAccessForSecurityGroup for turning off SSH and RDP exposure. The catch: the runbook is limited to the default SSH (22) and RDP (3389) ports open to all IP addresses (0.0.0.0/0), and to an IPv4 address that uses the IpAddressToBlock parameter. If the inbound rules don't match the patterns the runbook expects, auto-remediation fails with an InvalidPermission.NotFound error. For other ports, use a custom Systems Manager document.
Automated changes to a shared security group can also lock people out, for the same reason a manual change can. If you switch on auto-remediation, do it after Session Manager is working, not before. AWS has also published a security blog solution that uses GuardDuty findings, including both brute force finding types, to trigger automatic blocking of suspicious hosts. Treat those as patterns to study, not something to paste into production on a Friday.
Practice without a real attack
You don't have to wait for a real incident to learn what the finding looks like. GuardDuty can generate sample findings with fictitious details, marked with the prefix [SAMPLE], from the console, the API or the CLI. AWS's CLI reference shows the pattern with the create-sample-findings command, taking a detector ID and one or more finding types. Use the finding type UnauthorizedAccess:EC2/SSHBruteForce to practice reading the fields from the table earlier in this post, and to try out an EventBridge rule before you need it.
For a more realistic test, AWS publishes the amazon-guardduty-tester project. Run it in a dedicated non-production AWS account, because it deploys resources in your environment to simulate attacks. The project's readme says the SSH brute force finding shows up roughly 15 minutes after the script finishes. It is different from sample findings because it creates real activity, so keep it out of your production account.
What to tell your team, client, or boss
The alert usually lands five minutes after everyone has gone home, and the next morning someone will ask what happened. A short, plain note protects you more than any technical fix, because it separates what you know from what you don't. Jake wrote his for the one part-time employee who also has a key to the shop. Here is a shape that works for a teammate, a client or a manager.
- What happened. Name the finding type, the instance, the time window and the direction. For example: "GuardDuty reported repeated SSH password-guessing attempts against our server, coming in from outside."
- What we looked at. Say which records you reviewed and which time window they covered. If the records did not cover the whole window, say that too.
- What we found and what we did not find. "No successful login we could not explain" is a statement about the records you read. Don't stretch it into "nobody got in."
- What changed. The security group rule you narrowed, the second way in you set up, the alert rule you added.
- What happens next. A date for the follow-up check, and who owns it.
Notice what is missing: reassurance you can't back up. If the honest answer is "we could not confirm either way, so we rebuilt the server from a clean image," say exactly that. People forgive an inconvenient rebuild far more readily than a confident guess that turns out wrong.
When nothing works, and what we cannot tell you
Some honest limits. This post can't tell you whether anyone actually logged in to your server. Only your instance's own records can answer that, and if those records are gone or were tampered with, you may never get a clean answer. Nor can it tell you what a stranger did with a successful login. If you can't identify and stop the unauthorized activity, AWS's advice is to terminate the compromised instance and replace it, and it is a fair one. A rebuilt server from a known-good image is often less work than trusting a machine you can't fully vouch for.
If the finding keeps returning after you have restricted SSH, work through these in order:
- Check you edited the right security group. The finding shows the security group name and ID attached to the instance. An instance can have more than one group, and any one of them can hold an open SSH rule.
- Check both address families. Look for
0.0.0.0/0and::/0. - Check for other paths in. AWS's remediation page mentions network ACLs and on-host firewalls alongside security groups, so a permissive setting can live at more than one layer.
- Check the account and Region. The overview shows which account and Region generated the finding, and your other Regions may hold instances with the same mistake.
- Check whether the Actor changed. Aggregation swaps in the newest source IP, so a "new" address does not necessarily mean a new problem.
If you have done all of that and the picture still doesn't add up, AWS re:Post is the place to ask, and a premium support subscription lets you open a technical support case. You don't have to diagnose a possible break-in alone at midnight.
Ethan: "And Jake, the thing I'd like you to remember isn't a command. It's the order. Read the role, pick the route, fix the door, check the logs. If you do it in that order, the alert is a two-hour job. If you skip to terminate, it's a lost weekend."
Jake's Saturday queue was fine. He changed one rule, set up a way in that didn't depend on his shop's internet address, and left the tickets right where they were.
Frequently asked questions
What does UnauthorizedAccess:EC2/SSHBruteForce mean in GuardDuty?
It means an EC2 instance in your account was involved in a brute force attack aimed at obtaining passwords to SSH services on Linux-based systems. The instance can be either the target of the attack or the source of it, and the Resource role field tells you which. The activity can indicate unauthorized access, so the next step is always to read the direction and then check the login records.
Does an SSH brute force finding mean my EC2 instance was hacked?
Not by itself. The finding can indicate unauthorized access, and its Low severity level is defined as attempted suspicious activity that did not compromise the environment. But the finding is built from network traffic patterns, not from login results, so confirm by checking the instance's login records for any successful login you can't explain. If the records don't cover the whole time window, you can't confirm either way.
What is the difference between Resource role TARGET and ACTOR?
TARGET means your instance was the target of suspicious activity, and the finding shows an Actor section describing who targeted it. ACTOR means your instance carried out the activity, and the finding shows a Target section describing who it went after. TARGET is the routine case of bots probing an exposed port. ACTOR means your instance is likely compromised unless it has a legitimate reason to contact that address.
Why is my SSH brute force finding only low severity?
AWS sets this finding to Low when the brute force attack is aimed at one of your instances and to High when your instance is being used to perform the attack. Low is defined as attempted suspicious activity that did not compromise the environment. A Low finding still deserves a fix, because it signals that port 22 is reachable by attackers, and you should still check the login records.
Why didn't I get an alert email for this GuardDuty finding?
If you followed the usual example rule, it only matches severity 4 and above, meaning Medium, High and Critical. A TARGET-role SSH brute force finding is Low, so it stays in the console without sending anything. An ACTOR-role finding is High and would match. If you want Low findings, create a rule that matches this finding type on purpose.
How do I find the attacker's IP address in a GuardDuty finding?
Open the finding and read the Actor section, which lists the IP address, location and organization. From the CLI, AWS gives a get-findings query for the remote IPv4 address. Note that the address shown is the most recent source, because aggregation replaces older information, so when you search your login records, search by time window as well as by that one address.
Should I block the attacker's IP address?
Blocking one address rarely solves it, because the finding shows only the latest source and new actors replace the old one. AWS's remediation is to secure the SSH port so that only trusted IP addresses can reach it, through security groups, network ACLs or firewalls. That closes the door for every stranger at once, instead of chasing addresses one at a time.
How do I close port 22 to the internet without locking myself out?
Set up another way in first, such as Systems Manager Session Manager, which needs no inbound port. If you keep SSH, add a narrow rule for your public IP address or range, confirm a second connection works, and only then remove the wide rule. Without a static IP you need to work out the range your clients use.
What does the AWS Config restricted-ssh rule check?
It checks security groups and reports COMPLIANT when the incoming SSH addresses are restricted, meaning something other than 0.0.0.0/0 or ::/0. Otherwise it reports NON_COMPLIANT. Its rule identifier is INCOMING_SSH_DISABLED, its name is restricted-ssh, and it takes no parameters. It shows you every exposed group, not only the one GuardDuty named.
Why does the Count keep going up on the same finding?
GuardDuty aggregates repeat activity into the original finding instead of creating a new one, so the Count rises with each new attempt. Created at and Updated at will differ when the activity is ongoing. A new finding appears only when the activity targets a different instance, which gets its own finding ID.
Can I move SSH to a different port to stop the finding?
You can, but it only hides the alert. This finding is generated only by monitoring port 22, so a different port produces no finding while the exposure remains. Restrict who can connect instead, or close the inbound port entirely and use Session Manager. The Config rule for open ports can still flag a nonstandard port that is open to the world.
Does GuardDuty see brute force attempts over IPv6?
EC2 findings that use VPC flow logs as a data source do not support IPv6 traffic. The AWS Config restricted-ssh rule treats ::/0 as unrestricted, so it can flag an IPv6-open SSH rule that the GuardDuty finding is not built to detect. If you use IPv6, check your security groups directly.
Is it safe to create a suppression rule for this finding?
It can be, for a bastion host. AWS suggests two criteria: the finding type plus the bastion's image ID or tag value. Keep it narrow, because suppressed findings are archived and are not sent to Security Hub CSPM, Amazon S3, Amazon Detective or Amazon EventBridge. A broad rule could also hide a High-severity ACTOR finding on an instance that is not a bastion.
Do I need to enable VPC Flow Logs for GuardDuty to work?
No. GuardDuty consumes an independent, duplicate stream of flow log data, so you don't need to create or configure VPC Flow Logs for it. But GuardDuty discards the logs after extracting fields and doesn't make them available to you. If you want your own searchable record of connections, you have to enable VPC Flow Logs yourself.
How do I check for a successful login after a brute force finding?
Search the instance's SSH login records across the whole time window from Created at to Updated at, not only for the Actor IP shown. On an Amazon Linux AMI, AWS points to the /var/log/secure file, and other distributions may store these records elsewhere. Look for any successful login you don't recognize. CloudWatch can be used to review the logs if you have set it up.
When should I terminate the instance?
Not as a first reaction. For an ACTOR finding, isolate the instance and investigate. I recommend terminating and replacing it if you can't identify and stop the unauthorized activity. Record the instance details first, since terminated instances can lose details in findings, and remember that isolating with a new security group does not end connections that are already open.
Revision note. Written September 2026, covering the Amazon GuardDuty EC2 finding UnauthorizedAccess:EC2/SSHBruteForce, GuardDuty's EventBridge notification behavior, and the AWS Config managed rules restricted-ssh and vpc-sg-open-only-to-authorized-ports . This post will need updating if AWS renames console pages, changes the finding's severity behavior or the example alert pattern, or retires or replaces a finding type. If an alert with the word "brute force" in it reached you late at night, you did the right thing by reading it before reacting, and the fix is almost always smaller than the notification makes it feel.