Laptop to Production

Where to look first

A checklist that finds most problems in minutes.

Time
35 minutes
Level
Intermediate

Part 1: The problem

QuickCart, 3am

Kofi was on call when checkout started failing. He opened the orders service’s code, because that’s what he knew best. He read the checkout function twice. He added more logging and deployed it. He restarted the servers. Forty minutes passed.

Then Ana joined the call and asked one question: “What does the app actually get back?” The answer was a timeout — not an error from the code, just silence. That pointed straight at the network. Five minutes later they found it: a security group rule for the database had been removed that evening.

The code had never been the problem. The symptom had said so from the start.

When something breaks, the first job isn’t to fix it. It’s to find out which layer is broken — and the symptom usually tells you, if you read it exactly.

Part 2: The idea

A request passes through several layers: DNS, the network, the load balancer, the server, the app, and the app’s dependencies. Each layer fails in its own way. So the exact thing the client sees tells you where to start.

Key point: Timed out means the network. Refused means nothing is listening. 5xx means the server side. 4xx means the request.

The four symptoms

SymptomWhat happenedLook first at
Timed out, while connectingNothing answered — not even “no”. Something dropped the packetsSecurity groups, route tables, network ACLs, the address itself
Connection refusedThe machine was reached and said “nothing is listening here”Is the app running? On the right port? Listening on the right address?
5xxThe server side received the request and failedLoad balancer target health, app logs, the app’s dependencies
4xxThe server says the request was wrongThe path, the login, the input. Restarting servers won’t fix it

This is what each looks like from curl, the command-line tool for making requests:

Timed out: nothing answered
curl: (28) Failed to connect to 10.0.11.24 port 8080 after 75004 ms: Timeout was reached
Refused: reached, but nothing listening
curl: (7) Failed to connect to 10.0.11.24 port 8080 after 2 ms: Couldn't connect to server

Newer versions of curl say “Couldn’t connect to server” for a refusal; add -v to see the words Connection refused. Notice the times, too: a timeout takes seconds or minutes; a refusal comes back almost instantly.

There’s one more kind of timeout. If the connection opens but the app never answers, curl says:

Connected, but no answer
curl: (28) Operation timed out after 5006 milliseconds with 0 bytes received

Here the network worked. The app, or something the app is waiting on, is stuck. That’s a job for the logs, not the security groups.

What the load balancer’s 5xx codes mean

When the load balancer answers with a 5xx, the number tells you what went wrong between it and your servers:

CodeMeansLook at
502 Bad GatewayA server answered, but badly: it closed the connection or sent something that isn’t valid HTTPApp crashes and restarts; the target group expecting HTTP when the app speaks HTTPS, or the other way round
503 Service UnavailableThere’s no healthy server to send the request toTarget group → Targets
504 Gateway TimeoutA server didn’t answer in timeThe app’s security group; a slow app or a slow dependency
500 Internal Server ErrorUsually from your app itself, passed throughThe app’s logs

The places to look, and how to get there

Target health. The load balancer checks every server and says why it’s unhappy with each one.

In the AWS console: EC2Load BalancingTarget Groupsyour target groupTargets

Health reasonMeans
Target.TimeoutThe health check got no answer. Almost always a security group: the app’s group must allow the load balancer’s group on the app’s port
Target.ResponseCodeMismatchThe app answered, with the wrong status code: often the wrong health check path, returning 404
Target.FailedHealthChecksThe checks are failing: the app may be down, or refusing connections
Elb.InitialHealthCheckingStill being checked for the first time. Wait
Target.NotInUseThe target’s zone isn’t enabled on the load balancer

Recent changes. Most incidents follow a change. Check the last deploys, then ask CloudTrail for every change in the last hour.

In the AWS console: CloudTrailEvent history

Filter with Read only set to false. Who changed what shows how.

Logs. In Logs Insights, the first query shows the shape of the problem: when it started, when it stopped, how bad it is.

Logs Insights query
filter level = "ERROR"
| stats count() as errors by bin(1m), error_type
PartWhat it means
filter level = "ERROR"only the error lines
| stats count() as errorscount them
by bin(1m), error_typeper minute, and per kind of error

