Internet Request Paths for Backend Developers
LESSON
Internet Request Paths for Backend Developers
By the end of this lesson, you will be able to...
Trace one HTTPS request from browser URL to backend handler and back.
Match common symptoms, such as DNS failure, TLS failure, 404, 502, and 504, to the boundary that can explain them.
Choose useful evidence before changing backend code.
Idea in one sentence: A backend request is a chain of boundaries, and good debugging starts by finding which boundary last saw the request correctly.
Core Insight
A small team ships its first public orders API.
Locally, the route works:
GET http://localhost:3000/orders/123
After deployment, users open:
https://api.example.test/orders/123
Some users get JSON. Some see a browser error. One test returns 502 Bad Gateway. Another returns 504 Gateway Timeout. The backend logs show nothing for a few failed requests.
The naive idea is simple:
The frontend calls the backend.
That sentence is useful in a product meeting. It is too blurry for debugging.
A browser cannot call a Python, Go, Ruby, Java, or Node handler directly. It resolves a name, opens a connection, checks encryption, sends HTTP bytes, crosses a proxy, reaches a runtime, enters framework routing, runs middleware, calls a handler, waits for dependencies, and receives a response through the same kind of path.
The useful model is:
client intent -> network path -> edge boundary -> application boundary -> dependency boundary -> response
Each boundary can see different evidence. Each boundary can also fail in a different way.
That is why this is the first backend lesson. Before a backend developer can fix a handler, choose a framework, tune a database, or deploy a service, they need one durable habit:
Trace the request path before choosing the fix.
The Production Symptom
Use one request through the lesson:
GET https://api.example.test/orders/123
The user expects:
200 OK
Content-Type: application/json
{"id": "123", "status": "paid"}
The deployed system is small:
browser
-> DNS
-> TCP connection
-> TLS
-> HTTP request
-> edge proxy
-> orders service
-> web framework route
-> handler
-> database
At first, this looks like a lot of parts for one beginner lesson. The trick is not to memorize every protocol detail. The trick is to ask a plain question at each step:
What could this part see, change, wait for, or decide?
DNS can see a host name and return an address. It cannot see your route handler.
TLS can check whether the certificate matches the host. It cannot decide whether order 123 exists.
The proxy can receive an HTTP request and forward it to an upstream service. It may not know why the handler is waiting unless the application logs or traces say so.
The handler can decide whether the order exists. It cannot fix a browser that never resolved the host name.
So far, the request is not a single jump. It is a sequence of ownership boundaries.
Boundary Evidence
Plain meaning:
Evidence is something a component records or shows because the request reached that component.
In this scenario:
A proxy access log proves the request reached the proxy. A backend route log proves the framework matched a route. A database timing entry proves the handler reached the database call.
Technical name:
We will call this boundary evidence: evidence that a request crossed, failed at, or waited at a specific boundary.
This term matters because user symptoms are often vague. A user may say "the API is down" for all of these:
the browser cannot resolve the host
the certificate is invalid
the proxy cannot connect to the service
the framework has no matching route
the handler is waiting on a slow database query
Those are different failures. They need different fixes.
The beginner mistake is to change the code path they know best. If you mostly write handlers, you may open the handler first. If you recently changed deployment, you may blame the proxy first. Boundary evidence slows down that reflex in a useful way.
It asks:
What is the last boundary where the request still looked healthy?
What is the first boundary where it looked wrong?
A Worked Request Trace
Trace the successful request before looking at failures.
Input:
GET https://api.example.test/orders/123
Step 1: URL parsing
scheme=https
host=api.example.test
path=/orders/123
Step 2: DNS
api.example.test -> 203.0.113.10
Step 3: TCP
client opens a connection to 203.0.113.10:443
Step 4: TLS
server presents a certificate valid for api.example.test
Step 5: HTTP
client sends GET /orders/123 with headers
Step 6: edge proxy
proxy receives host=api.example.test path=/orders/123
proxy adds request_id=req-481
proxy forwards to orders-service:3000
Step 7: framework
framework matches GET /orders/:id
middleware attaches auth and request context
Step 8: handler
handler reads id=123
handler asks database for order 123
Step 9: database
database returns one row
Output:
200 OK with JSON body
Now make the intermediate evidence visible:
| Boundary | Healthy evidence | What it can prove | What it cannot prove |
|---|---|---|---|
| DNS | host resolves to an address | the client found where to connect | the backend handler is correct |
| TLS | certificate accepted | the encrypted connection can start | the route exists |
| proxy | access log with request ID | the edge received HTTP | the database is fast |
| framework | route log for GET /orders/:id |
application routing matched | the proxy timeout is high enough |
| handler | structured log with id=123 |
application code started work | DNS was fast for the user |
| database | query timing for request ID | dependency work happened | response reached the browser |
This table is not just for incidents. It is a design tool. A backend that cannot produce boundary evidence is harder to operate, even if the code is correct.
So far, we have built the central model: a request path is a chain, and each link owns a different kind of evidence.
Where The Naive Model Breaks
Now replay the same user complaint with four different failures.
Symptom 1:
Browser says ERR_NAME_NOT_RESOLVED.
Path:
browser -> DNS fails -> no TCP -> no TLS -> no HTTP -> no proxy log -> no app log
First useful evidence:
DNS or browser network output.
Likely first owner:
domain, DNS record, local resolver, or environment name.
The backend may be perfectly healthy. It never got a chance to be wrong.
Symptom 2:
Browser warns that the certificate is invalid.
Path:
browser -> DNS ok -> TCP ok -> TLS rejected -> no HTTP request -> no handler log
First useful evidence:
certificate details, TLS error, host mismatch, expiry, trust chain.
Likely first owner:
certificate configuration or edge TLS setup.
The route handler still has no useful opinion. The request body was not sent to it.
Symptom 3:
Response is 502 Bad Gateway.
Path:
browser -> DNS ok -> TLS ok -> proxy receives HTTP -> proxy cannot reach upstream
First useful evidence:
proxy log, upstream name, connection refused, service health, port.
Likely first owner:
service process, container networking, port binding, or proxy upstream config.
This connects directly to the next lesson. A backend service must start as a process and listen on the expected port before the proxy can forward requests to it.
Symptom 4:
Response is 504 Gateway Timeout.
Path:
browser -> proxy -> framework -> handler -> database waits too long -> proxy gives up
First useful evidence:
proxy duration, upstream timeout, app request ID, database query timing.
Likely first owner:
slow handler path, slow dependency, missing index, timeout mismatch, or overload.
In this case the proxy produced the visible response, but the underlying cause may sit behind the application boundary.
Check: The backend logs show no request, and the browser says the host cannot be found. Should you inspect the handler first?
Think first, then reveal.
Answer: No. The request failed before the client could open a connection. Start with DNS evidence. The handler may still be correct.
Reading Logs As A Path
Request IDs make boundary evidence easier to connect.
A request ID is a short value attached near the edge and carried through logs:
edge:
request_id=req-481 path=/orders/123 method=GET
proxy:
request_id=req-481 upstream=orders-service:3000 upstream_status=200 duration_ms=42
app:
request_id=req-481 route="GET /orders/:id" order_id=123
database:
request_id=req-481 query=find_order_by_id duration_ms=18
edge:
request_id=req-481 status=200 total_ms=64
This is a healthy path. You can follow one request from edge to application to dependency and back.
Now compare a broken path:
edge:
request_id=req-913 path=/orders/123 method=GET
proxy:
request_id=req-913 upstream=orders-service:3000 upstream_status=timeout duration_ms=30000
app:
request_id=req-913 route="GET /orders/:id" order_id=123
database:
request_id=req-913 query=find_order_by_id duration_ms=28650
edge:
request_id=req-913 status=504 total_ms=30001
The visible response is 504, so the proxy made the final timeout decision. But the trace also shows the application spent almost all of the time waiting for the database.
That difference matters. If you only raise the proxy timeout, the user may wait longer and still get a bad experience. If you only rewrite the route, you may miss the slow query. The trace tells you where to ask the next question.
Check: In the broken path above, what is the first mitigation you would consider: increase every timeout, or inspect the slow database call?
Think first, then reveal.
Answer: Inspect the slow database call first. A timeout increase may hide the symptom, but the trace says most of the request time is spent at the database boundary.
Common Confusions
Confusion: "No backend log means the backend is innocent"
Why it is tempting:
If the application did not write a log line, it feels natural to say the backend was not involved.
Better model:
No application log only means the request did not reach the logging point you are checking. That could mean the request failed before the app. It could also mean the app crashed before logging, the log level filtered the line, the request ID was not propagated, or the log pipeline dropped the event. Use the missing log as evidence, but do not turn it into a conclusion by itself.
Ask one more question:
What is the closest earlier boundary that definitely saw this request?
If the proxy saw it and the app did not, inspect forwarding, process health, port binding, and application startup. If the proxy did not see it either, move outward to DNS, connection, TLS, or client behavior.
Confusion: "The component that returns the status code is always the root cause"
Why it is tempting:
The visible status code feels like the source of truth. If the user sees 504 Gateway Timeout, the proxy must be the problem. If the user sees 500, the handler must be the whole problem.
Better model:
The component that returns the visible result owns the last decision. It may not own the deeper cause. A proxy may return 504 because a backend dependency was slow. A handler may return 500 because a required environment variable was missing at startup and the app fell into a bad fallback. The status code tells you where the failure became visible. The trace tells you where to look next.
Confusion: "More logs always make debugging better"
Why it is tempting:
When you are missing evidence, the obvious reaction is to record everything.
Better model:
More logs can help, but unfocused logs create noise and risk. Full request bodies may contain private data. Full tokens are secrets. High-cardinality fields can make storage expensive. Repeated logs inside loops can hide the one boundary decision you needed.
Boundary evidence is narrower. It records the crossing, duration, decision, status, and correlation ID. It tries to answer "where did the request go?" before it tries to describe every byte.
Trade-offs And Limits
Tracing request paths buys clarity. It reduces random fixes. It helps a beginner avoid the most common production mistake: changing code before knowing whether the request reached the code.
It also has costs.
The main trade-off is debugging clarity versus instrumentation cost and privacy risk.
Instrumentation takes work. Logs and traces need names, request IDs, timestamps, status codes, and duration fields. Too little evidence leaves you guessing. Too much evidence can become noisy, expensive, or unsafe.
The boundary rule is a useful compromise:
Record enough to know that a request crossed a boundary,
how long it spent there,
what decision happened,
and which identifier connects it to the next boundary.
Do not log secrets. Do not log full authorization tokens. Do not dump private request bodies just because debugging is hard. Useful evidence should explain the path without exposing the user.
There is another limit. Real systems may add CDNs, caches, queues, retries, service meshes, background jobs, and multiple downstream services. This lesson uses a direct HTTPS request because it gives you the base mental model. Later backend lessons add process startup, databases, frameworks, validation, authentication, testing, deployment, and observability.
The habit remains the same:
Find the boundary.
Ask what it could see.
Collect the evidence.
Then choose the fix.
Practice
Trace this request:
POST https://api.example.test/login
The browser receives:
502 Bad Gateway
The proxy log says:
request_id=req-222 upstream=auth-service:8080 error=connection_refused
The application log for req-222 has no entry.
Write a short diagnosis using the request-path model.
A good answer should mention:
- The request reached the proxy, because the proxy created a log entry and request ID.
- The request probably did not reach the auth application, because the app has no matching log entry.
connection_refusedpoints to process, port, container, or upstream configuration before handler logic.- The first useful checks are whether
auth-serviceis running, whether it listens on8080, and whether the proxy points to the right upstream.
Resources
- [DOC] MDN: An overview of HTTP
- Link: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview
- Focus: Connect methods, paths, headers, status codes, and bodies to the shape your backend receives.
- [DOC] MDN: What is a domain name?
- Link: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_domain_name
- Focus: Study the name-to-address step that happens before backend code can see a request.
- [REFERENCE] curl: Everything curl, verbose mode
- Link: https://everything.curl.dev/usingcurl/verbose
- Focus: Practice reading connection, TLS, and HTTP evidence from one command.
- [DOC] NGINX Admin Guide: Reverse Proxy
- Link: https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
- Focus: Notice how a reverse proxy receives client HTTP and forwards it to an upstream service.
Key Takeaways
- A backend request is a chain of boundaries, not a direct call from browser to handler.
- DNS, TLS, proxy routing, framework dispatch, handler execution, and dependency calls each own different evidence.
- Similar user symptoms can come from different parts of the path, so trace before changing code.
- Request IDs help connect proxy, application, and dependency logs for one request.
- Good backend debugging asks what each boundary could see, change, wait for, or decide.
← Back to Backend Development Foundations