What it means
EAI_AGAIN is the resolver library reporting that a lookup did not complete in time. The name is short for "try again", and it exists because DNS is expected to be occasionally busy — a transient condition a retry would clear.
Inside a VPC-attached Lambda it usually means something else entirely. The function is not being told to wait its turn; it is failing to reach a resolver at all. The query goes out over UDP 53 to the VPC resolver and nothing comes back, so the library waits out its timeout and reports the only thing it can distinguish, which is that no answer arrived. A permanent routing or filtering problem and a momentarily overloaded resolver produce the identical error code.
That is why the most useful first question is not why is DNS slow but is this every lookup or some of them. A blocked NACL, a disabled enableDnsSupport, or a DHCP option set pointing at unreachable name servers all produce total failure: every invocation, every name, indefinitely. Load-related failures — the per-interface DNS packet rate ceiling, reached by a function fanning out at high concurrency — produce scattered failures that track your traffic curve. The first is a configuration fix; the second is usually solved by resolving less, which mostly means constructing SDK clients at module scope so each execution environment does one lookup rather than one per request.
Network ACLs deserve particular suspicion because of how they fail. They are stateless, unlike security groups, so allowing outbound UDP 53 does nothing on its own — the response arrives on an ephemeral port and needs its own inbound rule. A VPC configured by someone thinking in security-group terms looks completely correct and drops every DNS reply, and the resulting EAI_AGAIN gives no hint that the query itself left successfully.
CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. Counted only when the error escapes the handler. LogStitch classified the example below from its log level rather than an extracted error type, so it reports the failure as caught.
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
7 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Error7e2c4b81-9f36-4d50-a217-6b8c3e0f5a41
- 08:41:19.008PLAT
START RequestId: 7e2c4b81-9f36-4d50-a217-6b8c3e0f5a41 Version: $LATEST
- 08:41:19.012INFO
INFO Loading database credentials
- 08:41:24.118ERROR
ERROR Error: getaddrinfo EAI_AGAIN secretsmanager.us-east-1.amazonaws.com at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) at loadCredentials (/var/task/src/db.js:14:22)3 lines - 08:41:24.402PLAT
END RequestId: 7e2c4b81-9f36-4d50-a217-6b8c3e0f5a41
- 08:41:24.402PLAT
REPORT RequestId: 7e2c4b81-9f36-4d50-a217-6b8c3e0f5a41 Duration: 5394.22 ms Billed Duration: 5395 ms Memory Size: 512 MB Max Memory Used: 112 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
Time it. A lookup that fails after several seconds — often five, matching the resolver timeout — is a timeout; one that fails instantly is a different error wearing a similar name. Check the elapsed time between the log line before the failure and the failure itself.
Then check whether it is intermittent or total. Genuinely transient DNS pressure produces scattered failures under load. A VPC that cannot reach its resolver produces EAI_AGAIN on every lookup, from every invocation, forever — and the word "temporary" in the error is then actively misleading, because nothing about it is temporary.
Causes, most likely first
A network ACL or security group blocks UDP 53 to the VPC resolver
Check the subnet's network ACL for outbound UDP 53 and the corresponding inbound ephemeral return range. Security groups are stateful and rarely the culprit; NACLs are stateless, so an outbound rule without a matching inbound rule for return traffic silently drops every reply and every lookup times out.
A custom DHCP option set points at unreachable name servers
Look at the DHCP option set associated with the VPC. If it names on-premises or third-party resolvers, the function must actually be able to reach them — over a VPN, Direct Connect, or peering. When that path is down or was never built, every lookup times out while the configuration looks deliberate.
The function is under heavy concurrency and exceeding the per-ENI DNS limit
Correlate the failures with concurrent executions. There is a packets-per-second ceiling for DNS queries from a network interface, and a function fanning out at high concurrency — especially one resolving a fresh name on every invocation rather than reusing a client — can reach it. This is the case where the failures really are intermittent and load-shaped.
DNS resolution is disabled on the VPC
Check enableDnsSupport. With it off there is nothing listening at the VPC's .2 resolver address, so queries go unanswered rather than refused — which surfaces as a timeout rather than as a clear failure.
Fixes
Allow DNS traffic in the network ACL, in both directions
NACLs are stateless. An outbound rule for UDP 53 without an inbound rule for the ephemeral return ports drops every response, which is indistinguishable from an unreachable resolver. This is the single most common cause in a hand-built VPC.
bash# Outbound query
aws ec2 create-network-acl-entry --network-acl-id acl-0abc \
--rule-number 110 --protocol udp --port-range From=53,To=53 \
--cidr-block 0.0.0.0/0 --rule-action allow --egress
# Inbound response, on the ephemeral range
aws ec2 create-network-acl-entry --network-acl-id acl-0abc \
--rule-number 110 --protocol udp --port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 --rule-action allow
Check the DHCP option set actually points somewhere reachable
A custom option set naming on-premises resolvers only works while the path to them does. Reverting to AmazonProvidedDNS is the fastest way to confirm whether the resolver choice is the problem.
bashaws ec2 describe-vpcs --vpc-ids vpc-0abc \
--query 'Vpcs[0].DhcpOptionsId' --output text | \
xargs -I{} aws ec2 describe-dhcp-options --dhcp-options-ids {}
Create clients once, at module scope, so lookups are not repeated per invocation
A client constructed inside the handler resolves its endpoint on every invocation. Hoisting it to module scope means one lookup per execution environment instead of one per request, which removes most of the DNS load a high-concurrency function generates.
jsimport { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
// Module scope: resolved once per execution environment, reused across invocations.
const client = new SecretsManagerClient({});
export const handler = async (event) => {
// Not: new SecretsManagerClient({}) on every request.
return client.send(command(event));
};
Use a VPC endpoint so the call never needs public DNS
An interface endpoint for the service places it inside your VPC with a private DNS name, removing both the resolution and the routing problem in one change.
yamlSecretsManagerEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref Vpc
ServiceName: !Sub "com.amazonaws.${AWS::Region}.secretsmanager"
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds: !Ref PrivateSubnets
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
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.