# AWS Lambda Configuration Explained: What to Care About (and Why)

- URL: https://karandeepsingh.ca/posts/lambda-configuration-explained/
- Author: Karandeep Singh
- Published: 2026-08-05
- Series: Serverless on AWS
- Tags: aws, lambda, serverless
- Summary: Most of AWS Lambda is configuration, not code, and the defaults aren't always right. This explains what each setting means, memory, timeout, concurrency, architecture, IAM, VPC, environment variables, and what to actually care about when you set them.

The strange thing about AWS Lambda is that most of it is **configuration, not code**. You write a handler, but memory, timeout, permissions, networking, and concurrency, the settings that decide whether it's fast, cheap, secure, and reliable, all live in a config panel. And the defaults aren't always right.

This is a plain-English tour of the settings that matter: what each one *means*, and what to actually care about when you set it. If you want the scaling-at-volume version afterwards, see [handling a million users an hour with Lambda](https://karandeepsingh.ca/posts/lambda-million-users-per-hour/).

**info:**
**Rule of thumb:** the code decides *what* your function does; the configuration decides *how well* it does it. Spend real thought on the config, not just the handler.

## First: is this even a job for Lambda?

Before configuring anything, sanity-check the fit.

- **Good fit:** short, event-driven, spiky, or bursty work, APIs, webhooks, file processing, scheduled jobs, glue between services.
- **Poor fit:** anything that runs **longer than 15 minutes** (the hard limit), needs to hold state in memory between requests, or runs at heavy, steady volume 24/7 (containers often win there).

Lambda is **stateless** and **ephemeral**, don't fight that. If you need it, that's a signal to look at ECS/Fargate instead.

## Memory: the setting that secretly controls everything

**What it means:** you set memory from 128 MB to 10 GB, but Lambda allocates **CPU in proportion to memory**. More memory = more CPU (and more network throughput).

_Diagram: There is no CPU setting. AWS documents one fixed point on this line — **at 1,769 MB a function gets the equivalent of one vCPU** — and CPU scales in proportion either side of it. Below that you are on a fraction of a core, which is why a starved function can cost *more*: it runs far longer. Above it, single-threaded code cannot use what you are buying._

**What to care about:** this is your main **performance and cost dial**, not just a RAM limit. A function starved at 128 MB may run slowly and actually cost *more* than the same function at 512 MB that finishes in a third of the time (you pay for GB-seconds = memory × duration). Don't guess, **tune it**: run the same workload at 256/512/1024 MB and compare cost and latency (the AWS Lambda Power Tuning tool automates this).

**tip:**
Counter-intuitive but true: **more memory can be cheaper.** If doubling memory more than halves the duration, your bill goes down and your function gets faster at the same time.

## Timeout: set it to reality, not the maximum

**What it means:** the maximum seconds an invocation may run (1 second to 15 minutes) before Lambda kills it.

**What to care about:** set it a little above your realistic **p99 duration**, not at the 15-minute max. A too-high timeout means a stuck invocation (a hung network call) burns money and a concurrency slot for 15 minutes instead of failing fast. Also align it with your callers: **API Gateway caps at 29 seconds**, so a 60-second Lambda behind it is pointless, the gateway gives up first.

## Ephemeral storage (/tmp): only if you touch files

**What it means:** scratch disk at `/tmp`, from 512 MB up to 10 GB.

**What to care about:** most functions never need more than the default. Raise it only if you download, generate, or process large files inside the function, and remember you **pay for the extra**. It's not persistent, it vanishes when the environment is recycled.

## Architecture: default to arm64 (Graviton)

**What it means:** you choose the CPU architecture, **arm64 (Graviton)** or **x86_64**.

**What to care about:** **arm64 is roughly 20% cheaper** and often as fast or faster. Default to it. The only reason to stay on x86 is a dependency or binary that isn't built for arm, which is increasingly rare.

## Runtime: pick for cold start and fit

**What it means:** the language/runtime (Node.js, Python, Go, Java, .NET, Rust, custom).

**What to care about:** it drives **cold-start time**. Node.js, Python, Go, and Rust start fast; Java/.NET start slower (pair them with SnapStart or provisioned concurrency). Pick the language your team is productive in *and* that starts fast on the hot path. For a worked Go example see [build and deploy a Go Lambda function](https://karandeepsingh.ca/posts/build-deploy-go-lambda-function/); for Python, [boto3 and Lambda](https://karandeepsingh.ca/posts/boto3-and-aws-lambda-a-match-made-in-serverless-heaven/).

## Concurrency: reserved vs provisioned (they're different)

This is the most misunderstood pair.

- **Reserved concurrency** — *caps and guarantees* a slice of your account's concurrency for this function. **What to care about:** use it to (a) stop one function from starving the rest of your account, and (b) protect a fragile downstream (e.g. limit a function to 10 concurrent so it can't overwhelm a small database).
- **Provisioned concurrency** — keeps N environments **pre-warmed**, so there's **no cold start**. **What to care about:** use it on latency-critical paths, but it **costs money even when idle**, so reserve it for endpoints where the cold-start tail actually hurts users.

**warning:**
Don't confuse them: **reserved** limits *how many* can run; **provisioned** removes the *cold start* for the ones that do. Reserved can even be set to zero to instantly disable a function.

## Environment variables: config yes, secrets no

**What it means:** key/value pairs passed to your function as config.

**What to care about:** use them for **non-secret** configuration (table names, feature flags, log levels). **Never put secrets** (DB passwords, API keys) in plain environment variables, they're visible to anyone who can read the function's configuration. Put secrets in **AWS Secrets Manager** or **SSM Parameter Store** (encrypted) and fetch them at runtime via the function's role.

## IAM execution role: least privilege, always

**What it means:** the IAM role that decides what AWS the function is *allowed to touch*.

**What to care about:** scope it tightly, grant only the specific actions on the specific resources the function needs (e.g. `dynamodb:PutItem` on one table), not `AmazonDynamoDBFullAccess`. The blast radius of a compromised or buggy function is exactly what this role permits. This is the single most important security setting on the function.

## VPC configuration: only when you truly need it

**What it means:** attach the function to your VPC so it can reach **private** resources (an RDS database, an internal service).

**What to care about:** don't put a function in a VPC "just because." A VPC-attached function **loses default internet access**, to call the public internet or AWS APIs it then needs a **NAT gateway** (extra cost) or VPC endpoints. Attach to a VPC **only** when you must reach private resources; otherwise leave it out and keep things simpler and cheaper.

## Async behaviour: retries and a DLQ

**What it means:** for asynchronous invocations (S3 events, SNS, EventBridge), Lambda **retries on failure** (twice by default) and can send failures to a **destination or Dead Letter Queue (DLQ)**.

**What to care about:** always configure an **on-failure destination or DLQ** so failed events aren't silently lost, and make your handler **idempotent**, because retries mean the same event can arrive more than once.

## Logging and tracing: turn them on, keep them tidy

**What it means:** Lambda writes to **CloudWatch Logs** automatically; **X-Ray** active tracing is a toggle.

**What to care about:** log **structured JSON** (queryable), set a **log retention** period (log groups default to *never expire*, which quietly costs money), and enable **X-Ray** so you can see where time goes across services when something's slow.

## Quick reference

| Setting | What it means | What to care about | Sensible start |
|---|---|---|---|
| **Memory** | RAM + proportional CPU | Your main speed/cost dial, tune it | 512 MB, then measure |
| **Timeout** | Max run time | Set near p99, mind API GW's 29 s | 10–30 s for APIs |
| **/tmp storage** | Scratch disk | Raise only for big files | 512 MB (default) |
| **Architecture** | CPU type | arm64 is ~20% cheaper | arm64 (Graviton) |
| **Runtime** | Language | Cold start + team fit | Node/Python/Go |
| **Reserved concurrency** | Caps/guarantees slots | Protect account & downstreams | Unset unless needed |
| **Provisioned concurrency** | Pre-warmed envs | Kills cold starts; costs when idle | Hot paths only |
| **Env variables** | Config to code | No secrets, ever | Non-secret config only |
| **IAM role** | What it can do | Least privilege | Scope to exact resources |
| **VPC** | Reach private resources | Adds NAT cost/complexity | Off unless needed |
| **DLQ/destination** | Failure handling | Don't lose failed events | Always set for async |

## Common mistakes

- **Leaving memory at 128 MB** and wondering why it's slow (and not cheaper).
- **Timeout at 15 minutes** so stuck calls burn money and slots.
- **Secrets in environment variables** instead of Secrets Manager/SSM.
- **`*FullAccess` execution roles** instead of least privilege.
- **VPC-attaching a function that doesn't need it**, then hitting "why can't it reach the internet?"
- **No DLQ** on async functions, so failures vanish silently.
- **Log groups with no retention**, a slow, quiet cost leak.

## Wrapping up

Lambda rewards a few minutes of deliberate configuration. Treat **memory** as a performance dial and tune it, keep **timeouts** realistic, default to **arm64**, keep **secrets out of env vars**, scope the **IAM role** tightly, stay **out of a VPC** unless you must, and always give async functions a **DLQ**. Get those right and the function panel stops being a mystery, it becomes the place you actually control cost, speed, and safety.

## References and Further Reading

- Amazon Web Services. [Configuring Lambda function memory](https://docs.aws.amazon.com/lambda/latest/dg/configuration-memory.html).
- Amazon Web Services. [Lambda concurrency: reserved and provisioned](https://docs.aws.amazon.com/lambda/latest/dg/lambda-concurrency.html).
- Amazon Web Services. [Using AWS Lambda with an Amazon VPC](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html).
- Amazon Web Services. [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html).

**Question:** Which Lambda setting have you most often seen left at a bad default, memory, timeout, or the IAM role?

## Quiz

1. On Lambda, what does increasing the memory setting also increase?
   a) Only the RAM available to your function
   b) The CPU allocated to your function (CPU scales with memory)
   c) The maximum timeout
   d) The number of concurrent executions allowed
   Answer: b. Lambda allocates CPU proportionally to memory. Raising memory gives your function more CPU, so a function can sometimes run faster and cost the same or less, because it finishes sooner. Memory is really a performance dial, not just a RAM dial.

2. Where should you store a database password used by a Lambda function?
   a) In a plain environment variable
   b) Hard-coded in the function
   c) In AWS Secrets Manager or SSM Parameter Store, fetched at runtime
   d) In the function's description field
   Answer: c. Plain environment variables are visible to anyone with read access to the function's configuration. Secrets belong in Secrets Manager or SSM Parameter Store (encrypted), fetched at runtime by the function's IAM role.


