How to Fix SQS Visibility Timeout in AWS Lambda

4 min read

If you’re triggering a Lambda function from an SQS queue, you may hit this error during deployment:

Invalid request provided: Queue visibility timeout: 30 seconds is less than Function timeout: 300 seconds

This means your SQS queue’s visibility timeout is shorter than your Lambda function’s timeout. AWS blocks this configuration because it causes duplicate message processing. This guide explains why the error happens and how to fix the SQS visibility timeout in AWS Lambda correctly.

Why This Happens

When Lambda polls a message from SQS, that message becomes invisible to other consumers for the duration of the visibility timeout. If Lambda takes longer to process the message than the visibility timeout allows, SQS assumes processing failed and makes the message visible again. Another Lambda invocation picks it up, and now you have two invocations processing the same message.

This leads to:

  • Duplicate processing (double charges, repeated emails, duplicate database writes)
  • Data inconsistency across systems
  • Wasted compute from redundant invocations

With the default SQS visibility timeout of 30 seconds and a Lambda timeout of 300 seconds, any invocation running longer than 30 seconds triggers a retry — even though the original is still running.

The Fix: Set Visibility Timeout to 6x Lambda Timeout

AWS recommends setting the queue’s visibility timeout to at least 6 times the Lambda function timeout, plus the MaximumBatchingWindowInSeconds value if you use batching. This accounts for the function’s execution time and allows room for Lambda to retry on throttling errors before the message becomes visible again.

For a Lambda function with a 300-second (5-minute) timeout:

  • Minimum visibility timeout: 300 × 6 = 1800 seconds (30 minutes)
  • With a 5-second batching window: (300 × 6) + 5 = 1805 seconds

The maximum visibility timeout for an SQS queue is 12 hours (43,200 seconds).

Configuring with AWS CLI

If your queue already exists, update the visibility timeout directly:

aws sqs set-queue-attributes \
  --queue-url https://sqs.ap-southeast-1.amazonaws.com/123456789012/my-queue \
  --attributes VisibilityTimeout=1800 \
  --region ap-southeast-1

Verify the change:

aws sqs get-queue-attributes \
  --queue-url https://sqs.ap-southeast-1.amazonaws.com/123456789012/my-queue \
  --attribute-names VisibilityTimeout \
  --region ap-southeast-1

You can also update this in the SQS console under Queue > Edit > Visibility timeout.

Configuring with CloudFormation

If you manage your infrastructure as code, define both the main queue and a dead letter queue (DLQ) in your CloudFormation template:

Resources:
  MainQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: my-queue
      VisibilityTimeout: 1800
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
        maxReceiveCount: 3

  DeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: my-queue-dlq
      VisibilityTimeout: 60
      MessageRetentionPeriod: 1209600

  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: my-function
      Handler: src/handler.lambda_handler
      Runtime: python3.12
      Timeout: 300

  SQSEventSource:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      EventSourceArn: !GetAtt MainQueue.Arn
      FunctionName: !Ref MyFunction
      BatchSize: 10
  • VisibilityTimeout: 1800 — 6x the Lambda timeout of 300 seconds
  • maxReceiveCount: 3 — after 3 failed attempts, the message moves to the DLQ
  • MessageRetentionPeriod: 1209600 — keeps DLQ messages for 14 days before automatic deletion

Configuring with Serverless Framework

The same configuration in serverless.yml:

functions:
  myFunction:
    handler: src/handler.lambda_handler
    timeout: 300

resources:
  Resources:
    MainQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ${sls:stage}-my-queue
        VisibilityTimeout: 1800
        RedrivePolicy:
          deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
          maxReceiveCount: 3

    DeadLetterQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ${sls:stage}-my-queue-dlq
        VisibilityTimeout: 60
        MessageRetentionPeriod: 1209600

Handling Failed Messages with a Dead Letter Queue

A dead letter queue (DLQ) captures messages that fail processing after maxReceiveCount attempts. Without a DLQ, failed messages keep cycling between the queue and Lambda until the message retention period expires.

Lambda does not automatically retry messages from the DLQ. To reprocess them, you can:

  • Use SQS dead-letter queue redrive in the console — select the DLQ, choose “Start DLQ redrive”, and move messages back to the main queue
  • Write a separate Lambda function that polls the DLQ and reprocesses or routes the messages
  • Manually inspect and replay messages using the AWS CLI

If your processing involves long-running or multi-step workflows, consider using Step Functions instead of a single Lambda. For an example of this pattern, see How to Avoid AWS Lambda Timeout When Processing HubSpot Records.

Monitoring SQS and Lambda

Watch these CloudWatch metrics to catch visibility timeout issues early:

Metric What It Tells You
ApproximateNumberOfMessagesVisible Messages waiting to be processed — a growing number means Lambda can’t keep up
ApproximateNumberOfMessagesNotVisible Messages currently being processed (in-flight)
NumberOfMessagesReceived Total messages received — compare with NumberOfMessagesDeleted to spot retries
ApproximateAgeOfOldestMessage How long the oldest message has been in the queue — high values indicate a processing backlog

Set a CloudWatch alarm on the DLQ’s ApproximateNumberOfMessagesVisible metric. Any message landing in the DLQ means something failed, and you want to know about it immediately. For cleaning up old Lambda logs generated during debugging, see How to Delete AWS Lambda Logs from CloudWatch using Python Scripts.

Quick Reference

Setting Value
Main queue visibility timeout 6x Lambda timeout (e.g., 1800s for a 300s function)
DLQ visibility timeout 60 seconds
DLQ max receive count 3–5 attempts
DLQ message retention 14 days (1209600 seconds)
Maximum visibility timeout 12 hours (43,200 seconds)

Conclusion

The fix is straightforward: set your SQS queue’s visibility timeout to at least 6x your Lambda function’s timeout. Pair it with a dead letter queue and a CloudWatch alarm on the DLQ to catch failures early.

For more on how Lambda receives and processes messages, see Understanding Lambda’s Event and Context Parameters. If you’re building cross-account messaging with SQS, check out Setting Up Cross-Account SNS to SQS Subscription in AWS.