If you've built anything real on Lambda, you've seen this line in your logs at least once:
Task timed out after 900.00 seconds
And then you did what everyone does. You split the job into chunks, or chained a few functions together with Step Functions, or saved progress to DynamoDB so the next run could pick up where the last one died. Or you gave up and moved the whole thing to Fargate.
The limit went from 5 minutes in 2014 to 15 minutes in 2018. Then it stayed there for eight years.
On September 9, 2026, AWS finally raised it. Lambda functions can now run for up to 90 minutes. That's 5,400 seconds.
But there's a catch that most headlines skip, and it changes everything: this only works on Lambda Managed Instances. That's a different way of running Lambda, with a very different bill. If you just bump the timeout on your normal function, it won't work.
So let's go through what actually changed, how to set it up, what it really costs, and when it's cheaper than Fargate (and when it's not even close).
What changed, and what didn't
The 90-minute limit doesn't apply to every kind of invocation. This is the part that confuses people, so here it is in one table:
| How the function is called | Max timeout | Examples |
|---|---|---|
| Asynchronous | 90 min | S3 events, SNS, EventBridge, InvocationType=Event
|
| Event source mapping | 90 min | SQS, Kinesis, DynamoDB Streams, MSK, self-managed Kafka |
| Event source mapping from Amazon MQ or DocumentDB | 15 min | Still capped |
| Synchronous | 15 min | API Gateway, ALB, normal SDK calls |
| Anything on standard Lambda | 15 min | Every function not on Managed Instances |
A few other things worth knowing:
- Synchronous calls get cut off at 15 minutes even if your timeout says 5,400. The config still shows 5,400, so nothing warns you.
- The Init phase (your startup code) is still limited to 15 minutes.
- You don't need to change your code. Same runtime, same handler, same IAM role, same VPC.
- AWS doesn't charge extra for the longer timeout itself. You pay normal Managed Instances pricing, which we'll get to.
- It works with durable functions too. Each async invocation inside a durable function on Managed Instances can run 90 minutes, and the full durable execution can last up to a year.
Wait, what are Lambda Managed Instances?
Normal Lambda runs each request inside its own tiny Firecracker microVM. Lambda Managed Instances, which AWS launched in late 2025, work differently. Your function runs on EC2 instances inside your own VPC, and AWS still takes care of those instances for you: launching them, patching them, scaling them.
There are three pieces:
- A capacity provider. This is where you say which VPC and subnets to use, which CPU architecture, which instance types are allowed, and the maximum number of vCPUs. It's also the security boundary.
- EC2 instances. Lambda launches these in your account. By default it runs three of them across Availability Zones.
- Execution environments. These are containers on the instances, and each one can handle many requests at the same time (up to 1,600).
flowchart LR
E[SQS / S3 / EventBridge] --> F[Lambda function version]
F --> CP[Capacity provider]
CP --> I1[EC2 instance AZ-a]
CP --> I2[EC2 instance AZ-b]
I1 --> X1[Execution environments]
I2 --> X2[Execution environments]
This setup changes a few things you might be used to.
There are no cold starts, because requests go to environments that are already running. The flip side is that there's no scale-to-zero either. The instances keep running, and keep costing money, even when nothing is happening.
Your code also has to handle several requests at once in the same environment. If you write to a fixed file like /tmp/output.csv, or keep request data in global variables, you'll get strange bugs.
And AWS is clear that containers are not a strong isolation boundary between functions. If you have workloads that shouldn't trust each other, give them separate capacity providers.
You can also pick the memory-to-vCPU ratio (2, 4 or 8 GiB per vCPU) and specific instance families if you need them, including the new Graviton5 C9g and M9g instances.
Setting it up
Here's the CLI version. The console follows the same steps under Lambda > Capacity providers.
1. Create two IAM roles
You need an execution role (what the function itself can access) and an operator role (which lets Lambda manage the EC2 instances for you). Both trust lambda.amazonaws.com.
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
EOF
# Execution role
aws iam create-role --role-name LongJobExecutionRole \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --role-name LongJobExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Operator role
aws iam create-role --role-name LongJobOperatorRole \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --role-name LongJobOperatorRole \
--policy-arn arn:aws:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator
If this is the first capacity provider in your account, you'll also need iam:CreateServiceLinkedRole.
2. Create the capacity provider
Use subnets in at least two Availability Zones:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws lambda create-capacity-provider \
--capacity-provider-name long-jobs \
--vpc-config SubnetIds=[subnet-aaa,subnet-bbb],SecurityGroupIds=[sg-123] \
--permissions-config CapacityProviderOperatorRoleArn=arn:aws:iam::${ACCOUNT_ID}:role/LongJobOperatorRole \
--instance-requirements Architectures=[arm64] \
--capacity-provider-scaling-config MaxVCpuCount=30
Think of MaxVCpuCount as your spending ceiling. Start small.
3. Create the function
REGION=$(aws configure get region)
aws lambda create-function \
--function-name long-job \
--runtime python3.13 \
--handler app.handler \
--zip-file fileb://function.zip \
--role arn:aws:iam::${ACCOUNT_ID}:role/LongJobExecutionRole \
--architectures arm64 \
--memory-size 4096 \
--timeout 5400 \
--capacity-provider-config LambdaManagedInstancesCapacityProviderConfig={CapacityProviderArn=arn:aws:lambda:${REGION}:${ACCOUNT_ID}:capacity-provider:long-jobs}
4. Publish a version
This one catches people. On Managed Instances, only published versions run. Nothing happens until you publish:
aws lambda publish-version --function-name long-job
Give it a few minutes. Lambda is launching real EC2 instances in the background.
5. Invoke it asynchronously
Synchronous calls still stop at 15 minutes, so test with an async call:
aws lambda invoke \
--function-name long-job:1 \
--invocation-type Event \
--cli-binary-format raw-in-base64-out \
--payload '{"minutes": 30}' \
response.json
A simple handler to prove it runs past 15 minutes:
import time
def handler(event, context):
minutes = event.get("minutes", 20)
for i in range(minutes):
time.sleep(60)
print(f"Still running: minute {i + 1}")
return {"done": True, "minutes": minutes}
If you use AWS SAM, the timeout is just one line:
LongJobFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.13
Handler: app.handler
Timeout: 5400
MemorySize: 4096
You attach the capacity provider in the same template. Check the SAM docs for the current property names.
What it really costs
AWS says the longer timeout is free, and that's true. But the real question is what the capacity behind it costs.
Managed Instances pricing has three parts:
- Requests: $0.20 per million, same as normal Lambda.
- EC2 instances: regular EC2 pricing for the instances in your capacity provider. Savings Plans and Reserved Instances apply here.
- A management fee: 15% of the EC2 on-demand price. Savings Plans don't reduce this part.
What's missing is the per-millisecond duration charge. You pay for instances being up, not for your code running. That's why a 90-minute run doesn't cost more than a 1-minute run on the same instance. It's also why idle time isn't free.
The worst case: a job that runs now and then
Let's put numbers on it with a pretty normal job, the kind that hits the 15-minute wall today:
- Runs twice a day, about 45 minutes each time
- Needs 2 vCPU and 4 GB of memory
- us-east-1, Graviton (arm64), on-demand prices
| Option | How it's billed | Monthly cost (approx.) |
|---|---|---|
| Standard Lambda | Not possible, stops at 15 minutes | n/a |
| Managed Instances (3 x m7g.large, always on) | 3 x $0.0816/hr x 730 hrs = $178.70, plus 15% fee = $26.81 | ~$205 |
| ECS Fargate (arm64, 2 vCPU / 4 GB, only while running) | ~$0.079/hr x 45 hrs of runtime | ~$3.60 |
No, that's not a typo. For a job that only runs now and then, Managed Instances can cost around 50 times more than Fargate. Lambda keeps three instances running around the clock whether your job is running or not.
But that example is the worst case for anything that's always on. So let's look at the other side.
When Managed Instances are cheaper than Fargate
One thing first: on plain on-demand pricing, the two cost about the same for the same machine size. A 2 vCPU / 8 GB Fargate task (arm64) costs about $0.093 per hour. An m7g.large (also 2 vCPU / 8 GB) on Managed Instances costs $0.0816 plus the 15% fee, so about $0.094 per hour. Basically a tie.
Managed Instances only win when something tips the balance. There are two big ones.
Scenario 1: Steady 24/7 work with a Savings Plan
Say you have a queue that never really empties, and it keeps 12 vCPU and 48 GB of memory busy all day, every day.
| Fargate (6 tasks, 2 vCPU / 8 GB) | Managed Instances (6 x m7g.large) | |
|---|---|---|
| On-demand | ~$408/month | ~$411/month ($357 EC2 + $54 fee) |
| With a 3-year Savings Plan | ~$196/month (up to 52% off) | ~$154/month ($100 EC2 at 72% off + $54 fee) |
With commitments in place, Managed Instances save about $42 a month, roughly 21%, or around $500 a year.
Notice the fee, though. The 15% is always charged on the on-demand price, so even with a big discount you still pay that $54. That's why the saving here is real but modest.
Scenario 2: Lots of long jobs that mostly wait
This is where Managed Instances really shine, and it's getting very common with AI workloads.
Imagine jobs that run 20 to 60 minutes but spend most of that time waiting: calling an LLM, polling an external API, scraping slow websites. Each job needs about 0.5 GB of memory and barely any CPU. At peak you have 200 of them running at once, all day.
On Fargate, the usual setup is one job per task. The smallest task that fits is 0.25 vCPU / 1 GB, and you're paying for 200 of them.
On Managed Instances, one execution environment can handle many requests at the same time. So those 200 jobs share a handful of instances. 200 jobs x 0.5 GB is 100 GB, which fits on 8 x r7g.large (16 GB each) with some room to spare.
| Fargate (200 tasks, 0.25 vCPU / 1 GB) | Managed Instances (8 x r7g.large) | |
|---|---|---|
| Compute | ~$1,702/month | ~$625/month EC2 |
| Management fee | n/a | ~$94/month |
| Requests | n/a | under $1/month |
| Total (on-demand) | ~$1,702/month | ~$719/month |
That's about $983 saved every month, around 58%, or roughly $11,800 a year. And that's before any Savings Plan.
To be fair to Fargate: you could get similar packing by writing your own worker that runs many jobs at once inside one task. But then you're building and running your own queue polling, concurrency and retry logic. With Managed Instances, you get that from the normal Lambda model.
All prices are approximate us-east-1 rates at the time of writing, arm64/Graviton. Savings Plan discounts are the best-case "up to" figures for 3-year plans, so real discounts are often lower. Check your own numbers in the AWS Pricing Calculator.
The simple rule
- Jobs that run now and then: Fargate wins by a lot
- Steady 24/7 work, on-demand: about a tie
- Steady 24/7 work with a Savings Plan: Managed Instances, by about 20%
- Many long jobs that mostly wait on network calls: Managed Instances, by 50% or more
One more option worth knowing: if your jobs can handle being interrupted, Fargate Spot can be up to 70% cheaper than regular Fargate, which may beat both.
So when should you use it?
| Your situation | What I'd use |
|---|---|
| Long jobs on a busy, steady queue | Lambda Managed Instances |
| Many long jobs waiting on LLMs or APIs | Lambda Managed Instances |
| A long job that runs a few times a day | ECS Fargate or AWS Batch |
| A job you can split into steps | Durable functions on standard Lambda |
| A long job where restarting from zero is expensive | Managed Instances plus durable functions |
| A workflow with branches, retries and approvals | Step Functions |
| An HTTP request that needs more than 15 minutes | Return a job ID and process it async |
My honest view: if you already run steady workloads on Managed Instances, this is a free win, and you can probably delete a fair bit of Step Functions glue. If you don't, don't adopt Managed Instances just for one long job. Fargate will be much cheaper.
Things that will bite you in production
These come from the AWS docs, plus what naturally goes wrong when a function runs six times longer than before.
Your SQS visibility timeout needs to be at least 6x the function timeout. For 90 minutes, that's 9 hours. Lambda checks this when you create the trigger, but it won't stop you from changing things later. Get it wrong and the same message gets processed twice.
NAT Gateway drops idle connections after 350 seconds. Open a database connection, work for 20 minutes, then try to use it, and it may be gone. Send keep-alives or reconnect before using it.
Tokens can expire halfway through. If your function grabs a temporary token at the start, make sure it lasts the full run, or refresh it.
Longer runs mean more chances of duplicates. Lambda doesn't promise exactly-once processing, and async calls retry twice by default. A job that fails at minute 80 might start over from the beginning. Make it idempotent. Powertools for AWS Lambda has a utility for this.
New concurrency bugs. Several requests share one environment, so use unique file names in /tmp (the request ID works well) and keep request data out of global variables.
Deploys are slow. Publishing a version launches EC2 instances, so it takes minutes, not seconds. Plan your pipeline and rollbacks around that.
And the classic one: if the job is triggered through API Gateway, it still stops at 15 minutes, with no warning.
Common errors
| Error | Likely cause | Fix |
|---|---|---|
| Timeout above 900 rejected | Function is on standard Lambda | Attach it to a capacity provider |
| Stops at exactly 15 minutes | Called synchronously | Call it async or through a queue |
| Stops at 15 minutes from Amazon MQ or DocumentDB | Those sources are still capped | Put SQS in front |
| Same message processed twice | SQS visibility timeout too short | Set it to at least 6x the function timeout |
| Invoke fails right after creating the function | No published version, or instances still starting | Publish a version and wait a few minutes |
| Capacity provider creation fails | Missing iam:CreateServiceLinkedRole
|
Add it for first-time setup |
Before you ship it
- [ ] Function is on a capacity provider, and a version is published
- [ ] It's triggered async or through SQS, Kinesis, DynamoDB Streams, MSK or Kafka
- [ ] SQS visibility timeout is at least 6x the function timeout
- [ ] Code handles concurrent requests (unique
/tmppaths, no shared request state) - [ ] The job is idempotent, or checkpoints with durable functions
- [ ] Connections survive long idle gaps
- [ ] Tokens last the whole run, or get refreshed
- [ ]
MaxVCpuCountis set as a cost ceiling - [ ] You've compared the cost with Fargate for your actual usage pattern
Wrapping up
People have been asking for longer Lambda timeouts for years, and this finally delivers. A lot of workaround code can go.
But it isn't "the same Lambda, just six times longer." It's a feature of a different compute model, with always-on instances and a different kind of bill. For an occasional job, it can cost 50 times more than Fargate. For a busy queue full of long jobs that mostly wait, it can save you more than half.
So do the math for your own workload before you switch. If you're already on Managed Instances, turn it on today. If you've got one long job that runs now and then, leave it on Fargate or Batch and save yourself a surprise at the end of the month.
Have you tried the 90-minute timeout yet? Did the cost surprise you? Let me know in the comments.
If you care about AWS costs, you might also like my AWS FinOps agent build and my ECS vs EKS comparison.
Further reading
Top comments (0)