Metrics. On the load balancer’s Monitoring tab: are 5xx rising? Is response time up? Is it all servers or one? Logs and metrics explains which statistic to read.

A server that won’t boot. If it never becomes healthy, you often can’t open a shell on it either, because whatever broke the app broke that too. AWS can fetch the server’s boot output for you, without any network access to it:

In the AWS console: EC2Instancesthe instanceActionsMonitor and troubleshootGet system log

It shows what the boot script printed and where it stopped. Get instance screenshot, in the same menu, shows the server’s screen.

Network ACLs: the other firewall

Security groups are the firewall you’ll use most, but each subnet also has a network ACL. The default one allows everything, so most people never touch it. If someone has changed it, it can cause timeouts that the security groups can’t explain.

Security groupNetwork ACL
Applies toOne resource, such as a server or a load balancerA whole subnet
Remembers connectionsYes: if a request is allowed in, its reply is allowed outNo: the reply needs its own rule, both ways
Can say “deny”No, only “allow”Yes
DefaultNothing in; everything outEverything in and out

The checklist

Work down it in order. Most problems are found by step 4.

#AskWhere
1What exactly does the client see? Timeout, refused, which status code?curl -v, the app’s error, the load balancer’s metrics
2Everyone, or some? One server, one zone, one route?Logs grouped by instance_id; target health per server
3What changed recently?Deploy history; CloudTrail with Read only = false
4Is the load balancer happy with the servers?Target group → Targets, and each health reason
5What do the app’s logs say?Logs Insights: errors per minute, then by error_type
6Are the dependencies healthy?Database metrics and connections; other services’ alarms
7Is the network path open, end to end?Security groups, route tables, network ACLs, in the order the request travels

Part 3: See it

