+1 (726) 207-9872

Cutting Your Serverless Bill: Lambda Billing in 2026 and Where the Money Actually Goes

Serverless bills are easy to ignore until they are not. The invoice arrives as a hundred small line items -- Lambda GB-seconds, API Gateway requests, CloudWatch Logs ingestion, NAT Gateway data processing, DynamoDB reads -- and no single one looks big enough to investigate. This post is the cost review we run on Serverless Framework projects: what AWS actually bills you for, where the money usually goes, and the serverless.yml changes that cut the invoice without changing behaviour.

Everything here assumes Serverless Framework V4 on AWS. Prices quoted are us-east-1 list prices and are there to show the shape of the arithmetic -- check the current pricing pages before you build a business case.

What Lambda actually bills

Three components:

  1. Requests -- about $0.20 per million invocations.
  2. Duration -- GB-seconds: configured memory multiplied by billed time, rounded to the millisecond. Roughly $0.0000166667 per GB-second on x86 and about 20% less on arm64 (Graviton2).
  3. Storage above the free ephemeral 512 MB, plus Provisioned Concurrency and SnapStart charges if you use them.

Two changes are worth knowing about because they invalidate older advice:

  • The INIT phase is billed. Since August 2025, initialization time for on-demand invocations on managed runtimes is included in billed duration. It used to be free. If you have a function with a 1.5-second init and a 50 ms handler, your bill for that function just became mostly cold start. This makes the bundle-trimming work in our cold start post a cost lever, not only a latency lever.
  • Duration pricing is tiered. Above roughly 6 billion GB-seconds per month the rate drops, and again above 15 billion. Almost nobody reaches this, but if you are consolidating accounts it is worth knowing the tiers apply per payer account.

The practical consequence: your Lambda cost is invocations x memory x duration. There are exactly three dials, and two of them are under your control in serverless.yml.

Step 1: Find out where the money is before touching anything

Do not optimize from intuition. Tag everything, then read the bill.

provider:
  name: aws
  stackTags:            # applied to every resource in the stack
    project: checkout
    stage: ${sls:stage}
    owner: platform-team
  tags:                 # applied to the Lambda functions
    project: checkout
    stage: ${sls:stage}

Activate project and stage as cost allocation tags in the Billing console (they take up to 24 hours to appear), then group by them in Cost Explorer. Filter to a single service and group by USAGE_TYPE to see whether Lambda spend is duration, requests, or provisioned concurrency.

For per-function attribution, CloudWatch metrics get you most of the way. This Logs Insights query ranks functions by GB-seconds over the last week:

filter @type = "REPORT"
| stats sum(@billedDuration * @memorySize / 1024 / 1000) as gbSeconds,
        count(*) as invocations,
        avg(@billedDuration) as avgMs
  by @log
| sort gbSeconds desc
| limit 20

In most reviews the top three functions are 80% of the compute bill, and one of them is doing something nobody remembered was still running.

Step 2: Switch to arm64

The single highest-return line change in a serverless project:

provider:
  architecture: arm64

Graviton pricing is roughly 20% lower per GB-second, and CPU-bound workloads frequently run faster as well, so the real saving is often larger. The caveats are narrow: native binaries (sharp, bcrypt, anything with a .node addon) must be built for ARM, and a handful of Lambda layers are x86-only. Build in CI on an ARM runner or with a container image, deploy to a staging stage, and compare Duration before promoting.

Step 3: Right-size memory in both directions

Memory is the multiplier on every billed millisecond, but less memory is not automatically cheaper -- Lambda scales CPU with memory, so an under-provisioned function pays for its own slowness. Run AWS Lambda Power Tuning with the cost optimization strategy and set the result per function rather than a blanket provider default:

provider:
  memorySize: 512          # sane default

functions:
  thumbnail:
    handler: src/thumbnail.handler
    memorySize: 2048       # CPU-bound: faster AND cheaper here
  webhookAck:
    handler: src/webhook.handler
    memorySize: 256        # I/O-bound, does nothing but write to SQS

Two related settings people forget:

functions:
  reportBuilder:
    timeout: 30            # not the 900s someone set while debugging
    ephemeralStorageSize: 512

A long timeout costs nothing by itself, but it is what turns a hung HTTP call into 15 minutes of billed duration multiplied by every retry.

Step 4: Stop paying for logs you never read

On many small projects CloudWatch Logs costs more than Lambda. Ingestion is around $0.50 per GB, storage around $0.03 per GB-month, and the default retention in a Serverless Framework project is never expire.

Set retention on every log group:

provider:
  logRetentionInDays: 14   # V4 supports this at provider level

