What it means
When a Lambda is attached to a VPC, every execution environment it creates needs a path into your network. Lambda provides that with a shared elastic network interface — one per unique combination of subnets and security groups, not one per concurrent execution, which is what makes VPC-attached functions scale at all. But interfaces still consume addresses from your subnets, and both the addresses and the interfaces are finite.
The result is a failure that is invisible until the day it is not. At low concurrency the function needs very little, everything works, and no test reproduces anything. Then a queue backs up or a scheduled job fans out, Lambda tries to scale, and there is nowhere to put the new environments. The invocations that cannot get an interface fail before your code runs.
Two different ceilings produce this, and the exception name is what tells them apart. SubnetIPAddressLimitReachedException means your subnet ran out of IP addresses — and subnets are sized once, early, usually by someone who was not thinking about Lambda concurrency. A /28 has sixteen addresses, five of which AWS reserves, leaving eleven for everything in that subnet including whatever else already lives there. ENILimitReachedException means the account's interface quota for the region is the binding constraint, which is a support request rather than a CIDR change.
ENINotReadyException is a third thing again and worth not confusing with the other two. It is a race, not a ceiling: an interface was being provisioned and was not usable at the moment an invocation arrived. It clears on its own, and the correct response is a retry rather than a capacity change — which is exactly the wrong response to the other two.
CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. The function could not start, so the invocation fails and CloudWatch counts it. LogStitch classified the example below from its log level rather than an extracted error type, because the message is a plain error line rather than a Runtime. envelope.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
5 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Errorc37f0a92-6b41-4e58-9d02-8a1c5e7b3f60
- 11:22:08.114PLAT
START RequestId: c37f0a92-6b41-4e58-9d02-8a1c5e7b3f60 Version: $LATEST
- 11:22:08.118INFO
INFO Draining order queue batch=25
- 11:22:08.204ERROR
ERROR ENILimitReachedException: The elastic network interface limit was reached for the function's VPC
- 11:22:08.402PLAT
END RequestId: c37f0a92-6b41-4e58-9d02-8a1c5e7b3f60
- 11:22:08.402PLAT
REPORT RequestId: c37f0a92-6b41-4e58-9d02-8a1c5e7b3f60 Duration: 288.44 ms Billed Duration: 289 ms Memory Size: 512 MB Max Memory Used: 96 MB Status: error
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
Correlate the failures with concurrency rather than with input. This never fires on a quiet function and never reproduces in testing; it fires when a spike asks Lambda to create more execution environments than the VPC can supply interfaces or addresses for.
The exception name narrows it. SubnetIPAddressLimitReached means the subnet's CIDR has no free addresses left — the fix is a bigger or additional subnet. ENILimitReached means the account's interface quota in the region is the constraint, which is a quota increase. ENINotReady is timing rather than capacity: an interface was being created and was not usable yet.
Causes, most likely first
The subnets are too small for the concurrency the function reaches
Check the CIDR size and the free address count on each subnet the function is attached to. A /28 leaves eleven usable addresses after AWS reserves five, and anything sharing those subnets — an RDS instance, an ECS task, another Lambda — is competing for the same pool.
The account's ENI quota for the region has been reached
Compare the number of network interfaces in the region against the quota. Hyperplane ENIs are shared across functions with the same subnet-and-security-group combination, so this is usually hit by an account with many distinct combinations rather than by one busy function.
Many functions each use a unique security-group combination
List the VPC configurations across your functions. Lambda shares one interface across functions that share a subnet set and a security group set; giving each function its own bespoke security group defeats that sharing and multiplies interface consumption.
A traffic spike outpaced interface creation
Look for the failures clustering in the first moments of a spike and clearing shortly after. ENINotReady in particular is a race rather than a ceiling — capacity existed, it just was not ready at the instant the invocation arrived.
Fixes
Give the function larger subnets, across multiple availability zones
This is the fix for address exhaustion and the one most often needed. A /24 leaves 251 usable addresses against a /28's eleven, and spreading across zones both multiplies the pool and improves availability.
yaml# A /28 gives 11 usable addresses. A /24 gives 251.
LambdaSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
CidrBlock: 10.0.16.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Check free addresses before you go looking anywhere else
One command distinguishes address exhaustion from a quota problem, and they have completely different fixes.
bashaws ec2 describe-subnets --subnet-ids subnet-0abc subnet-0def \
--query 'Subnets[].{Id:SubnetId,Cidr:CidrBlock,Free:AvailableIpAddressCount}'
Share security groups so functions share interfaces
Lambda creates one shared interface per distinct subnet-and-security-group combination. Functions with identical VPC configuration reuse it; functions with bespoke security groups each consume their own. Consolidating the combinations reduces interface count directly.
Cap concurrency so the function cannot outgrow its subnets
Reserved concurrency bounds how many execution environments can exist at once, which bounds how many addresses the function can consume. This is the fastest mitigation while a subnet change is planned.
yamlResources:
VpcFunction:
Type: AWS::Serverless::Function
Properties:
ReservedConcurrentExecutions: 50
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- AWS Lambda Developer Guide — Connecting outbound networking to resources in a VPC
- AWS Lambda Developer Guide — Lambda quotas
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.