Introduction
AWS Lambda function timeout errors occur when function execution exceeds the configured timeout limit, causing AWS to terminate the invocation with a Task timed out after X.XX seconds error. Unlike application exceptions, timeouts provide minimal error context—the function is simply killed. Root causes include cold start delays, slow downstream dependencies, insufficient memory causing CPU throttling, large batch sizes, or infinite loops. Timeouts are particularly challenging because terminated functions cannot log their failure state, requiring proactive instrumentation and distributed tracing for diagnosis.
Symptoms
- CloudWatch Logs show
Task timed out after X.XX secondserror - Function invocation returns
Status: FailedwithError Type: Lambda.TimeoutException - Duration metric shows
Timeoutinstead of milliseconds - Issue intermittent during normal operation, consistent during outages
- Timeouts increase during traffic spikes or downstream service degradation
- Partial batch failures in SQS/Kinesis event sources
- Issue appears after deploy adding new dependencies, increased payload size, or network configuration changes
Common Causes
- Cold start latency exceeding timeout (VPC, Java/.NET, large packages)
- Synchronous invocation chain with cumulative latency
- Downstream service (RDS, API, DynamoDB) responding slowly
- Insufficient memory causing CPU throttling (Lambda allocates CPU proportionally)
- Large event batch size causing processing to exceed timeout
- Network connectivity issues (VPC NAT gateway, security groups)
- Infinite loops or unbounded recursion in code
- Provisioned concurrency not configured for latency-sensitive functions
Step-by-Step Fix
### 1. Analyze CloudWatch Logs for timeout patterns
Extract timeout patterns from logs:
```bash # Filter timeout errors aws logs filter-log-events \ --log-group-name /aws/lambda/<function-name> \ --filter-pattern "Task timed out" \ --start-time $(date -d '1 hour ago' +%s)000 \ --query 'events[*].[timestamp, message]' \ --output table
# Check function duration before timeout aws logs filter-log-events \ --log-group-name /aws/lambda/<function-name> \ --filter-pattern "REPORT RequestId" \ --start-time $(date -d '1 hour ago' +%s)000 \ --query 'events[*].message' \ --output text | grep -oP 'Duration: \K[0-9.]+' | sort -n | tail -20
# Check for patterns before timeout # Look for last successful log entry before timeout ```
Key log fields to analyze:
- Duration: Function execution time in ms
- Billed Duration: Rounded up to nearest 1ms
- Memory Size: Configured memory in MB
- Max Memory Used: Peak memory consumption
- Init Duration: Cold start initialization time
### 2. Enable AWS X-Ray for distributed tracing
X-Ray reveals where time is spent:
```bash # Enable X-Ray tracing aws lambda update-function-configuration \ --function-name <function-name> \ --tracing-config "Mode=Active"
# Or via console: Configuration > Monitoring > X-Ray > Active
# Get trace summaries for timeout invocations aws xray get-trace-summaries \ --start-time $(date -d '1 hour ago' +%s) \ --end-time $(date +%s) \ --filter-expression "annotation.aws:LambdaFunctionName = '<function-name>' AND error = true" \ --query 'TraceSummaries[*].[Id, StartTime, Duration]'
# Get full trace details aws xray get-trace \ --trace-ids <trace-id> \ --query 'Traces[0].Segments[*].Document' ```
X-Ray trace analysis:
- Look for subsegment with highest duration
- Identify error or fault annotations
- Check http subsegments for slow API calls
- Review aws subsegments for DynamoDB/Lambda calls
### 3. Check downstream dependency latency
Identify slow external services:
```python # Add timing instrumentation to handlers import time import logging from datetime import datetime
logger = logging.getLogger() logger.setLevel(logging.INFO)
def lambda_handler(event, context): start = datetime.now()
try: # Time database calls db_start = time.time() result = query_database(event) logger.info(f"DB query took: {time.time() - db_start:.3f}s")
# Time API calls api_start = time.time() response = call_external_api(event) logger.info(f"API call took: {time.time() - api_start:.3f}s")
# Time S3 operations s3_start = time.time() upload_to_s3(result) logger.info(f"S3 upload took: {time.time() - s3_start:.3f}s")
total = (datetime.now() - start).total_seconds() remaining = context.get_remaining_time_in_millis() / 1000 logger.info(f"Total execution: {total:.3f}s, Remaining: {remaining:.3f}s")
return {"statusCode": 200, "body": "Success"}
except Exception as e: logger.error(f"Error: {str(e)}") raise ```
Check RDS/Aurora connectivity:
```python # Add connection timeout and socket timeout import pymysql
def query_database(event): connection = pymysql.connect( host=os.environ['DB_HOST'], user=os.environ['DB_USER'], password=os.environ['DB_PASS'], database=os.environ['DB_NAME'], connect_timeout=5, # Fail fast if DB unreachable read_timeout=30, # Max query time write_timeout=30, cursorclass=pymysql.cursors.DictCursor )
try: with connection.cursor() as cursor: cursor.execute("SELECT /* Lambda */ * FROM table") return cursor.fetchall() finally: connection.close() ```
### 4. Optimize cold start latency
Cold starts add 1-15 seconds to first invocation:
```python # WRONG: Heavy imports at module level import pandas # 50MB, slow import import numpy import boto3 import requests
# CORRECT: Lazy imports for cold start optimization _pandas = None def get_pandas(): global _pandas if _pandas is None: import pandas _pandas = pandas return _pandas
def lambda_handler(event, context): pd = get_pandas() # Use pandas only when needed ```
Cold start best practices:
```python # Initialize clients outside handler (connection reuse) import boto3 dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') rds = boto3.client('rds')
# Use connection pooling for database import psycopg2 from psycopg2 import pool
# Global connection pool (initialized once per container) _db_pool = None
def get_db_pool(): global _db_pool if _db_pool is None: _db_pool = psycopg2.pool.SimpleConnectionPool( 1, 20, host=os.environ['DB_HOST'], database=os.environ['DB_NAME'], user=os.environ['DB_USER'], password=os.environ['DB_PASS'], connect_timeout=5 ) return _db_pool
def lambda_handler(event, context): conn = get_db_pool().getconn() try: # Use connection pass finally: conn.putconn() ```
Deployment package optimization:
```bash # Check deployment package size aws lambda get-function \ --function-name <function-name> \ --query 'Configuration.CodeSize'
# > 50MB: Consider Lambda layers # > 250MB: Will have significant cold start
# Create layer for large dependencies mkdir python pip install pandas numpy -t python/ zip -r9 layer.zip python aws lambda publish-layer-version \ --layer-name python-deps \ --zip-file fileb://layer.zip \ --compatible-runtimes python3.9 ```
### 5. Tune memory and CPU allocation
Lambda CPU scales with memory:
```bash # Check current configuration aws lambda get-function-configuration \ --function-name <function-name> \ --query '{Memory:MemorySize,Timeout:Timeout,CPU:EPHemeralStorage.Size}'
# Memory and CPU relationship: # 128MB = 0.17 vCPU # 1024MB = 1.0 vCPU (baseline) # 3008MB = 3.0 vCPU (max)
# If function is CPU-bound, increase memory aws lambda update-function-configuration \ --function-name <function-name> \ --memory-size 2048 \ --timeout 30
# Monitor actual memory usage
aws lambda get-function-metrics \
--function-name <function-name> \
--start-time $(date -d '7 days ago' +%Y-%m-%d) \
--end-time $(date +%Y-%m-%d) \
--query 'Metrics[?MetricName==MemoryUtilization]'
```
Power Tuning tool:
```bash # Use Lambda Power Tuning (open source) # Deploy via SAM: git clone https://github.com/aws-samples/aws-lambda-power-tuning cd aws-lambda-power-tuning sam deploy --guided
# Invoke via Step Functions console # Input: function ARN, payload, memory values to test # Output: optimal memory for cost/performance ```
### 6. Configure reserved and provisioned concurrency
Prevent concurrency starvation:
```bash # Check current concurrency aws lambda get-function-concurrency \ --function-name <function-name>
# Set reserved concurrency (guaranteed capacity) aws lambda put-function-concurrency \ --function-name <function-name> \ --reserved-concurrent-executions 50
# Configure provisioned concurrency for latency-sensitive functions aws lambda put-provisioned-concurrency-config \ --function-name <function-name> \ --qualifier LIVE \ --provisioned-concurrent-executions 10
# Or for alias aws lambda put-provisioned-concurrency-config \ --function-name <function-name> \ --qualifier prod \ --provisioned-concurrent-executions 20 ```
When to use provisioned concurrency: - Synchronous APIs with strict latency SLOs - Functions with consistent traffic patterns - Critical business functions where timeouts are unacceptable - VPC-connected functions (eliminates ENI allocation delay)
### 7. Handle event source batch sizes
Large batches can cause timeouts:
```bash # Check SQS event source configuration aws lambda get-event-source-mapping \ --uuid <mapping-uuid>
# Output: # { # "BatchSize": 10, # "MaximumBatchingWindowInSeconds": 0, # ... # }
# Reduce batch size for faster processing aws lambda update-event-source-mapping \ --uuid <mapping-uuid> \ --batch-size 5 \ --maximum-batching-window-in-seconds 5 ```
Batch processing with partial failure:
```python # SQS batch with partial failure handling def lambda_handler(event, context): failed_messages = []
for record in event['Records']: try: process_message(record['body']) except Exception as e: logger.error(f"Failed to process: {e}") failed_messages.append({ 'itemIdentifier': record['messageId'] })
# Return failed messages for retry # Don't let entire batch fail due to single message if failed_messages: return { 'batchItemFailures': failed_messages } return {} ```
Kinesis/Sharding for throughput:
```python # Process Kinesis records with checkpointing def lambda_handler(event, context): for record in event['Records']: try: process_record(record['kinesis']['data']) # Checkpoint after each record # (handled by Lambda for Kinesis) except Exception as e: logger.error(f"Failed: {e}") # Lambda will retry from failed sequence number raise
# Configure parallel processing with shards # More shards = more concurrent Lambda invocations ```
### 8. Check VPC network configuration
VPC functions can timeout due to networking:
```bash # Check function VPC configuration aws lambda get-function-configuration \ --function-name <function-name> \ --query 'VpcConfig'
# Output: # { # "SubnetIds": ["subnet-xxx"], # "SecurityGroupIds": ["sg-xxx"], # "VpcId": "vpc-xxx" # }
# Verify NAT gateway exists for internet access
aws ec2 describe-route-tables \
--filters "Name=association.subnet-id,Values=<subnet-id>" \
--query 'RouteTables[*].Routes[?GatewayId!=null && starts_with(GatewayId, nat-)]'
# Check security group egress rules aws ec2 describe-security-groups \ --group-ids <sg-id> \ --query 'SecurityGroups[0].IpPermissionsEgress'
# Expected: Allow all outbound or specific ports # { # "IpProtocol": "-1", # "IpRanges": [{"CidrIp": "0.0.0.0/0"}] # } ```
VPC endpoint optimization:
```bash # Create VPC endpoints for AWS services (no NAT needed) aws ec2 create-vpc-endpoint \ --vpc-id <vpc-id> \ --service-name com.amazonaws.<region>.dynamodb \ --vpc-endpoint-type Gateway
aws ec2 create-vpc-endpoint \ --vpc-id <vpc-id> \ --service-name com.amazonaws.<region>.s3 \ --vpc-endpoint-type Gateway
# For Lambda service endpoint aws ec2 create-vpc-endpoint \ --vpc-id <vpc-id> \ --service-name com.amazonaws.<region>.execute-api \ --vpc-endpoint-type Interface \ --subnet-ids subnet-xxx \ --security-group-ids sg-xxx ```
### 9. Implement graceful timeout handling
Handle timeout before Lambda terminates:
```python # Check remaining time in handler import signal import logging
logger = logging.getLogger()
class TimeoutError(Exception): pass
def timeout_handler(signum, frame): raise TimeoutError("Function timeout approaching")
def lambda_handler(event, context): # Set alarm 5 seconds before timeout remaining = context.get_remaining_time_in_millis() / 1000 if remaining < 10: # Not enough time to do meaningful work logger.warning(f"Insufficient time remaining: {remaining}s") return {"statusCode": 408, "body": "Timeout approaching"}
# Set timer for 5 seconds before timeout signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(int(remaining) - 5)
try: result = process_with_timeout(event, remaining - 5) signal.alarm(0) # Cancel alarm return result except TimeoutError as e: logger.warning(f"Timeout handled gracefully: {e}") # Save partial progress, return error save_partial_state(event) return {"statusCode": 408, "body": "Partial processing completed"} ```
Async processing for long operations:
```python # Step Functions for workflow orchestration import boto3 sfn = boto3.client('stepfunctions')
def lambda_handler(event, context): # Start Step Functions execution for long-running workflow response = sfn.start_execution( stateMachineArn=os.environ['SFN_ARN'], input=json.dumps(event) )
# Return immediately, Step Functions handles the workflow return { "statusCode": 200, "executionArn": response['executionArn'], "body": "Processing started" } ```
### 10. Set up timeout monitoring and alerting
CloudWatch alarm for timeout detection:
```bash # Create CloudWatch alarm aws cloudwatch put-metric-alarm \ --alarm-name lambda-timeouts-<function-name> \ --alarm-description "Lambda function timeout rate exceeds threshold" \ --metric-name Errors \ --namespace AWS/Lambda \ --statistic Sum \ --period 60 \ --threshold 5 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 1 \ --dimensions Name=FunctionName,Value=<function-name> \ --alarm-actions arn:aws:sns:region:account:lambda-alerts
# Also monitor duration approaching timeout aws cloudwatch put-metric-alarm \ --alarm-name lambda-duration-warning-<function-name> \ --alarm-description "Lambda duration approaching timeout" \ --metric-name Duration \ --namespace AWS/Lambda \ --statistic p95 \ --period 300 \ --threshold 15000 \ --comparison-operator GreaterThanThreshold \ --evaluation-periods 2 \ --dimensions Name=FunctionName,Value=<function-name> \ --alarm-actions arn:aws:sns:region:account:lambda-alerts ```
Dashboard metrics:
- Invocations: Request rate
- Errors: Timeout and exception count
- Duration: p50, p95, p99, max
- Throttles: Request throttling
- ConcurrentExecutions: Active invocations
- UnreservedConcurrentExecutions: Available capacity
Prevention
- Set timeout to 2x p99 duration with headroom for spikes
- Use provisioned concurrency for latency-critical functions
- Implement circuit breakers for downstream dependencies
- Add connection timeouts to all network calls
- Use async patterns (SQS, Step Functions) for long operations
- Monitor p95 duration and alert at 70% of timeout
- Regularly test cold start performance after deployments
- Use Lambda Power Tuning to find optimal memory setting
Related Errors
- **Task timed out**: Function exceeded configured timeout
- **Request throttled**: Concurrent execution limit reached
- **Memory limit exceeded**: Function exceeded memory allocation
- **Connection timeout**: Downstream service unreachable