Pick the symptom, answer the follow-up question, and see where it leads. Try every path: they’re the most common ways a web service breaks.

    What does the request do?

    Start hereSomething is broken. Pick what the client actually sees. The exact symptom matters more than anything else you know so far.

    What to notice: the first choice — timeout, refused, 5xx or 4xx — already rules out most of the system. You never start by reading code. You start by reading the symptom exactly.

    Part 4: Do it

    Free — everything runs on your own computer.

    You’ll make each symptom happen on purpose, on your own machine, so you know them on sight. This lab reproduces the symptoms locally with a tiny web server; the same messages appear, with the same meanings, when the server is in AWS. About 15 minutes.

    You need python3 and curl, which come with macOS and Ubuntu, including Ubuntu on WSL. You’ll use two terminal windows.

    1. Make an empty folder and start a web server

      In window 1:

      Terminal
      mkdir quickcart-triage
      PartWhat it means
      mkdirmake a directory
      quickcart-triageits name
      Terminal
      cd quickcart-triage
      PartWhat it means
      cdmove into a directory
      quickcart-triagethe empty folder you just made, so the server has nothing to share
      Terminal
      python3 -m http.server 8080 --bind 127.0.0.1
      PartWhat it means
      python3 -m http.serverrun Python’s built-in web server, which serves the files in this folder
      8080listen on port 8080
      --bind 127.0.0.1only accept connections from this computer
      You should see
      Serving HTTP on 127.0.0.1 port 8080 (http://127.0.0.1:8080/) ...

      Leave it running. It prints a line for each request it receives.

    2. A healthy request

      In window 2:

      Terminal
      curl -i http://127.0.0.1:8080/
      PartWhat it means
      curlmake a web request
      -ialso show the response’s status line and headers
      http://127.0.0.1:8080/this computer, port 8080, the top page
      The first lines
      HTTP/1.0 200 OK
      Server: SimpleHTTP/0.6 Python/3.12.0

      Your Python version may differ. Below that is a short HTML page. 200: everything worked.

    3. A 4xx: the request was wrong

      Terminal
      curl -i http://127.0.0.1:8080/checkout
      PartWhat it means
      curl -imake a request and show the status line
      http://127.0.0.1:8080/checkouta path that doesn’t exist on this server
      The first line
      HTTP/1.0 404 File not found

      The server is fine. It answered quickly and said the request asked for something that isn’t there. Restarting it would change nothing. In window 1, the server’s own log says the same: code 404, message File not found.

    4. A 5xx: the server side failed

      Terminal
      curl -i -X POST http://127.0.0.1:8080/
      PartWhat it means
      curl -imake a request and show the status line
      -X POSTuse the POST method, like submitting a form. This little server can’t handle it
      http://127.0.0.1:8080/the top page
      The first line
      HTTP/1.0 501 Unsupported method ('POST')

      A 5xx: the request reached the server, and the server couldn’t do it. For a real app, this is when you read its logs, which is exactly what window 1 now shows: code 501, message Unsupported method ('POST').

    5. Refused: nothing is listening

      In window 1, stop the server with Control+C. Then in window 2:

      Terminal
      curl -v http://127.0.0.1:8080/
      PartWhat it means
      curlmake a web request
      -vverbose: show each step of the connection, including why it failed
      http://127.0.0.1:8080/the same address as before
      Among the lines it prints
      * connect to 127.0.0.1 port 8080 from 127.0.0.1 port 59283 failed: Connection refused
      * Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server
      curl: (7) Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server

      Your port numbers will differ. 0 ms: your computer answered at once that nothing is on port 8080. The network is fine. The process is gone.

    6. Timed out: nothing answers at all

      Terminal
      curl --connect-timeout 5 http://10.255.255.1:8080/
      PartWhat it means
      curlmake a web request
      --connect-timeout 5give up if the connection hasn’t opened after 5 seconds, instead of waiting a minute or more
      http://10.255.255.1:8080/a private address that nothing on a normal home or office network uses, so nothing will answer
      You should see
      curl: (28) Failed to connect to 10.255.255.1 port 8080 after 5006 ms: Timeout was reached

      5006 ms: curl waited the whole five seconds and heard nothing — not even a refusal. This is what a security group dropping traffic looks like from outside. On some networks you may see No route to host instead; that’s also the network.

    7. Match each symptom to its layer

      Without looking back, fill in the right-hand column on paper, then check against the idea:

      What you sawLayer to check first
      404 File not found, instantly
      501, instantly, and the server logged it
      Couldn't connect, Connection refused, 0 ms
      Timeout was reached after 5,006 ms
      You should see

      The request itself; the app and its logs; the process and its port; the network path: security groups, routes and network ACLs.

    8. Clean-up step: Clean up

      The server is already stopped. Remove the empty folder from window 1:

      Terminal
      cd ..
      PartWhat it means
      cd ..move up one folder, out of quickcart-triage
      Terminal
      rmdir quickcart-triage
      PartWhat it means
      rmdirremove a directory, which only works if it’s empty
      quickcart-triagethe folder you made
      You should see

      No output. Nothing is left running, and nothing was created in AWS.

    Part 5: Check yourself

    A request to the API hangs for 30 seconds, then fails with "Timeout was reached". Where do you look first?

    curl says "Couldn't connect to server" instantly, and curl -v shows "Connection refused". What does that tell you?

    The load balancer returns 503 to every request. What is the most likely cause?

    New servers boot, fail their health checks and get replaced in a loop. You can't open a shell on them. What do you read?

    The site was fine an hour ago. Now half the requests fail. Which question should you ask early?

    Part 6: Cheat sheet

    On one card
    Symptom or placeWhat it means, or where it is
    Timed out while connectingThe network dropped it: security groups, routes, network ACLs
    Connection refused, instantlyReached the machine; nothing listening on that port
    Connected, then timed out with 0 bytesThe app, or something it waits on, is stuck
    502 / 503 / 504 from the load balancerBad answer / no healthy targets / target too slow
    500The app failed. Read its logs
    4xxThe request was wrong. Don’t restart anything
    Target.TimeoutHealth check never answered. Check the app’s security group
    What changed?Deploys, and CloudTrail with Read only = false
    Won’t boot?Actions → Monitor and troubleshoot → Get system log
    127.0.0.1 vs 0.0.0.0This machine only vs every address
    curl -vShows each step of a connection, and why it failed

    Finished the build and the quiz? Mark it done to fill in its stop on the route map.