---
cuppy_hub: aws
cuppy_path: guides/vpc/diagnose-connection-timeout.md
cuppy_section: docs
cuppy_doc: AJWB-W6CR
cuppy_doc_uuid: 5ecaf565-622e-4de2-8737-11beea2e18ef
cuppy_permalink: https://cuppy.io/aws/d/AJWB-W6CR
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/vpc/diagnose-connection-timeout
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 2689eb123095e7026af78c83270005c1a6f0a0753e74c4733e6a8f03e207af12
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Diagnose "it deployed but cannot connect"

**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.

```ts
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: true` means 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_PATH` can 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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
