Lambda ephemeral storage cost is $0.0000000309 per GB-second for every MB you configure above the free 512 MB, charged for as long as the function runs (us-east-1, September 2026). A function set to 10,240 MB that runs 6 million seconds a month pays about $1.76 for it. To find these functions, list EphemeralStorage.Size above 512 and multiply by the CloudWatch Duration sum.
Every Lambda function gets 512 MB of /tmp for free, and you can raise it to 10,240 MB in 1 MB steps. People raise it for a video job or an ML model download, copy the setting into a template, and it spreads to functions that never write a file. This example is for engineers and FinOps reviewers who want a list of functions configured above 512 MB, an estimate of the Lambda ephemeral storage cost for each, and a safe way to bring it down.
The honest headline first: this charge is small next to memory. The script is still useful because it ranks the functions, shows which ones have measured /tmp use, and changes only the functions you name with --apply --names.
What does Lambda ephemeral storage cost?
Ephemeral storage is billed like memory: allocated size times run time, in GB-seconds. Only the part above 512 MB counts. As of September 2026, the AWS Price List for AWS Lambda in US East (N. Virginia), published 19 September 2026, gives these rates:
| Lambda charge (us-east-1) | Price | Unit |
|---|---|---|
| Ephemeral storage above 512 MB (x86_64 and arm64) | $0.0000000309 | per GB-second |
| Compute, x86_64, first 6 billion GB-seconds | $0.0000166667 | per GB-second |
| Compute, arm64, first 7.5 billion GB-seconds | $0.0000133334 | per GB-second |
| Requests | $0.20 | per million |
Worked example: a function with 10,240 MB of ephemeral storage and 2,048 MB of memory runs 2 million times a month for 3 seconds each. That’s 6,000,000 seconds. The billable storage is (10,240 − 512) / 1,024 = 9.5 GB, so 6,000,000 × 9.5 = 57,000,000 GB-seconds × $0.0000000309 = $1.76 a month. The same function’s compute is 6,000,000 × 2 GB × $0.0000166667 = $200.00. Storage is under 1% of the bill.
The AWS Lambda pricing page has its own example with 2,048 MB of storage that comes to $0.08 a month. So if Lambda is a big line on your bill, finding Lambda functions with too much memory and moving Lambda functions to arm64 Graviton come first. The ephemeral storage check is cleanup: cheap to run, and it finds settings that nobody can explain.
How can you tell whether a function uses its /tmp?
The standard AWS/Lambda metrics say nothing about disk. There are two ways to measure it:
- Lambda Insights. If the Lambda Insights extension is installed, CloudWatch has
tmp_usedandtmp_freein theLambdaInsightsnamespace, with afunction_namedimension. The script reads the maximum oftmp_usedover the window. Lambda Insights has its own CloudWatch charges, so don’t install it only for this. - A log line. Add one line to the handler that reports how full
/tmpis, run it through a normal week, then read the maximum with CloudWatch Logs Insights.
// handler.ts: log how much of /tmp the function used, once per invocation.
import { statfs } from "node:fs/promises";
async function work(event: unknown): Promise<{ ok: boolean }> {
// ... your existing handler code ...
return { ok: event !== undefined };
}
export const handler = async (event: unknown): Promise<{ ok: boolean }> => {
try {
return await work(event);
} finally {
const fs = await statfs("/tmp");
const usedMb = ((fs.blocks - fs.bfree) * fs.bsize) / 1_048_576;
console.log(JSON.stringify({ metric: "tmp_used_mb", value: Math.ceil(usedMb) }));
}
};
statfs comes from Node.js fs/promises, available since Node.js 18.17 (the Node.js fs.statfs documentation lists the fields). Used space is total blocks minus free blocks, times the block size. Because files left in /tmp stay there for later invocations in the same execution environment, measure at the end of the invocation, as above. Then read the largest tmp_used_mb value for the week with a CloudWatch Logs Insights query; the guide to running CloudWatch Logs Insights queries with SDK v3 shows how to do that from code.
What does the script do?
- Lists functions above 512 MB
paginateListFunctionsreturns every function with itsEphemeralStorage.Size; the script keeps the ones above 512. - Reads run timeOne
GetMetricDatacall per 500 queries for dailyInvocationsandDurationsums over 30 days, plus thetmp_usedmaximum where Lambda Insights exists. - Prices the extra storageTotal duration in seconds × (size − 512) / 1,024 × $0.0000000309, scaled to 30 days.
- Suggests a sizeOnly when
/tmpuse is measured: the peak plus 50%, rounded up to 64 MB, never below 512 or above today’s value. - Applies on requestWith
--apply --names, callsUpdateFunctionConfigurationfor those functions and waits withwaitUntilFunctionUpdatedV2. Unmeasured functions are skipped unless you pass an explicit--size.
Prerequisites
- Node.js 18.17 or later with
tsx,@aws-sdk/client-lambdaand@aws-sdk/client-cloudwatch. - A read-only profile for the report; connecting AWS profiles, SSO and assumed roles covers the setup. Use a separate role for
--apply. - At least a week of traffic, so monthly jobs don’t look idle. For functions that barely run, counting Lambda invocations for the last 24 hours is a quick first look.
Which IAM permissions does it need?
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadFunctionsAndMetrics",
"Effect": "Allow",
"Action": ["lambda:ListFunctions", "cloudwatch:GetMetricData"],
"Resource": "*"
},
{
"Sid": "LowerEphemeralStorageOnlyWithApply",
"Effect": "Allow",
"Action": ["lambda:UpdateFunctionConfiguration", "lambda:GetFunction"],
"Resource": "arn:aws:lambda:*:123456789012:function:*"
}
]
}
Drop the second statement for report-only runs; lambda:GetFunction is there because the waiter polls it. ListFunctions and GetMetricData don’t support resource-level permissions. To derive a policy from your own version of the script, paste it into the IAM policy generator for TypeScript code.
The script to find extra Lambda ephemeral storage
// find-lambda-extra-ephemeral-storage.ts
// Lists Lambda functions configured with more than the free 512 MB of /tmp, estimates what the extra
// ephemeral storage costs from CloudWatch Duration, and reads Lambda Insights tmp_used where it exists.
// Report only by default. With --apply --names it lowers ephemeral storage on the functions you name.
// Usage: npx tsx find-lambda-extra-ephemeral-storage.ts [--regions us-east-1,eu-west-1] [--days 30]
// npx tsx find-lambda-extra-ephemeral-storage.ts --regions us-east-1 --apply --names thumbnailer,etl-loader [--size 512]
import {
LambdaClient,
paginateListFunctions,
UpdateFunctionConfigurationCommand,
waitUntilFunctionUpdatedV2,
type FunctionConfiguration,
} from "@aws-sdk/client-lambda";
import { CloudWatchClient, paginateGetMetricData, type MetricDataQuery } from "@aws-sdk/client-cloudwatch";
const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
const i = args.indexOf(name);
return i >= 0 ? args[i + 1] : undefined;
};
const regions = (flag("--regions") ?? process.env.AWS_REGION ?? "us-east-1").split(",").map((r) => r.trim()).filter(Boolean);
const days = Math.min(Math.max(Number(flag("--days") ?? 30), 7), 63);
const apply = args.includes("--apply");
const applyNames = new Set((flag("--names") ?? "").split(",").map((n) => n.trim()).filter(Boolean));
const forcedSize = flag("--size") ? Number(flag("--size")) : undefined;
// us-east-1, AWS Price List for AWS Lambda published 19 September 2026: the same rate for x86_64 and arm64.
const FREE_MB = 512;
const PRICE_PER_GB_SECOND = 0.0000000309;
const HEADROOM = 1.5; // keep 50% above the largest /tmp use seen
interface Row {
Function: string;
TmpMB: number;
Invocations: number;
AvgMs: number;
PerMonth: string;
TmpUsedMaxMB: number | string;
SuggestMB: number | string;
SavePerMonth: string;
Note: string;
cost: number;
save: number;
suggest?: number;
}
interface Usage {
invocations: number;
durationMs: number;
tmpUsedBytes?: number;
}
const errorText = (err: unknown): string => (err instanceof Error ? `${err.name}: ${err.message}` : String(err));
async function readUsage(cw: CloudWatchClient, names: string[]): Promise<Map<string, Usage>> {
const end = new Date();
const start = new Date(end.getTime() - days * 86_400_000);
const metric = (id: string, ns: string, metricName: string, dim: string, fn: string, stat: string): MetricDataQuery => ({
Id: id,
MetricStat: { Metric: { Namespace: ns, MetricName: metricName, Dimensions: [{ Name: dim, Value: fn }] }, Period: 86_400, Stat: stat },
});
const queries = names.flatMap((fn, i) => [
metric(`i${i}`, "AWS/Lambda", "Invocations", "FunctionName", fn, "Sum"),
metric(`d${i}`, "AWS/Lambda", "Duration", "FunctionName", fn, "Sum"),
// Only present when the Lambda Insights extension is installed on the function.
metric(`t${i}`, "LambdaInsights", "tmp_used", "function_name", fn, "Maximum"),
]);
const out = new Map<string, Usage>(names.map((n) => [n, { invocations: 0, durationMs: 0 }]));
for (let i = 0; i < queries.length; i += 500) {
const pages = paginateGetMetricData({ client: cw }, { MetricDataQueries: queries.slice(i, i + 500), StartTime: start, EndTime: end });
for await (const page of pages) {
for (const r of page.MetricDataResults ?? []) {
const m = /^([idt])(\d+)$/.exec(r.Id ?? "");
const values = r.Values ?? [];
if (!m || values.length === 0) continue;
const usage = out.get(names[Number(m[2])]);
if (!usage) continue;
if (m[1] === "i") usage.invocations += values.reduce((a, b) => a + b, 0);
if (m[1] === "d") usage.durationMs += values.reduce((a, b) => a + b, 0);
if (m[1] === "t") usage.tmpUsedBytes = Math.max(usage.tmpUsedBytes ?? 0, ...values);
}
}
}
return out;
}
function toRow(fn: FunctionConfiguration, usage: Usage | undefined): Row {
const size = fn.EphemeralStorage?.Size ?? FREE_MB;
const u = usage ?? { invocations: 0, durationMs: 0 };
const extraGb = (size - FREE_MB) / 1024;
// GB-seconds of extra storage over the window, scaled to 30 days.
const cost = (u.durationMs / 1000) * extraGb * PRICE_PER_GB_SECOND * (30 / days);
const row: Row = {
Function: fn.FunctionName ?? "",
TmpMB: size,
Invocations: Math.round(u.invocations),
AvgMs: u.invocations > 0 ? Math.round(u.durationMs / u.invocations) : 0,
PerMonth: `$${cost.toFixed(2)}`,
TmpUsedMaxMB: "-",
SuggestMB: "-",
SavePerMonth: "$0.00",
Note: "",
cost,
save: 0,
};
if (u.invocations === 0) {
row.Note = `no invocations in ${days} days: costs nothing until it runs`;
return row;
}
if (u.tmpUsedBytes === undefined) {
row.Note = "no Lambda Insights data: log /tmp use before lowering";
return row;
}
const usedMb = u.tmpUsedBytes / 1_048_576;
row.TmpUsedMaxMB = Math.ceil(usedMb);
const suggest = Math.min(Math.max(Math.ceil((usedMb * HEADROOM) / 64) * 64, FREE_MB), size);
row.SuggestMB = suggest;
if (suggest < size) {
row.suggest = suggest;
row.save = (cost * (size - suggest)) / (size - FREE_MB);
row.SavePerMonth = `$${row.save.toFixed(2)}`;
} else row.Note = "uses what it has: keep";
return row;
}
async function applyChanges(lambda: LambdaClient, rows: Row[]): Promise<void> {
for (const name of applyNames) {
const row = rows.find((r) => r.Function === name);
if (!row) continue;
const target = row.suggest ?? forcedSize;
if (target === undefined || target >= row.TmpMB) {
console.log(`${name}: skipped (no measured /tmp use; pass --size after you have measured it)`);
continue;
}
try {
await lambda.send(new UpdateFunctionConfigurationCommand({ FunctionName: name, EphemeralStorage: { Size: target } }));
await waitUntilFunctionUpdatedV2({ client: lambda, maxWaitTime: 300 }, { FunctionName: name });
console.log(`${name}: ephemeral storage ${row.TmpMB} MB -> ${target} MB`);
} catch (err) {
console.error(`${name}: ${errorText(err)}`);
}
}
}
async function scanRegion(region: string): Promise<void> {
const lambda = new LambdaClient({ region });
const fns: FunctionConfiguration[] = [];
for await (const page of paginateListFunctions({ client: lambda }, {})) {
for (const fn of page.Functions ?? []) {
if ((fn.EphemeralStorage?.Size ?? FREE_MB) > FREE_MB) fns.push(fn);
}
}
if (fns.length === 0) {
console.log(`${region}: every function uses the free 512 MB of /tmp`);
return;
}
const usage = await readUsage(new CloudWatchClient({ region }), fns.map((f) => f.FunctionName ?? ""));
const rows = fns.map((f) => toRow(f, usage.get(f.FunctionName ?? ""))).sort((a, b) => b.cost - a.cost);
console.log(`\n${region}: functions with more than 512 MB of ephemeral storage (last ${days} days, cost scaled to 30 days)`);
console.table(rows.map(({ cost: _cost, save: _save, suggest: _suggest, ...visible }) => visible));
const total = rows.reduce((sum, r) => sum + r.cost, 0);
const saving = rows.reduce((sum, r) => sum + r.save, 0);
console.log(`Extra ephemeral storage: about $${total.toFixed(2)} a month at us-east-1 prices; the suggested sizes save $${saving.toFixed(2)}.`);
if (apply) await applyChanges(lambda, rows);
}
async function main(): Promise<void> {
if (apply && applyNames.size === 0) {
console.error("--apply needs --names fn1,fn2 (the script never changes functions you didn't name)");
process.exit(2);
}
if (forcedSize !== undefined && !(Number.isInteger(forcedSize) && forcedSize >= 512 && forcedSize <= 10_240)) {
console.error("--size must be a whole number of MB between 512 and 10240");
process.exit(2);
}
for (const region of regions) {
try {
await scanRegion(region);
} catch (err) {
console.error(`${region}: ${errorText(err)}`);
}
}
}
main().catch((err) => {
console.error(errorText(err));
process.exit(1);
});
Both paginators follow continuation tokens for you; the guide to AWS SDK v3 paginators explains the pattern.
How do you run it?
npm install @aws-sdk/client-lambda @aws-sdk/client-cloudwatch
npm install --save-dev tsx typescript @types/node
# Report only
AWS_PROFILE=readonly npx tsx find-lambda-extra-ephemeral-storage.ts --regions us-east-1,eu-west-1
# Lower a measured function; set an unmeasured one to 1024 MB after checking its logs
AWS_PROFILE=lambda-admin npx tsx find-lambda-extra-ephemeral-storage.ts --regions us-east-1 --apply --names thumbnailer
AWS_PROFILE=lambda-admin npx tsx find-lambda-extra-ephemeral-storage.ts --regions us-east-1 --apply --names etl-loader --size 1024
Sample output
us-east-1: functions with more than 512 MB of ephemeral storage (last 30 days, cost scaled to 30 days)
┌─────────┬───────────────────┬───────┬─────────────┬────────┬──────────┬──────────────┬───────────┬──────────────┬──────────────────────────────────────────────────────────┐
│ (index) │ Function │ TmpMB │ Invocations │ AvgMs │ PerMonth │ TmpUsedMaxMB │ SuggestMB │ SavePerMonth │ Note │
├─────────┼───────────────────┼───────┼─────────────┼────────┼──────────┼──────────────┼───────────┼──────────────┼──────────────────────────────────────────────────────────┤
│ 0 │ 'video-transcode' │ 10240 │ 400000 │ 45000 │ '$5.28' │ 6349 │ 9536 │ '$0.38' │ '' │
│ 1 │ 'thumbnailer' │ 4096 │ 2000000 │ 800 │ '$0.17' │ 150 │ 512 │ '$0.17' │ '' │
│ 2 │ 'etl-loader' │ 2048 │ 3000 │ 120000 │ '$0.02' │ '-' │ '-' │ '$0.00' │ 'no Lambda Insights data: log /tmp use before lowering' │
│ 3 │ 'old-report' │ 10240 │ 0 │ 0 │ '$0.00' │ '-' │ '-' │ '$0.00' │ 'no invocations in 30 days: costs nothing until it runs' │
└─────────┴───────────────────┴───────┴─────────────┴────────┴──────────┴──────────────┴───────────┴──────────────┴──────────────────────────────────────────────────────────┘
Extra ephemeral storage: about $5.47 a month at us-east-1 prices; the suggested sizes save $0.56.
thumbnailer: ephemeral storage 4096 MB -> 512 MB
etl-loader: skipped (no measured /tmp use; pass --size after you have measured it)
This run used mocked Lambda and CloudWatch responses. video-transcode writes 6.2 GB to /tmp, so it keeps most of its 10 GB and the saving is $0.38. thumbnailer never used more than 150 MB and drops to 512 MB. etl-loader has no Lambda Insights, so the script skipped it even though it was named. old-report hasn’t run in 30 days; it costs nothing, and finding unused Lambda functions is the better report for it.
When is lowering ephemeral storage worth it?
- Lower it when it’s measured and unused. A function that peaks at 150 MB doesn’t need 4 GB. The saving is small, but the setting stops misleading the next reader.
- Leave it when the job really needs disk. ETL jobs, ML inference, media and image processing are the use cases AWS lists. When
/tmpfills up, file writes fail, and a failed invocation costs more than the storage. - Fix the source, not only the function. If the value came from a shared SAM, CDK or Terraform module, a console change is overwritten on the next deploy. Change the
EphemeralStorageproperty in the template. - Look at the bigger levers at the same time. A function with oversized storage often has an oversized timeout too; finding Lambda timeouts set too high and finding unused provisioned concurrency cover those.
Troubleshooting
- Every function says “no Lambda Insights data”. Expected unless the extension is installed. Add the log line above, wait a week, and pass
--sizewith the value you measured. ResourceConflictExceptionon apply. The API reference says it means another operation is in progress, usually a deploy. Wait for it to finish and rerun.- The estimate is lower than Cost Explorer.
Durationmay not include all billed init time, and the price used is us-east-1’s. Treat the figure as a ranking, not an invoice. - Access denied. Compare the role with the policy above, then work through troubleshooting AWS IAM access denied errors.
Ask ChatWithCloud instead
For a quick look, ask ChatWithCloud “Which Lambda functions have more than 512 MB of ephemeral storage, and how long did they run last month?” It writes AWS SDK for JavaScript v2 code, runs it on your machine with your profile and summarizes the result; how ChatWithCloud runs AWS queries locally explains the loop. It can be wrong and runs changes without a confirmation step, so use a read-only profile and change function settings yourself. When the question is about failures rather than cost, asking AI about Lambda errors is the better starting point. More scripts live in the AWS practical examples library.
Frequently asked questions
How much does Lambda ephemeral storage cost?
In us-east-1, $0.0000000309 per GB-second for the storage configured above 512 MB, as of September 2026. The first 512 MB is included at no extra cost.
What is the maximum /tmp size in Lambda?
10,240 MB. You can set any value from 512 MB to 10,240 MB in 1 MB increments.
Is there a CloudWatch metric for Lambda /tmp usage?
Not in the standard AWS/Lambda namespace. Lambda Insights publishes tmp_used and tmp_free; otherwise log the value from your handler.
Does /tmp persist between Lambda invocations?
Within one execution environment, yes, so clean up large files. Across environments, no: never rely on it as storage.
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud