Build the mental model
When a deployed website breaks, the instinct is to change things at random until something works — restart the server, redeploy, tweak the DNS, check the code. That wastes time and can introduce new bugs on top of the original one.
A better approach is a decision tree: a fixed sequence of yes/no checks that narrows down where in the stack the failure actually lives, moving from the outside of the system inward, one layer at a time.
1. Does the domain resolve?
If a DNS lookup fails, nothing past this point matters yet — the problem is DNS, not your application.
2. Does HTTPS work?
If DNS succeeds but the browser rejects the connection or shows a certificate warning, the issue is TLS or certificate configuration.
3. Does the server respond at all?
If DNS and HTTPS are both fine but requests never get any response, the problem is usually the hosting platform or runtime — wrong port, a crashed process, or a wrong start command.
4. Is there an application-level error?
If the server does respond, but with a 500 status or broken behavior, the bug lives inside your code, its environment variables, or its database connection.
Why the order matters
Each check eliminates an entire category of causes before you touch the next layer. Skipping ahead — digging through logs before confirming DNS and HTTPS — wastes time chasing symptoms one layer removed from the real cause.
This is a general debugging skill, not just a deployment one: confirm the outer boundary of a system before doubting the layer beneath it.
DEPLOYMENT TROUBLESHOOTING DECISION TREE
----------------------------------------
DEPLOYMENT TROUBLESHOOTING DECISION TREE
-----------------------------------------
START: "My website isn't working"
|
v
STEP 1: Does the domain resolve (DNS lookup succeeds)?
|-- NO -> DNS ISSUE (check DNS records / nameservers)
|-- YES
|
v
STEP 2: Does HTTPS load (no cert/TLS error)?
|-- NO -> TLS / CERTIFICATE ISSUE (cert expired, misconfigured)
|-- YES
|
v
STEP 3: Does the server respond at all (any status code)?
|-- NO -> PLATFORM / RUNTIME ISSUE (wrong port, crashed
| process, wrong start command)
|-- YES
|
v
STEP 4: Is there an application-level error (500, broken page)?
|-- YES -> CHECK APP LOGS / ENV VARS / DATABASE CONNECTIVITY
|-- NO -> Site is healthy - stop hereConnect it to a real scenario
Suppose a teammate deploys a new backend and reports that the site now shows a raw "500 Internal Server Error" page. The platform dashboard says the deployment succeeded and the health check is green — the process is clearly running.
Check 1: Domain resolves?
A DNS lookup for the domain returns the correct IP address. DNS is fine — move to the next check.
Check 2: HTTPS works?
The browser shows a valid padlock with no certificate warning. TLS is fine too — move to the next check.
Check 3: Server responds at all?
A direct curl request to the server returns an HTTP 500 status — not a timeout, not connection refused. The server is running and answering; it just answered with an error.
Check 4: Application-level error?
Yes — the 500 status confirms it. The next step is the platform's log viewer, not the DNS settings or the certificate panel.
What the logs show
The stack trace ends inside the database client, and a warning logged just before it notes that an expected connection-string value came back empty at startup.
Working code plus a value that's empty only in this environment is a distinct pattern from a logic bug — one worth checking in the platform's configuration before touching the code itself.
Try the working example
function diagnose({ dnsResolves, httpsWorks, serverResponds, hasAppError }) {
if (!dnsResolves) return "DNS_ISSUE";
if (!httpsWorks) return "TLS_CERTIFICATE_ISSUE";
if (!serverResponds) return "PLATFORM_RUNTIME_ISSUE";
if (hasAppError) return "APP_ERROR_CHECK_LOGS_ENV_DB";
return "SITE_HEALTHY";
}
const scenarios = [
{
name: "Blank page, DNS_PROBE_FINISHED_NXDOMAIN in browser",
checks: { dnsResolves: false, httpsWorks: false, serverResponds: false, hasAppError: false },
},
{
name: "Domain loads over HTTPS, but curl to the server times out",
checks: { dnsResolves: true, httpsWorks: true, serverResponds: false, hasAppError: false },
},
{
name: "Server returns HTTP 500 with a stack trace in the logs",
checks: { dnsResolves: true, httpsWorks: true, serverResponds: true, hasAppError: true },
},
];
for (const s of scenarios) {
console.log(`${s.name} -> ${diagnose(s.checks)}`);
}Running diagnose() against the three scenarios returns the correct category for each layer of the tree:
Blank page, DNS_PROBE_FINISHED_NXDOMAIN in browser -> DNS_ISSUE
Domain loads over HTTPS, but curl to the server times out -> PLATFORM_RUNTIME_ISSUE
Server returns HTTP 500 with a stack trace in the logs -> APP_ERROR_CHECK_LOGS_ENV_DB5-minute try-it
Before checking the answers, use the decision tree on your own: a visitor reports your site "won't load" and their browser shows a certificate warning, not a blank page. Which of the four checks catches this, and which category does it fall into? Then try a second case: the site loads instantly with no errors, but a specific button click always fails silently. Where in the tree does this failure sit, and what would you check first?
One important caution
Jumping straight into application logs before confirming DNS and HTTPS wastes time chasing symptoms of a problem that's actually one layer removed.
Treating "HTTP 500" and "connection refused" as the same kind of failure — one means the server responded with an error, the other means it never responded at all, and they need different fixes.
MDN Web Docs - DNS — Cloud & Deployment