The function worked for a year. It picked up files from S3 each night, processed them, wrote the results to Redshift. Then a customer onboarded, the nightly batch went from 500 files to 5,000, and the logs filled with this:
Task timed out after 900.02 seconds
There is no configuration change that fixes an AWS Lambda 15 minute timeout. It is a hard quota, it has not moved since 2018, and support cannot raise it for you.
What there is, instead, is three different fixes for three different problems that all produce that same line. Most of the advice you will find picks one and presents it as the answer. That is why so much of it does not work.
The limit is real and it is not moving
Lambda's maximum timeout went from 5 minutes to 15 on 10 October 2018, and the release note is blunt about the reason: to allow for long-running functions. It has stayed there since. You can set any value between 1 second and 900 seconds, and 900 is the ceiling.
This is a hard quota rather than a soft one, which matters more than it sounds. Soft quotas are account limits you can raise by opening a support ticket. Hard quotas are properties of the service. There is no ticket to open here, and every hour spent looking for one is an hour not spent on the actual fix.
So the question is not how to get more time. It is what to do with a job that needs more time than the service gives.
First, work out why you ran out of time
This is the step almost every article skips, and skipping it is why people end up with the wrong architecture.
Ask one question:
Are you out of time because you are computing, or because you are waiting?
Those look identical in CloudWatch. A function that spends 14 minutes crunching a dataframe and a function that spends 14 minutes polling another service for a result both show up as long-running invocations. They need completely different answers.
| Why you ran out | What the job looks like | The fix |
|---|---|---|
| Computing | CPU pinned, one indivisible chunk of work | Fargate, AWS Batch or Glue |
| Waiting | Idle most of the time, polling or awaiting a callback | Durable functions |
| Volume | Fast per item, thousands of items | Chunk it, fan out with Distributed Map |
Get this wrong and you pay for it literally. Putting a waiting job on Fargate means paying for a container to sit idle for three hours. Putting a computing job on durable functions means discovering, after you have rewritten it, that each invocation still dies at 900 seconds.
If you are waiting: durable functions
This is the newest answer and the one most existing advice predates, so it is worth being precise about what it actually does.
AWS announced Lambda durable functions on 2 December 2025. They launched in 14 regions, and 16 more were added in April 2026. The SDK covers JavaScript, TypeScript, Python and Java.
A durable function checkpoints its progress as it goes. When it hits a wait, Lambda suspends the execution and stops the clock. When the wait is satisfied, whether that is a timer, a callback or an external event, Lambda invokes your function again, replays the checkpoint log to skip everything already done, and carries on from where it paused. Invoked asynchronously, a single durable execution can span up to a year. AWS is equally direct about the billing: waits "suspend execution without incurring compute charges", so the clock stops on the invoice as well as on your code.
For a job that polls a long-running export, waits on a human approval, or sits on an external system's callback, this is the right tool and it did not exist eighteen months ago.
Three things will bite you, and none of them are in the announcement post.
Your handler has to be deterministic. Durable functions replay your code from the top on every resume. Anything inside a completed step returns its checkpointed result without re-running. Anything outside a step runs again, every single time. So a uuid4() or a datetime.now() in your handler body produces a different value on replay, and if you branch on it, the replay takes a different path than the original did. Wrap non-deterministic work in a step and branch on the step's return value.
Pin your versions. If an execution is suspended for three days waiting on an approval and you deploy in the meantime, the replay runs your new code against the old checkpoint log. Point functions at a pinned version or alias, not $LATEST.
It is cheaper, not free. You genuinely are not charged for duration while a function is suspended on a wait, which is the headline benefit. But durable operations are billed at $8.00 per million, the data those checkpoints write at $0.25/GB, and retention at $0.15/GB-month with a 14-day default. A chatty workflow that checkpoints constantly is not free just because it is idle. Model it before you commit.
If it is volume: chunk it and fan out
If each item is fast and there are simply too many, you do not need a longer timeout. You need more Lambdas.
Step Functions Distributed Map is the native way to do this. It reads the item list straight from S3, an object listing, a CSV or a JSON array, and if you leave MaxConcurrency unset it runs 10,000 child executions in parallel. That is a default rather than a ceiling, and AWS warns against raising it past whatever your downstream service can absorb. Each child processes its slice inside a normal 15-minute invocation. The 5,000-file batch that timed out as one job finishes in the time the slowest single file takes.
A few limits worth knowing before you design around it:
- The input passed to each child execution is capped at 256 KiB. Pass an S3 pointer, not the data.
- Reading items from a file, Distributed Map stops after the first 100 million lines.
- The
ItemReaderandResultWriterbuckets must be in the same account and region as the state machine.
I have built this shape of fix outside Lambda too, and the lesson transfers cleanly. On a verification pipeline that was capped near 40,000 records a day, the bottleneck was a heavy browser session running synchronously per record. Splitting the work into queued units and putting a fleet of stateless workers behind it took sustained throughput past two million records a day. The queue was RabbitMQ rather than Step Functions, but the move is identical: stop asking one process to finish everything, and make the unit of work small enough that no single unit can run out of time.
That reframing is the actual win. The timeout stops being a ceiling and becomes a constraint on chunk size.
If it is compute: stop using Lambda
Sometimes the honest answer is that you picked the wrong service, and a fair amount of writing on this topic will bend over backwards to avoid saying so.
If a job needs 40 minutes of continuous CPU on data it cannot split, there is no serverless trick that fixes it. Move it.
| Move it to | When |
|---|---|
| AWS Fargate | You have containerised it already, or want to. No execution limit, pick your own CPU and memory. |
| AWS Batch | Queued, scheduled or array jobs where you want retries and job dependencies handled for you. |
| AWS Glue | The work is a data transform. Spark, job bookmarks and schema handling come free, and a Glue job runs up to 48 hours. |
The rule I use on data pipelines: if the function is processing more than about a gigabyte, or reliably takes more than a couple of minutes, it is a Glue job wearing a Lambda costume. Price the move before you make it, because Glue bills by DPU-hour and the arithmetic is not obvious: the Glue cost calculator takes worker type, count and runtime and gives you the monthly figure. Lambda is at its best on short, event-driven work. Arrival, validation, routing, small transforms.
That split is what I run on a pipeline that moves millions of records a day: Lambda handles files landing and being validated, the heavy transforms run in Glue, and neither is asked to do the other's job.
Worth reading the cost signal too. A function pinned at 15 minutes is billed for GB-seconds the entire time, and for long-running work that is usually more expensive than the container you were avoiding. The same arithmetic that drives Glue cost optimization applies here, just in the other direction.
The pattern that looks clever and is not
Somewhere in your search results, someone will suggest having the Lambda invoke itself before it times out, passing along its progress so the next invocation picks up where it left off.
Do not do this.
It has no durable state, so progress lives in whatever you remembered to pass forward. It has no retry semantics, so a failed link in the chain just stops. It has no visibility, so debugging means reconstructing the sequence from CloudWatch. And the failure mode is expensive: a bug in the termination condition gives you a function that invokes itself forever, and you find out from the bill.
Durable functions are what this pattern was reaching for, done properly, with checkpointing and replay handled by the service. If you wrote self-invocation before December 2025, it was a reasonable hack. It is not one now.
What actually happens when a function times out
Worth knowing, because it changes what you have to clean up.
When the timeout hits, the invocation is terminated. Your code does not get a callback, a signal or a chance to finish the transaction it was halfway through. There is no graceful shutdown.
That means partial work stays partial. Rows already written are still written. A file half-uploaded to S3 is still sitting there. If the trigger retries the invocation, and asynchronous invocations retry twice by default, the whole thing runs again from the top, on top of whatever the first attempt left behind.
The practical defence is to know how much time you have left. Every runtime exposes it:
def handler(event, context):
for record in records:
if context.get_remaining_time_in_millis() < 30_000:
# Stop cleanly, checkpoint what is done, hand the rest on
return {"processed": done, "remaining": queue}
process(record)Reserving the last 30 seconds to exit cleanly turns a hard kill into a controlled handover. It also makes the job restartable, which matters much more than it sounds once retries are in play.
The deeper fix is idempotency, so a rerun cannot double-write. That is its own topic and I will cover it properly, but the short version is that every write should carry a key that makes replaying it harmless.
Frequently asked questions
Can an AWS Lambda function run longer than 15 minutes?
No. A single Lambda invocation is capped at 900 seconds and this is a hard quota that AWS support cannot raise. The maximum has been 15 minutes since October 2018. Work that needs more continuous runtime belongs on AWS Fargate, AWS Batch or AWS Glue, all of which allow far longer executions.
Do Lambda durable functions remove the 15-minute timeout?
No. Durable functions leave the per-invocation limit at 15 minutes. What they add is checkpointing and suspension: an execution can pause on a wait, stop incurring duration charges, then resume in a later invocation with completed steps replayed from the checkpoint log. A durable execution invoked asynchronously can span up to a year, but no single stretch of your code runs longer than 900 seconds.
How do I process a file that takes longer than 15 minutes in Lambda?
Split it. If the file contains independent records, chunk it and process the pieces in parallel with Step Functions Distributed Map, which defaults to 10,000 concurrent child executions reading their work list from S3. If the transform cannot be split, such as a sort or an aggregation over the whole file, move the job to AWS Glue or Fargate rather than trying to fit it into Lambda.
What happens to my data when a Lambda times out mid-execution?
The invocation is terminated with no graceful shutdown, so partial writes remain written and open work is abandoned. Asynchronous invocations then retry twice by default, re-running the function from the start on top of that partial state. Guard against it by checking context.get_remaining_time_in_millis() and exiting cleanly before the deadline, and by making writes idempotent so a rerun cannot duplicate them.
Is Fargate or Step Functions better for a long-running Lambda job?
It depends on why the job is long. If it needs continuous compute on data that cannot be divided, Fargate is right because it has no execution limit. If it is long because it processes many independent items, Step Functions Distributed Map is better, because it keeps each unit inside a fast Lambda and parallelises them. If it is long because it waits on external events, durable functions beat both, since you are not billed for the idle time.
Are Lambda durable functions free while suspended?
Not entirely. You are not charged for duration while an on-demand function is suspended on a wait, which is the main saving. You are still charged for durable operations at $8.00 per million, for data written by checkpoints at $0.25/GB, and for retention at $0.15/GB-month with a 14-day default retention period. A workflow that checkpoints heavily has a real cost even while idle.
The takeaway
Task timed out after 900.02 seconds is not one problem. It is three, and the fix only works if you match it to the right one.
Diagnose first. Computing, waiting, or too many items. A job that waits now has a native answer in durable functions, and that answer is genuinely new, but it does not extend how long your code can run and treating it as though it does will cost you a rewrite. A job that is simply large wants smaller chunks, not more time. And a job that needs 40 minutes of CPU needs a service that gives you 40 minutes of CPU.
The limit is worth treating as useful information rather than an obstacle. A function that keeps running out of time is telling you the unit of work is too big. That is usually true of the architecture as well, not just the function.
If you are staring at a pipeline where the timeouts keep coming back and you want a second opinion on where the work actually belongs, tell me what it is doing and I will tell you what I would move. For the neighbouring problem, when your Lambda package is too big to deploy covers the other limit that catches Python data jobs, and Dagster vs Airflow vs Prefect covers what to do once you have outgrown AWS-native orchestration entirely.
Mirza Hammad Tariq
Data & Automation Engineer with 5+ years on AWS: ETL pipelines, backend APIs and automation workflows in Python, SQL and FastAPI, built to cost less to run.