All posts
ffmpeg aws lambda

FFmpeg on AWS Lambda: Limits, Layers and When to Offload

How to run FFmpeg on AWS Lambda with a layer or container image, the limits that break video jobs (15 minutes, /tmp, 6 MB payloads), and when to offload.

You can run FFmpeg on AWS Lambda: add a static FFmpeg binary as a layer (or use a container image), start it from your handler, and read and write files through S3. It works well for short jobs on small and medium files. For long encodes and large files, the 15-minute limit and the /tmp disk decide the design.

This guide shows the setup, the limits with their numbers from the AWS docs, the usual workarounds, and when it is simpler to send the job to an API. For a wider comparison, see how to run FFmpeg in the cloud.

The Lambda limits that matter for video

LimitValueWhy it matters
Timeout900 seconds (15 minutes)The whole job must finish in one invocation.
Memory128 MB to 10,240 MBCPU scales with memory: 1,769 MB is about one vCPU.
/tmp storage512 MB to 10,240 MBInput and output files on disk must fit here.
Request and response payload6 MB synchronous, 1 MB asynchronousYou cannot send the video in the request.
Deployment package250 MB unzipped, including layersA full FFmpeg build uses a large part of it.
Container image10 GBThe way around the package limit.

Source: AWS Lambda quotas, checked on 16 October 2026.

Set up FFmpeg in a Lambda function

1. Add the binary. Download a static Linux FFmpeg build for your function’s architecture (x86_64 or arm64), put it in a zip as bin/ffmpeg, and publish it as a layer. Lambda extracts layers to /opt, so the binary is at /opt/bin/ffmpeg. For a larger build, use a container image and install FFmpeg in the Dockerfile.

2. Pass a URL, not the file. Upload the video to S3 first and give the function a presigned GET URL. FFmpeg can read HTTPS input directly, so the input does not need to go to /tmp.

3. Write to /tmp, then upload. Most output formats, MP4 included, need a seekable file. Write the output to /tmp and upload it to S3 when FFmpeg ends.

index.mjs (Node.js 20)
import { spawn } from 'node:child_process'
import { createReadStream } from 'node:fs'
import { unlink } from 'node:fs/promises'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
const s3 = new S3Client({})
function run(args) {
return new Promise((resolve, reject) => {
const ff = spawn('/opt/bin/ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] })
let log = ''
ff.stderr.on('data', (chunk) => (log = (log + chunk).slice(-4000)))
ff.on('error', reject)
ff.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`ffmpeg exited ${code}: ${log}`))))
})
}
export const handler = async ({ inputUrl, bucket, key }) => {
const out = '/tmp/output.mp4'
try {
await run(['-y', '-i', inputUrl, '-vf', 'scale=-2:720', '-c:v', 'libx264', '-crf', '23', '-c:a', 'aac', out])
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: createReadStream(out) }))
return { bucket, key }
} finally {
await unlink(out).catch(() => {})
}
}

Delete files in /tmp when you are done. A warm Lambda environment keeps /tmp between invocations, so old outputs can fill the disk.

Where it breaks

  • Long encodes. A 1080p re-encode of a long recording can need more than 15 minutes, even at high memory. The invocation ends and the work is lost.
  • Large files. An input you download plus the output you write must fit in /tmp. Reading the input from a URL helps; a large output still needs the disk.
  • Uploads from users. A browser cannot post a large video to the function, because the request body is at most 6 MB. Use presigned S3 uploads.
  • Package size. A static build with many codecs, plus your code and dependencies, can pass 250 MB unzipped.
  • Retries. An asynchronous invocation that fails is retried, so a long job that times out can run again and time out again. Make the handler idempotent.

Workarounds people use

Container images. Up to 10 GB, so the FFmpeg build is no longer a problem. The 15-minute timeout and the /tmp limit stay the same.

Split and join. Cut the input into segments, encode them in parallel invocations (often with Step Functions), and join them with the concat demuxer. This gets around the timeout and can finish sooner, but you now build and debug an orchestration: segment boundaries on keyframes, audio sync, retries per segment, and the final join.

Move long jobs to a container service. ECS on Fargate or AWS Batch has no 15-minute limit. You keep Lambda for short jobs and send long ones to a queue that containers read. This works, but it is two systems to operate.

AWS Elemental MediaConvert. A managed AWS service for transcoding with job templates. It does not take FFmpeg commands, so filters and flags you already have must be mapped to its settings.

Or send the job to an API

If Lambda is where your code runs but not where you want FFmpeg to run, keep the Lambda and remove the binary. The function sends one small JSON request and returns at once. Xora runs FFmpeg with the same arguments and calls your webhook when the job ends.

index.mjs
export const handler = async ({ inputUrl }) => {
const res = await fetch('https://api.xora.sh/v1/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.XORA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
mode: 'ffmpeg',
input_files: { in_1: inputUrl },
output_files: { out_1: 'output_720p.mp4' },
ffmpeg: {
args: ['-i', '{{in_1}}', '-vf', 'scale=-2:720', '-c:v', 'libx264', '-crf', '23', '-c:a', 'aac', '{{out_1}}'],
},
webhookUrl: process.env.XORA_WEBHOOK_URL,
}),
})
if (!res.ok) throw new Error(`Xora: ${await res.text()}`)
return res.json() // { id, state: "queued" }
}

The function finishes in the time one HTTP request takes, so the timeout, /tmp and the package size no longer apply to the video work. Point webhookUrl at a second function (a Lambda function URL works) that reads data.jobId and data.state from the webhook and stores the output link. Credits are billed for machine time and output size; failed and rejected jobs cost nothing. See pricing.

Next steps

Frequently asked questions

Can AWS Lambda run FFmpeg?

Yes. Add a static FFmpeg binary as a Lambda layer, or build a container image that includes FFmpeg. Your handler then starts the binary as a child process. The job must finish inside the function's limits: at most 15 minutes, up to 10,240 MB of memory and up to 10,240 MB of /tmp storage.

What is the maximum video size Lambda can process?

There is no fixed file limit, but the input and output must fit in /tmp (512 MB by default, configurable up to 10,240 MB) unless FFmpeg reads the input from a URL and streams the output. The job must also finish within 15 minutes. You cannot send the video in the request body, because a synchronous payload is at most 6 MB.

Why does my FFmpeg Lambda time out?

Encoding speed depends on CPU, and Lambda gives CPU in proportion to memory: at 1,769 MB a function has the equivalent of one vCPU. Raise the memory setting, use a faster preset, cut the job into segments, or move long jobs to a container service or an FFmpeg API.

Should I use a layer or a container image for FFmpeg?

A layer is simpler if your code and FFmpeg fit in 250 MB unzipped, the limit for a function and all its layers. A container image can be up to 10 GB, so it is the better choice for a full FFmpeg build with many codecs.

Ready to try Xora?

Replace your local FFmpeg binaries with a secure, high-performance cloud API.