Fixing AWS CloudWatch AccessDeniedException on PutLogEvents

intermediate☁️ AWS2026-03-31| AWS SDK (Python, Node.js, Go), AWS CLI, EC2, Lambda, or ECS using IAM Roles.

Error Message

An error occurred (AccessDeniedException) when calling the PutLogEvents operation: User: arn:aws:sts::123456789012:assumed-role/MyAppRole is not authorized to perform: logs:PutLogEvents on resource: arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/my-func:*
#aws#cloudwatch#iam#troubleshooting#devops

The Scenario

You’ve just deployed a new microservice or Lambda function. Everything seems fine until you check the logs. Instead of seeing your application data, you find a wall of AccessDeniedException errors. Your application is trying to push data to CloudWatch, but AWS is slamming the door shut.

An error occurred (AccessDeniedException) when calling the PutLogEvents operation: User: arn:aws:sts::123456789012:assumed-role/MyRole is not authorized to perform: logs:PutLogEvents on resource

This failure is frustrating because it often happens even when you think you've granted enough permissions. Even if you attached CloudWatchLogsFullAccess, a subtle resource-level restriction or a missing KMS permission can still break your logging pipeline.

Why Is This Happening?

The PutLogEvents action is pickier than most AWS operations. Here are the most common culprits:

  • Missing Helper Actions: Many SDKs, like Boto3 for Python, don't just call PutLogEvents. They often check for the stream's existence first using DescribeLogStreams. If that action is missing, the whole process fails.
  • The Trailing Wildcard: A common mistake is pointing your IAM policy at the Log Group ARN without adding :*. Without that wildcard, the policy grants rights to the group, but not the streams inside it.
  • KMS Encryption: If you’ve encrypted your logs with a Customer Managed Key (CMK), your IAM role needs more than just Log permissions. It needs permission to use that specific key.
  • Organization Guardrails: In many corporate AWS accounts, Service Control Policies (SCPs) act as a master override. If an SCP denies logs:*, your local IAM policy won't matter.

Step-by-Step Fix

1. Pinpoint the Exact Resource ARN

Identify the specific Log Group causing the stir. Open the CloudWatch Console and head to Log Groups. Grab the ARN from the Log group details tab. It should look like this:

arn:aws:logs:us-east-1:123456789012:log-group:my-app-logs

2. Refine the IAM Policy

Open the IAM role mentioned in your error message. Instead of using broad permissions, apply this targeted policy. It includes the Describe and Create actions that modern SDKs require to function smoothly.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogStreams",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:123456789012:log-group:my-app-logs:*"
            ]
        }
    ]
}

Pro Tip: Notice the :* at the end of the Resource string. This is mandatory for PutLogEvents because the action technically occurs on the log stream, which is a child of the log group.

3. Check for KMS Key Permissions

Are your logs encrypted with a custom key? If so, your IAM role needs to be a "Key User." Add this statement to your role's policy to allow it to encrypt log data on the fly:

{
    "Effect": "Allow",
    "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey*"
            ],
    "Resource": "arn:aws:kms:us-east-1:123456789012:key/your-key-id"
}

Testing the Fix

IAM changes usually take effect in seconds, though they can occasionally take up to a minute to propagate. You can test the connection immediately using the AWS CLI from your local machine (assuming you have the same permissions):

1. Create a test stream:

aws logs create-log-stream --log-group-name my-app-logs --log-stream-name manual-test

2. Send a dummy event:

aws logs put-log-events \
    --log-group-name my-app-logs \
    --log-stream-name manual-test \
    --log-events timestamp=$(date +%s000),message="Permission test success"

If the CLI returns a nextSequenceToken, you are back in business.

Summary of Best Practices

  • Use Least Privilege: Don't use Resource: "*". It’s a security risk. Limit access to the specific Log Groups your application actually uses.
  • Infrastructure as Code: Define these permissions in Terraform or AWS CDK. Manual console changes are hard to track and even harder to replicate when you move from Staging to Production.
  • Monitor for Throttling: If you fix the permissions but logs are still missing, check for ThrottlingException. You might be hitting the 5 transactions per second (TPS) limit for PutLogEvents per stream.

Related Error Notes