Debugging Network Issues: DNS, SSL, and Headers
A layer-by-layer debugging walkthrough for DNS, TLS, HTTP headers and CORS, with the exact commands and the spec clause behind each failure signature.
"The site is down" is usually a lie - the site is fine, and something between you and it is not. Before you blame the application, check the four layers underneath: DNS resolution, the TLS handshake, the response headers, and the browser's CORS policy. Each has its own failure signature, and each can be checked in seconds without leaving the browser. If you only need the headline answer first, the Website Status Checker reports whether the site is reachable at all before you dig into layers.
The table below maps each symptom to the layer that produced it.
| What you see | Layer | First check | Authoritative spec |
|---|---|---|---|
| Works on my machine, fails for a colleague | DNS | dig <name> A +short against two resolvers | RFC 1035, RFC 8020 |
Browser warning, but curl succeeds | TLS | openssl s_client -servername <host> | RFC 5280, RFC 6066 |
| Right status code, wrong or stale content | HTTP cache | curl -sSI <url> | RFC 9111 |
| Request works everywhere except the browser | CORS | Re-send with an Origin header | WHATWG Fetch |
| Infinite bounce between two URLs | Redirects | Count hops and read location | RFC 9110, RFC 7538 |
Layer 1: DNS - does the name resolve to what you expect?
DNS problems look like "it works on my machine" - because your resolver or hosts file differs from the user's. When a domain behaves differently for different people, start by querying the actual records with the DNS Lookup tool. Check the A and AAAA records against the IP your provider says the site should be on, and look at MX and TXT records when email or verification is the complaint.
| Record | Answers the question | Query |
|---|---|---|
A / AAAA | Which IPv4 / IPv6 address serves this name? | dig name A +short |
CNAME | Is this name an alias of another name? | dig name CNAME +short |
MX | Which hosts receive mail for the domain? | dig name MX +short |
TXT | SPF, DKIM and domain-verification tokens | dig name TXT +short |
NS / SOA | Which servers are authoritative, and what is the zone serial? | dig name NS +short |
CAA | Which certificate authorities may issue for this domain? | dig name CAA +short |
The record types and their semantics are defined in
RFC 1035, the DNS specification.
Two rules in that spec explain most "impossible" DNS findings. First,
CNAME is exclusive: "If a CNAME RR is present at a node, no
other data should be present," per
RFC 1034 section 3.6.2,
and
RFC 2181 section 10.1
confirms "there may be only one such canonical name for any one alias."
So a CNAME at the apex - or a CNAME sitting beside
the MX records - is invalid, and providers reject it for that
reason.
Second, "not found" is louder than most people assume.
RFC 8020
states that an NXDOMAIN response denies the name and every
name underneath it, so deleting a parent record takes its children
offline even when those child records still exist. The related timing
limits are in RFC 1035 section 2.3.4: a single label is at most 63 octets,
a whole domain name at most 255, and a record's TTL is "positive values of
a signed 32 bit number." That TTL is why a fix can look broken for hours
while cached answers age out - and why a stale record is not necessarily a
wrong record.
dig softwaretesttips.com A +short
dig softwaretesttips.com MX +short
dig @1.1.1.1 softwaretesttips.com AAAA
dig +trace softwaretesttips.com
The last command is the one that earns its keep: +trace walks
the delegation chain from the root down, so you see which nameserver is
actually handing out the answer you dislike. If your own resolver is the
suspect, swap in a public one - and remember that encrypted transports now
run DNS over HTTPS, which hides the query from the local network.
RFC 8484
defines that mapping of a DNS query-response pair onto an HTTP exchange,
and it is why a query that fails in dig can still work in a
browser configured for it.
Layer 2: TLS - is the certificate valid, and for the right names?
Certificate errors usually mean one of four things: the certificate expired, it does not cover the hostname being requested, the chain of signatures does not reach a trusted root, or the system clock is wrong. The SSL Checker pulls the certificate from public Certificate Transparency logs and shows the issuer, validity window, and the subject alternative names it actually covers.
Chain validation is not a single check but an algorithm: it walks from the leaf certificate through the issuers until it reaches a trust anchor, then verifies every signature and constraint along the way. RFC 5280 section 6.1 specifies that path validation - "valid paths begin with certificates issued by a trust anchor." That is why an "invalid certificate" on a perfectly current certificate is usually a missing intermediate: the leaf is fine, but the server never sent the certificate that signs it.
The classic hostname gotcha runs the other way. A certificate valid for
example.com but not www.example.com - or vice
versa - fails even though the dates are perfect, because browsers match the
requested name against the SAN list. On a shared load balancer the server
picks the certificate from the name the client sends during the handshake;
that indicator is the server_name extension defined in
RFC 6066 section 3.
Omit it - as some old clients and many bare openssl
invocations do - and you get the default certificate, which looks like a
wrong-certificate bug but is really a missing SNI argument.
Handshake cost is the other number worth knowing. RFC 8446, the TLS 1.3 specification, completes a full handshake in a single round trip, with an optional 0-RTT mode that "saves a round trip at connection setup for some application data." On a high-latency link that is a measurable difference between TLS 1.3 and an older stack, and it is worth checking before you blame the application for a slow first byte.
Two policy failures also surface as TLS problems. A site that refuses plain
HTTP for a year advertises that with
Strict-Transport-Security: max-age=31536000 - the one-year
value used throughout
RFC 6797, HTTP Strict Transport Security.
And if a certificate authority refuses to issue, check for a
CAA record: RFC 8659 defines the record that "allows a DNS
domain name holder to specify one or more Certification Authorities
authorized to issue certificates," so a leftover CAA from an
old provider blocks the new one.
openssl s_client -connect softwaretesttips.com:443 -servername softwaretesttips.com
openssl s_client -connect softwaretesttips.com:443 -servername softwaretesttips.com -showcerts Read the output bottom-up: the verification result first, then the chain, then the subject and SAN list. If the chain stops short of a known root, the server is not sending the intermediate. If the dates look impossible, check the clock on the machine doing the verifying before you file a ticket - an expiry that is off by years is a clock-skew symptom, not a certificate symptom.
Layer 3: Headers - what is the server actually saying?
When the connection works but the behaviour is wrong, read the response headers. The HTTP Header Checker fetches a URL and shows exactly what the server sent, which answers a surprising number of "why is this broken" questions. Caching is the most common answer, and RFC 9111 defines the directives that decide it.
| Directive | What it means for the cached copy |
|---|---|
max-age=600 | Reuse without revalidating for 600 seconds. |
no-cache | May be stored, but must be revalidated before every reuse. |
no-store | Must not be stored at all - the setting that removes caching as a suspect. |
must-revalidate | Never serve the stored copy once it is stale, even if the origin is down. |
private / s-maxage | Keep it out of shared caches; give the CDN its own expiry. |
stale-while-revalidate=30 | Serve the stale copy for 30 seconds while refreshing in the background (RFC 5861). |
Redirects are the second most common header bug. RFC 9110 section 15.4
defines the family, with 308 specified separately in RFC 7538: 301 and 308
are permanent, 302 and 307 are temporary, and only 307 and 308 guarantee
that a redirected POST stays a POST - a form that
silently becomes a GET is usually a 301 where a 307 was
needed.
Then confirm whether the browser is even asking for the page. Conditional
requests carry the validator the server supplied - ETag in the
response, If-None-Match in the next request - and
304 Not Modified means the client keeps its copy. When a
response varies by request header, Vary tells caches which one
mattered; a missing Vary: Origin in front of a CDN is how one
visitor's CORS headers reach another. The
HTTP Status Codes
reference explains any code you do not recognize.
curl -sSI https://example.com
curl -sS -o /dev/null -L -w 'code=%{http_code} hops=%{num_redirects} dns=%{time_namelookup}s tls=%{time_appconnect}s total=%{time_total}s\n' https://example.com The second command splits the request into its phases, which shows whether the time is going to DNS, to the TLS handshake, or to the server.
Layer 4: CORS - the one that only appears in the browser
CORS failures are the strangest to debug because curl cannot
reproduce them - the browser enforces the policy, not the server. For
anything beyond a simple request the browser first sends an
OPTIONS request and checks the answer; the WHATWG Fetch
standard defines that step as the
CORS-preflight fetch.
The CORS Tester sends a request
with a chosen Origin header and reports which
access-control-* headers actually came back.
Read the two failures differently. If
access-control-allow-origin does not match your origin, the
fix belongs in the API configuration, not the frontend - and a wildcard
* is not enough once credentials are involved. If the
preflight itself is rejected, look for a missing
access-control-allow-methods or
access-control-allow-headers: the browser asked permission for
a method the server never listed.
The runbook: four commands, in order
- Does the name resolve, and to the address you expect?
dig name A +short, then repeat against a public resolver. - Does the TLS handshake complete with a valid chain and the right SAN?
openssl s_client -servername name. - What did the server actually answer, and how long did each phase take?
curl -sS -L -wwith the timing format. - Is the browser enforcing a policy the server never granted? Re-send with an
Originheader and read theaccess-control-*response.
Nine times out of ten the answer is in one of those four places - and when it is not, you have per-layer evidence for the person upstream.
Related reading
- How to Test JSON APIs Without Writing Code - once the plumbing checks out, that guide covers the other half of API debugging: making sense of the payloads the server finally delivers.
- How to Generate Realistic Test Data for QA - reproducing an intermittent network bug usually means building a realistic reproducer payload, and that guide shows how to generate one without real user data.
- Regex Testing for Beginners - a surprising number of "network" bugs are really a validation pattern rejecting a valid hostname, an email or a header value.
- Smoke Testing vs Sanity Testing - the layer-by-layer walk here is the manual version of a smoke test; that guide explains where each check belongs in a release cycle.
- Webhook Tester - when an integration fails silently, capturing the raw delivery is the fastest way to see whether the request ever arrived intact.