Then reduce what you write:

  • Use a structured logger with a level controlled by an environment variable, and run production at WARN or INFO -- not DEBUG. Powertools for AWS Lambda reads POWERTOOLS_LOG_LEVEL and supports percentage-based debug sampling so you keep a few verbose traces without paying for all of them.
  • Turn off API Gateway execution logging and full request/response logging in production; access logs in a compact JSON format are enough.
  • Never console.log an entire event or SDK response in a hot path. One 8 KB event logged on 50 million invocations a month is roughly 400 GB of ingestion.
  • For logs you must retain but rarely query, Lambda's Infrequent Access log class halves ingestion cost, at the price of losing Live Tail and some Insights features.
functions:
  chatty:
    handler: src/chatty.handler
    logs:
      logFormat: JSON
      applicationLogLevel: WARN   # runtime-level filtering, before ingestion
      systemLogLevel: WARN

Advanced logging controls filter at the runtime, so suppressed lines are never ingested and never billed. That is strictly better than filtering in your logger alone.

Step 5: Pick the cheaper front door

For a plain JSON API, HTTP API costs about $1.00 per million requests; REST API costs about $3.50 per million plus optional caching. Unless you need per-request WAF integration, API keys with usage plans, request validation models, or private endpoints, use httpApi:

functions:
  api:
    handler: src/api.handler
    events:
      - httpApi:            # not "http" (that is REST API)
          path: /orders
          method: post

If the endpoint is internal, machine-to-machine, or a webhook receiver, a Lambda Function URL costs nothing at all beyond the Lambda invocation. It gives you no throttling and no request validation, so put IAM auth or a shared-secret check in front of it.

Step 6: Check for the invisible line items

These are the ones that surprise people:

  • NAT Gateway. About $0.045 per hour per gateway plus $0.045 per GB processed. If your functions are in a VPC only so they can reach DynamoDB, S3, or Secrets Manager, replace the NAT Gateway with VPC endpoints (Gateway endpoints for S3 and DynamoDB are free) -- or take the functions out of the VPC entirely. Three AZs of NAT Gateway that exist for one RDS query is roughly $100 a month before any traffic.
  • DynamoDB capacity mode. On-demand is the right default for spiky and unknown workloads. For a predictable baseline, provisioned capacity with autoscaling can be several times cheaper. Check the ConsumedReadCapacityUnits graph for a week before deciding.
  • Step Functions. Standard workflows bill per state transition (~$25 per million); Express workflows bill on duration and are dramatically cheaper for high-volume, short-lived orchestration. Swapping a hot Standard workflow to Express is often a 90% cut on that line.
  • Idle non-production stages. Provisioned Concurrency, warm RDS instances, and OpenSearch domains in dev and staging run 24/7 by default. Schedule them down or make review stages ephemeral and delete them with serverless remove when the pull request closes.
  • X-Ray at 100% sampling. Traces cost per recorded trace; sample at 5-10% in production and keep 100% only for error paths.

Step 7: Put a guardrail on it so it does not drift back

Cost work that is not automated regresses within a quarter. Two cheap defences:

A budget with an alert, defined in the same stack:

resources:
  Resources:
    MonthlyBudget:
      Type: AWS::Budgets::Budget
      Properties:
        Budget:
          BudgetName: ${self:service}-${sls:stage}
          BudgetLimit:
            Amount: 200
            Unit: USD
          TimeUnit: MONTHLY
          BudgetType: COST
          CostFilters:
            TagKeyValue:
              - 'user:project$checkout'
        NotificationsWithSubscribers:
          - Notification:
              NotificationType: FORECASTED
              ComparisonOperator: GREATER_THAN
              Threshold: 80
              ThresholdType: PERCENTAGE
            Subscribers:
              - SubscriptionType: EMAIL
                Address: platform-team@example.com

Concurrency caps so a runaway loop or a retry storm cannot bill you a fortune before anyone wakes up:

functions:
  webhookProcessor:
    handler: src/webhook.handler
    reservedConcurrency: 20   # hard ceiling on parallel executions

Reserved concurrency is a blast-radius control as much as a cost control: it caps spend and protects downstream databases from being hammered by a scaling event.

A realistic outcome

A recent review of a mid-sized Serverless Framework estate: arm64 across the board (-18% on compute), memory right-sizing on the six busiest functions (-22% more, with lower p95 latency), log retention plus applicationLogLevel: WARN (CloudWatch spend down 70%, the largest single saving), REST API to HTTP API on two services, and a NAT Gateway removed in favour of VPC endpoints. Total: a little under 45% off the monthly bill, no functional change, about three days of work.

The order matters. Measure, then tag, then change one dial at a time and compare a full billing day before moving on.


If you would like this run against your own account, our Serverless Performance Tuning and DevOps for Serverless Apps engagements include a cost review with the numbers written down. Get in touch and tell us roughly what you are spending today.