Diagnose "it deployed but cannot connect"
Turn a silent network timeout into a named cause. Run Reachability Analyzer between the two resources to get an explanation code, then check the five layers in order — security groups, network ACLs, routes, gateways (IGW, NAT, endpoints) and VPC Block Public Access — with the CLI or TypeScript call for each.
markdownguidev4AJWB-W6CRstage: operatetrust: officialservice: vpcinterface: cliinterface: sdkconnection timed outcannot connecttimeout no errorReachability Analyzercreate-network-insights-pathstart-network-insights-analysisNetworkPathFoundExplanationCodeENI_SG_RULES_MISMATCHNO_ROUTE_TO_DESTINATIONsecurity groupnetwork ACL ephemeral portsroute tableNAT gatewayVPC Block Public Accessflow logs
A timeout has no error message, so make AWS produce one: run Reachability Analyzer from the source to the destination. When the path is blocked, it names the blocking component and gives an explanation code you can look up in reference/vpc/errors.md. It reads configuration only: it checks security groups, network ACLs, routes, gateways and load balancers, not the application. Each analysis is billed.
1. Ask the analyzer
aws ec2 create-network-insights-path --source <source-id> --destination <destination-id> \
--protocol TCP --filter-at-source '{"DestinationPortRange":{"FromPort":443,"ToPort":443}}'
aws ec2 start-network-insights-analysis --network-insights-path-id <nip-…>
aws ec2 describe-network-insights-analyses --network-insights-analysis-ids <nia-…> \
--query 'NetworkInsightsAnalyses[0].[Status,NetworkPathFound,Explanations[].ExplanationCode]'
Sources and destinations are resource IDs: instances, network interfaces, internet gateways, endpoints and so on. To test toward an IP address, omit --destination and put DestinationAddress in the filter. The analysis takes a few minutes. Delete the analysis before the path; analyses are kept for 120 days.
import {
EC2Client,
CreateNetworkInsightsPathCommand,
StartNetworkInsightsAnalysisCommand,
DescribeNetworkInsightsAnalysesCommand,
} from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({ region: "<region>" });
const path = await ec2.send(new CreateNetworkInsightsPathCommand({
Source: "<source id: instance, ENI, IGW, endpoint…>",
Destination: "<destination id>",
Protocol: "tcp",
FilterAtSource: { DestinationPortRange: { FromPort: 443, ToPort: 443 } },
}));
const pathId = path.NetworkInsightsPath?.NetworkInsightsPathId!;
const start = await ec2.send(new StartNetworkInsightsAnalysisCommand({ NetworkInsightsPathId: pathId }));
const analysisId = start.NetworkInsightsAnalysis?.NetworkInsightsAnalysisId!;
for (;;) {
await new Promise((r) => setTimeout(r, 10_000));
const res = await ec2.send(new DescribeNetworkInsightsAnalysesCommand({ NetworkInsightsAnalysisIds: [analysisId] }));
const a = res.NetworkInsightsAnalyses?.[0];
if (a?.Status === "running") continue;
console.log("reachable:", a?.NetworkPathFound);
for (const e of a?.Explanations ?? []) {
console.log(e.ExplanationCode, e.Direction, e.SecurityGroups?.map((g) => g.Id), e.Acl?.Id, e.RouteTable?.Id);
}
break;
}
NetworkPathFound: truemeans the network allows the connection, so the problem is above the network. Check that the process is listening on that port, the TLS settings, and the application's own firewall.NO_PATHcan also mean an unsupported case, such as IPv6. Then do the manual checks below.
2. Or check the five layers by hand, in this order
Security groups (stateful, allow-only)
- The destination's group needs an inbound rule for the port, from the source's IP, CIDR or security group. A new group allows no inbound traffic.
- The source's group needs an outbound rule. A new group allows all outbound traffic, but it may have been removed.
- Return traffic is automatic. You never add rules for responses.
- A rule that references a security group only matches private IPs of members, in the same VPC, a peered VPC, or (inbound only) across a transit gateway. Traffic to a public IP or through a middlebox does not match it.
aws ec2 describe-security-groups --group-ids <sg> --query 'SecurityGroups[0].[IpPermissions,IpPermissionsEgress]'
Network ACLs (stateless, ordered)
- They apply when traffic enters or leaves a subnet, in both directions, separately. Responses are not automatically allowed, so the return path needs the ephemeral ports 1024–65535.
- Rules are evaluated lowest number first, and the first match wins.
- A custom NACL denies everything until you add rules (UNVERIFIED in the collected sources: the NACL page links to its custom-NACL subpage without stating the default). The default NACL allows all traffic.
aws ec2 describe-network-acls --filters Name=association.subnet-id,Values=<subnet>
Routes
- The source subnet's route table needs a route covering the destination. The most specific route wins.
- Internet from a private subnet:
0.0.0.0/0 → nat-…. From a public subnet:0.0.0.0/0 → igw-…, and the resource needs a public IP. - Gateway endpoints (S3, DynamoDB) work only for the route tables they are associated with.
aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=<subnet> --query 'RouteTables[0].Routes'
A subnet with no explicit association uses the main route table (UNVERIFIED in the collected sources).
Gateways
- NAT gateway: must be
Available. A zonal one sits in a public subnet routed to the internet gateway. A regional one needs no public subnet but can take up to 60 minutes to expand to a new Availability Zone. NAT passes only TCP, UDP and ICMP, drops connections idle for 350 seconds (with an RST), and does not support IPsec or fragmented TCP. - Internet gateway: attached to the VPC, and the resource has a public IPv4 address or an Elastic IP.
- Interface endpoint: private DNS on, and its security group allows 443 from the caller.
VPC Block Public Access
An account-level setting per Region that overrides routes. In Bidirectional mode it blocks internet gateway traffic both ways. In Ingress-only mode it blocks inbound traffic while NAT and egress-only gateways keep working. Excluded VPCs or subnets are exempt. Reachability Analyzer reports it as VPC_BLOCK_PUBLIC_ACCESS_ENABLED. Check with aws ec2 describe-vpc-block-public-access-options (UNVERIFIED command name).
3. Things that are not the network
- DNS to the Route 53 Resolver (the VPC+2 address), instance metadata, and Time Sync can't be blocked by security groups or NACLs. If those fail, look elsewhere.
- A load balancer in the path brings its own rules: listeners, target health, and target groups in the enabled AZs (
guides/elb/fix-alb-5xx.md). - Lambda in a VPC never has a public IP (
patterns/lambda-in-vpc.md). Fargate pulling images needs a route to ECR and S3 (patterns/ecs-to-ecr.md).
4. Confirm with traffic
Turn on VPC Flow Logs for the network interface or subnet. A REJECT record means a security group or NACL dropped the packet; nothing recorded means it never arrived, so check the routes. How to read the records: reference/vpc/flow-logs.md. Ready-made queries: reference/cloudwatch/logs-insights-queries.md.