Your endpoint returns 200 on your laptop, all tests are passing, and your client in Mumbai is still getting a timeout. Nothing in the diff explains it, because the problem sits below the code: in a resolver, a route, a certificate, or an IP family.
Here are the network troubleshooting habits that help turn "works for me" into a provable cause, with the commands to run and what the output means.
How to Troubleshoot Network Issues: Habits That Help Get to the Root Cause
Good network troubleshooting walks the OSI model layers in the order a request uses them. A name must resolve, a socket must open, and the TLS handshake must complete before HTTP even matters. Skip a layer and you end up tuning your nginx config to fix a DNS typo. Common errors and what they point to:
- ENOTFOUND or EAI_AGAIN: the name didn't resolve.
- ECONNREFUSED: the host answered, but nothing listens on that port or address.
- ETIMEDOUT: packets vanished. Think firewalls and security groups.
- CERT_HAS_EXPIRED or "unable to verify the first certificate": it's TLS.
- 502 or 504 from a proxy: the edge works; the upstream doesn't.
Start Network Troubleshooting with Name Resolution
Run queries against specific resolvers so that a local cache does not play tricks on you:
$ dig api.example.com +noall +answer
api.example.com. 287 IN A 203.0.113.24
$ dig @8.8.8.8 api.example.com +short
198.51.100.7
The second column is the TTL in seconds. Resolvers disagree while a change propagates: each one keeps the old record until its cached TTL runs out. So lower the TTL to 300 a day before a migration, not during it. NXDOMAIN answers are cached for the negative TTL set in the zone's SOA record (often an hour), so a typo fix may not show up until that cache expires. Use dig +trace to find which nameserver returns the wrong answer. nslookup host 1.1.1.1 performs the basic test on Windows. Use resolvectl flush-caches on Linux and ipconfig /flushdns on Windows to flush local caches.
DNS resolution delays lurk in Kubernetes, too. The default ndots:5 makes a lookup for api.stripe.com try the cluster's search domains first, and even a single dropped UDP query causes a 5-second delay (that is the glibc resolver timeout). The same dig commands also check TXT records, such as the SPF and DMARC records that protect your domain from spoofed phishing emails.
Test the Connection and the Route
Many clouds drop ICMP traffic, so a failed ping says very little. First, test the port. Then trace the path over that same TCP port:
$ nc -vz 203.0.113.24 443
Connection to 203.0.113.24 443 port [tcp/https] succeeded!
$ mtr -rwc 100 -T -P 443 203.0.113.24
HOST: dev-laptop Loss% Snt Last Avg Best Wrst StDev
1.|-- 192.168.1.1 0.0% 100 1.2 1.4 0.9 3.1 0.4
2.|-- 10.20.0.1 0.0% 100 8.7 9.1 7.9 15.2 1.1
3.|-- ae-4.edge1.fra.carrier.net 42.0% 100 21.4 22.0 19.8 30.4 2.3
4.|-- 203.0.113.24 0.0% 100 24.1 24.6 22.9 31.0 1.4
Read mtr output from the end. Losses at hop 3 that disappear at the destination point to a router limiting probe reply rate, not real packet loss. Loss that starts at one hop and continues to the end is real; start investigating at the hop where it first appears.
Inspect the TLS Handshake
$ openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null
0 s:CN = api.example.com
i:C = US, O = Let's Encrypt, CN = R11
Verify return code: 21 (unable to verify the first certificate)
Code 21 almost always means a missing intermediate. Browsers often fetch intermediates themselves, so your site looks great in Chrome, but curl, Python, and mobile clients fail on the TLS handshake. Code 10 means expired. Pass -servername, especially when you connect by IP, or a server hosting multiple domains may return its default certificate. Pipe the output into openssl x509 -noout -enddate to get the expiration date. This matters more now: the CA/Browser Forum cut the maximum certificate lifetime to 200 days in 2026, and it drops to 47 days by 2029.
Debugging HTTP Requests and Timeouts with curl
curl verbose mode gives all the details:
$ curl -v https://api.example.com/health
* Trying 203.0.113.24:443...
* Connected to api.example.com (203.0.113.24) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
< HTTP/2 503
Where the output stops is your answer: hanging after "Trying" means that TCP does not connect; hanging after "Connected" points to a TLS or proxy problem. To debug intermittent request timeouts, split the request time into phases:
$ curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer}\n' https://api.example.com/health
dns:5.012 connect:5.038 tls:5.102 ttfb:5.240
The values are cumulative, so this one is the resolver-timeout case from above, not slow code. --resolve host:443:198.51.100.7 allows you to hit one backend while preserving hostname and SNI. --max-time prevents a test from taking longer than your real client would. Output like this is network troubleshooting evidence your hosting provider can act on.
IPv4 vs IPv6: What Breaks in Dual-Stack Networking?
Fewer than a third of dual-stack requests worldwide happen over IPv6 – in India it's more than two-thirds. Publish AAAA records, and many mobile clients will choose IPv6 for their connections.
Browsers use Happy Eyeballs (RFC 8305): try v6, wait 250 ms, race v4. Many server-side clients just wait the full connect timeout. Since Node.js 17, dns.lookup() keeps the resolver's address order instead of putting IPv4 first, so localhost can resolve to ::1.
If your dev server listens only on 127.0.0.1, a request to ::1:3000 fails with ECONNREFUSED. Node.js 20+ softens this with autoSelectFamily, but older runtimes and other clients still hit it. The classic production bug in dual-stack networking is an AAAA record pointing to a host where the app binds only to 0.0.0.0 or the firewall has only IPv4 rules.
Testing Your Application Over IPv6
$ dig api.example.com AAAA +short
2001:db8:85a3::8a2e:370:7334
$ curl -6 -sv https://api.example.com/health 2>&1 | grep '^< HTTP/'
< HTTP/2 200
$ ss -tln | grep ':443'
LISTEN 0 511 [::]:443 [::]:*
You want a listener on [::]:443, not on 0.0.0.0:443. Check that the AAAA record is pointing where you expect it to, and then re-run the request with curl -6 to force IPv6. If the bug is only occurring for some customers, then an IPv4-only office line won't help you to reproduce it.
You can run the same requests through an IPv6 proxy to make the API call from an IPv6 address (as those clients do) and then compare the status codes, headers, and response times against the v4 path. It also reveals rate limits, allowlists, or geo rules written with only IPv4 in mind, which can silently block entire IPv6 prefixes.
Latency Spikes and Packet Loss
Latency spikes and packet loss hurt much more than stable latency, as even a single TCP retransmit pauses the whole stream. Run mtr for hundreds of cycles in the bad window, not once after it. Small requests succeed while large responses hang? Suspect the MTU size:
$ ping -M do -s 1472 203.0.113.24
ping: local error: message too long, mtu=1420
ping -M do only works on Linux; on macOS the flag is -D.
1472 bytes of payload + 28 bytes of headers = 1500 byte packet. VPNs and cloud overlays lower this limit, and path MTU discovery relies on ICMP, so a firewall that drops ICMP can make large packets vanish silently. IPv6 routers never fragment, so a firewall that drops ICMPv6 "Packet Too Big" messages breaks large transfers outright. Few network troubleshooting guides mention this, yet it explains many "only big uploads fail" tickets.
A Quick Checklist for Troubleshooting Network Issues
Network Troubleshooting Documentation
Store the raw command output, not a summary, with timestamps, the source IP you tested from, the resolver, the TTL you observed, and changes made. If the same symptom returns after a certificate renewal or DNS migration, those notes turn an evening of network troubleshooting into a 10-minute fix. They also form a perfect post-mortem timeline.
Most "application bugs" of this kind boil down to a stale DNS record, a missing intermediate certificate, a firewall rule that covers only one IP family, or an MTU mismatch. Working through the OSI model layers in request order (name resolution, connection and route, TLS, then HTTP) for both IP families keeps network troubleshooting short and guesswork-free.