"It can't connect" is the most common failure you will ever debug, and it hides a dozen different causes: a DNS record that has not propagated, a closed port, an expired certificate, a load balancer sending traffic to a dead instance. This module gives you the layered model and the handful of tools that let you find which one it is in minutes instead of guessing.
- Trace a request from hostname to response: DNS, TCP, TLS, HTTP
- Read IP addresses, CIDR ranges and ports, and tell private from public addresses
- Query DNS with
digand explain record types, TTLs and caching - Interpret HTTP status codes and headers, and inspect a TLS certificate from the command line
- Explain what reverse proxies and load balancers do and how health checks drive them
One request, four steps
When a client calls https://api.example.com/orders, four things happen in order, and each can fail separately.
- DNS turns the name
api.example.cominto an IP address. - TCP opens a connection to that IP on a port (443 for HTTPS).
- TLS verifies the server's certificate and sets up encryption.
- HTTP sends the request and receives a response with a status code.
Debugging is walking those steps in order and finding the first one that fails. The error message usually tells you which, if you know how to read it.
| What you see | Failing step | Typical cause |
|---|---|---|
Could not resolve host | DNS | Wrong name, missing record, broken resolver |
Connection refused | TCP | Host reachable, but nothing is listening on that port |
Connection timed out | TCP | A firewall or security group is silently dropping packets, or wrong IP |
certificate has expired / doesn't match | TLS | Expired or wrong certificate, missing intermediate |
502 / 503 / 504 | HTTP | The proxy answered, but the backend behind it did not |
Refused and timed out mean different things. Refused is an active answer: the machine is there and rejected you, so look at the service. Timed out is silence: something dropped the packets, so look at firewalls, security groups and routing.
Addresses, CIDR and ports
An IPv4 address is 32 bits written as four numbers, such as 10.0.1.25. CIDR notation describes a range: 10.0.1.0/24 means the first 24 bits are fixed, leaving 8 bits, so 256 addresses. Each step down in the prefix doubles the size: a /23 holds 512, a /16 holds 65,536. 0.0.0.0/0 means every address, which is why it is the dangerous value in a firewall rule.
Three ranges are reserved as private and are never routed on the internet: 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. Cloud networks and home routers use them internally and reach the internet through NAT, which rewrites the private source address to a public one. 127.0.0.1 (localhost) is the machine itself.
A port identifies which program on the host should receive the traffic. A server listens on a port; a client connects to it. Ports below 1024 need elevated privileges to bind.
| Port | Protocol |
|---|---|
| 22 | SSH |
| 53 | DNS |
| 80 | HTTP |
| 443 | HTTPS |
| 5432 | PostgreSQL |
| 6379 | Redis |
ip addr # this machine's interfaces and addresses
ip route # routing table; 'default via' is the gateway
ss -tlnp # listening TCP ports and the owning process
nc -vz db.internal 5432 # can I open a TCP connection to that port?
ping -c 3 10.0.1.25 # basic reachability (ICMP is often blocked)A service listening on 127.0.0.1:8080 accepts connections only from the same machine. To be reachable from other hosts, or from outside its container, it must listen on 0.0.0.0 or a specific interface address. This is a classic reason a containerised app works locally and is unreachable once deployed.
DNS
DNS is a distributed, cached lookup table. Your machine asks a resolver, which walks the hierarchy (root, then .com, then the authoritative nameservers for example.com) and caches the answer for the record's TTL, in seconds.
| Record | Maps |
|---|---|
A / AAAA | Name to IPv4 / IPv6 address |
CNAME | Name to another name (an alias). Not allowed at the zone apex. |
MX | Domain to its mail servers |
TXT | Free text: domain verification, SPF, DKIM |
NS | Zone to its authoritative nameservers |
dig api.example.com # full answer, including TTL
dig +short api.example.com # just the addresses
dig api.example.com @1.1.1.1 # ask a specific resolver
dig +trace api.example.com # walk the hierarchy from the root
dig NS example.com +short # who is authoritative?
cat /etc/resolv.conf # which resolver does this host use?"DNS propagation" is really cache expiry: after you change a record, resolvers keep serving the old answer until its TTL runs out. Before a planned migration, lower the TTL a day ahead so that the switch takes effect in minutes. If two resolvers disagree, compare them with dig @RESOLVER and query the authoritative server directly to see the truth.
Inside Kubernetes, DNS is how services find each other: a Service named orders in namespace shop resolves as orders.shop.svc.cluster.local.
HTTP
An HTTP request is a method, a path, headers, and an optional body. GET reads and must be safe to repeat; POST creates or triggers; PUT replaces; PATCH modifies; DELETE removes. The response carries a status code, and its first digit tells you whose problem it is.
| Class | Meaning | Examples |
|---|---|---|
| 2xx | Success | 200 OK, 201 Created, 204 No Content |
| 3xx | Go elsewhere | 301 permanent redirect, 302 temporary, 304 not modified |
| 4xx | The client's request is wrong | 400 bad request, 401 not authenticated, 403 not allowed, 404 not found, 429 rate limited |
| 5xx | The server side failed | 500 app error, 502 bad gateway, 503 unavailable, 504 gateway timeout |
The three gateway codes matter most in operations. 502 means the proxy reached the backend and got garbage or a reset connection, often a crashed or restarting app. 503 means no healthy backend is available. 504 means the backend did not answer within the proxy's timeout.
curl -i https://api.example.com/health # include response headers
curl -v https://api.example.com/health # show DNS, TCP, TLS and headers
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://api.example.com/
curl -X POST -H 'Content-Type: application/json' -d '{"qty": 2}' \
https://api.example.com/orders
curl -H 'Host: api.example.com' http://10.0.1.25/health # hit one backend directlyTLS
TLS gives you encryption and, just as important, identity. The server presents a certificate that binds its hostname to a public key and is signed by a certificate authority (CA). The client checks three things: the signature chains up to a CA it trusts, the current date is inside the validity period, and the hostname it asked for appears in the certificate's Subject Alternative Names.
Failures map directly onto those checks: an expired certificate, a hostname mismatch, or a server that forgot to send its intermediate certificate, in which case browsers may cope but curl and application clients reject it. SNI lets one IP address serve certificates for many hostnames, because the client names the host it wants at the start of the handshake.
# show the certificate chain the server actually sends
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null
# just the validity dates and the names it covers
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null \
2>/dev/null | openssl x509 -noout -dates -subject -ext subjectAltNamecurl -k and "verify: false" turn off the identity check and leave you open to interception. Use them to confirm a diagnosis, never as the fix. Expired certificates are a preventable outage: automate renewal and alert on days-to-expiry.
Proxies and load balancers
A reverse proxy (NGINX, HAProxy, Envoy, a cloud load balancer, a Kubernetes Ingress controller) sits in front of your application. It terminates TLS, routes by hostname and path, and spreads requests over several backends. A forward proxy is the opposite: it sits in front of clients and makes outbound requests for them.
- A layer 4 balancer forwards TCP connections and knows nothing about HTTP. A layer 7 balancer understands HTTP, so it can route on path or header, retry, and add headers.
- Health checks decide which backends receive traffic. The balancer probes each one on an interval and removes those that fail. A missing or wrong health-check path is a common reason for a
503with perfectly healthy apps. - Behind a proxy your app sees the proxy's IP as the client. The real address arrives in the
X-Forwarded-Forheader, and the original scheme inX-Forwarded-Proto. - Timeouts exist at every hop. If the proxy's timeout is shorter than the app's slowest request, users get a
504while the app finishes work nobody is waiting for.
When a request through the balancer fails, take it out of the picture: curl a backend directly, as in the Host header example above. If the backend answers, the problem is the balancer's configuration, health check or firewall rules, not your application.