Imagine you open your browser and type in:
https://google.comA moment later, the website is right there in front of you. From your perspective, nothing particularly interesting happened. You typed a URL, pressed Enter, and the site opened. But that simple press of Enter actually starts a journey. A journey that passes through several different systems, often faster than you can even think about what’s happening. So let’s ask a simple question:
When we enter this URL and press Enter, what actually happens?
We’re not going to answer that by memorizing a list of definitions. Instead, we’ll follow the request itself—from the moment it starts until it reaches its destination and comes back.
1. Everything Starts with the Browser
When we press Enter, the browser first needs to figure out exactly what we’re asking it to do. To us, a URL is just an address, but the browser breaks it down into several distinct parts:
https://google.com
│ │
│ └── host (domain name)
└── schemegoogle.com is the domain name we’re trying to reach.
https also tells the browser two important things. First, the protocol is HTTP, but it’s going to be carried over a connection secured with TLS. Second, since we didn’t specify a port, the browser uses the default HTTPS port: 443.
There’s one more thing we didn’t specify: the path. When no path is provided, the browser requests the root path, /.
So if we add everything we left implicit, we end up with this URL, which is effectively the same as the one we entered from the browser’s perspective:
https://google.com:443/The browser now knows which protocol to use, which port to connect to, and which page it’s looking for. But there’s still one fundamental thing it doesn’t know:
Where is this website?
A domain name is easy for us to remember, but to establish a network connection, the browser needs an IP address.
2. So... Where Is This Website?
This is where DNS comes in. DNS, or Domain Name System, can be thought of as the internet’s phone book, at least in the simplest sense: we give it a name, and it gives us an address back.
But the phone-book analogy only goes so far. DNS isn’t a single directory stored in one place. It’s a distributed system that can find the answer for a domain through multiple servers and several steps. What exactly happens during those steps is a story for another article.
There’s another important detail: DNS lookups don’t always start from scratch. Every DNS response has a validity period called TTL, and during that time, the browser, operating system, or a DNS resolver on the network can cache the previous result. If you’ve recently visited the site, the answer might come directly from one of those caches.
In any case, the result of this step is the same: the domain name is resolved to one or more IP addresses—IPv4, IPv6, or both.
Now we know where to go.
But knowing the address of a house is not the same as knocking on its door.
3. We Have the Address; Now We Need to Knock
We now have both things we need to start the connection: DNS gave us the IP address, and the URL gave us the port.
185.xxx.xxx.xxx:443To keep things simple, let’s assume this connection uses TCP. TCP provides a reliable connection between the two sides: data is delivered in the same order it was sent, and if something gets lost along the way, it can be retransmitted.
But before any actual data can be exchanged, the connection itself needs to be established. This happens through a three-step exchange known as the Three-Way Handshake:
The browser says, “I want to establish a connection,” the server responds, “I’m ready,” and the browser confirms, “Let’s start.”
The connection is now established.
But anything we send over this connection is still unencrypted. If a password or Cookie is going to travel across it, we don’t want anyone who can observe the traffic along the way to be able to read it.
And remember what our URL started with?
4. Before We Say Anything, We Secure the Connection
That https at the beginning of the URL means that before we send our HTTP request, we need to establish a secure connection. That’s where TLS comes in.
We’re not going into the details of cryptography here. For this journey, we only need to understand the three things TLS provides:
Confidentiality: Data exchanged between the browser and the server is encrypted, so others can’t read its contents.
Authentication: The browser can verify that it’s actually connected to a server that is authorized to respond for this domain.
Integrity: If someone tampers with the data in transit, the parties can detect it.
For authentication, the server presents a Certificate, and the browser verifies that it’s valid and actually issued for the domain we’re connecting to.
There’s another small but important negotiation happening during this handshake. The browser and server agree on which version of HTTP they’ll use to communicate, such as HTTP/2 or HTTP/1.1. This mechanism is called ALPN, and it’ll matter to us a few steps from now.
Browser <──── encrypted (TLS) ────> ServerWe now have a secure connection.
But so far, we’ve only prepared the path. We still haven’t told the server what we want.
5. Finally, We Say What We Want
Now the browser sends its request. If the agreed-upon HTTP version is HTTP/1.1, the simplest form of our request looks like this:
GET / HTTP/1.1
Host: google.comThose two lines already tell us quite a lot.
GET tells the server, “I want to retrieve something from you.”
/ is the path the browser added for us—it means we’re requesting the root page.
Host tells the server which domain this request is for. That might seem strange: we already connected to the server’s IP address, so why do we still need the domain name?
Because a single IP address can host many different websites, and the server needs to know which one this request is meant for. That’s why the Host header is mandatory in HTTP/1.1.
A real browser request is usually much longer. A simplified HTTP/1.1 request might look something like this:
GET / HTTP/1.1
Host: google.com
Connection: keep-alive
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml
Accept-Language: fa-IR,fa;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session_id=abc123This is just an educational example, and different browsers won’t necessarily send these exact headers in every situation. But it gives us a good idea of the overall structure:
Request Line
Headers
Empty Line
Body (optional)
For a GET request, we usually don’t have a Body. A Body is used when we need to send data to the server, such as when submitting a form.
One important detail: this text-based format is specific to HTTP/1.1. If the browser and server agreed on HTTP/2 during the TLS handshake—which is very common for modern HTTPS websites—the same information is sent using binary Frames. For example, instead of the Host header, HTTP/2 uses a :authority field.
The wire format is different, but the meaning of the request is still the same: method, path, domain, and headers. You can learn more about the differences between HTTP versions on the HTTP page.
The request has been sent.
But on the other end of the connection, who exactly is waiting for it?
6. Where Does the Request Go?
You might think the request goes directly to the application that runs the website. In a simple architecture, that can be the case.
But in many real-world architectures, there’s another layer in front of the application: a Reverse Proxy, such as Nginx.
A Reverse Proxy uses information from the request to decide where to send it. For example:
example.com/api → API service
example.com → Web app
admin.example.com → Admin panelA Reverse Proxy can do much more than just forward requests. It can handle Load Balancing across multiple Application instances, restrict access, serve static files, and terminate TLS.
That last one creates an interesting moment in our journey.
Remember how we established a secure connection with the “server” in the previous step? In an architecture like this, that server was most likely the Nginx instance sitting in front of the Application.
In other words, Nginx presents the Certificate, terminates the TLS connection, decrypts the request, and then typically forwards it to the Application over the internal network, often using plain HTTP. The Application itself may never have to deal with TLS directly.
(If there’s a CDN in front of the site, there’s another layer in the picture. In that case, the first TLS connection might be established with the CDN instead.)
Either way, the job of this layer in our story is to get the request to the place where the application code can actually process it.
Now let’s see exactly where that request enters the code.
7. How Does the Application Know What to Do?
Now we’re getting into the core of the application.
Let’s say our Web Application is built with Django. The same idea applies to FastAPI or Go—the implementation just looks different.
The request enters the Application, and the Router determines which part of the application should handle it:
GET / → home
GET /articles/42 → article_detail
POST /login → login
Our request was GET /, so it gets routed to the handler for the homepage.
Let’s say the homepage displays a list of the latest articles. Our handler might look something like this:
# views.py
from django.shortcuts import render
from .models import Article
def home(request):
# Ten most recent articles for the homepage
articles = Article.objects.order_by("-created_at")[:10]
return render(request, "home.html", {"articles": articles})
These few lines are where the entire journey finally reaches our code. The request that started in the browser and passed through DNS, TCP, TLS, and the Reverse Proxy has now arrived at this function as a request object.
There’s a subtle detail here, though: the Article.objects... line doesn’t actually hit the Database yet. Django QuerySets are lazy, so the actual query is executed only when the QuerySet is evaluated—for example, when the Template iterates over the articles.
But sooner or later, that query has to run.
And this time, the Application needs to make another request to the Database to complete the original request.
8. A Smaller Journey Within the Journey
Once the QuerySet is actually evaluated, the Django ORM turns it into a SQL query and executes it. Something like this:
SELECT id, title, created_at
FROM blog_article
ORDER BY created_at DESC
LIMIT 10;
The [:10] in Python becomes LIMIT 10 in SQL. The Database executes the query and returns the result:
id title created_at
----------------------------------------------------------------
42 What Actually Happens When You Open a Website? 2026-09-10
41 How Does DNS Actually Work? 2026-09-08
40 What Is a Reverse Proxy? 2026-09-05
The Application now has the data it needs and can work with it: filter it, combine it with other data, and ultimately decide what kind of Response to build.
In our example, Django passes the data to the home.html Template and renders it into HTML.
But a Response isn’t always HTML. An API might return JSON, while another Endpoint might return a file.
The Application doesn’t always need to query the Database either. If this list of articles was generated and cached a few seconds ago, the request might not hit the Database at all.
So this architecture:
Browser
↓
Reverse Proxy
↓
Web Application
↓
DatabaseThis is a common pattern, not the only possible architecture. The Application might use a Cache instead of the Database, send a Request to another service, or not need any external data at all for certain Requests.
For our journey, what matters is that the Application now has everything it needs to produce a Response.
9. The Response Starts Its Journey Back
The Application eventually builds an HTTP Response, which makes its way back along the same path the Request took. Again, using the HTTP/1.1 format (with a few Headers omitted to keep the example simple):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<title>Google</title>
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
</head>
<body>
<img src="/images/logo.png" alt="Logo">
<h1>Latest articles</h1>
<ul>
<li><a href="/articles/42">What Actually Happens When You Open a Website?</a></li>
<li><a href="/articles/41">How Does DNS Actually Work?</a></li>
</ul>
</body>
</html>The first line gives us the most important piece of information:
HTTP/1.1 200 OK200 means the Request was successfully processed. OK is just a human-readable description; the browser looks at the status code, and in HTTP/2 and HTTP/3, this textual description doesn’t exist at all.
If the page didn’t exist, we’d get 404 Not Found. And if something went wrong on the server side, we might get something like 500 Internal Server Error.
Then come the Headers. Content-Type tells the browser that the content it’s receiving is HTML and specifies which Encoding should be used to read it. There are also other Headers for caching, cookies, compression, and security that we’ve left out here.
After a blank line comes the Body itself—the HTML that the Template generated.
The round trip is complete:
Browser ──request──> Reverse Proxy ──> Web Application ──> Database
Browser <─response── Reverse Proxy <── Web Application <── Database
It might seem like the job is done. The Request went out, the Response came back, and the HTML arrived.
But if you look more closely at that HTML, you’ll see that this is only the beginning.
10. One Enter, Dozens of Requests
As the browser receives and processes the HTML, it discovers resources such as CSS, JavaScript, images, and fonts, and sends new Requests to fetch them. Browsers can even use mechanisms like a preload scanner to discover and fetch some resources earlier.
Remember these lines from the HTML?
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
<img src="/images/logo.png" alt="Logo">
Each of these can trigger a new Request:
GET /fonts/main.woff2
GET /styles.css
GET /app.js
GET /images/logo.png
So pressing Enter once doesn’t necessarily mean sending just one Request. A typical webpage can generate dozens of Requests.
You might be wondering: does that mean the entire DNS → TCP → TLS process starts over from scratch for every one of these files?
Fortunately, no. For subsequent Requests to the same destination, the browser can usually reuse existing Connections, so it doesn’t have to repeat the entire process for every file.
With HTTP/1.1, a browser can maintain multiple simultaneous Connections to the same destination and distribute Requests across them. With HTTP/2, multiple Requests can be sent concurrently over a single Connection.
Of course, this only applies when the Request is going to the same destination. If some files come from a different domain—for example, a CDN—that domain may require its own DNS lookup, Connection, and TLS handshake.
Each of these Requests can also have a different outcome:
┌── styles.css → browser cache
│
├── app.js → CDN
HTML ───────────┤
├── logo.png → Nginx (static file)
│
└── main.woff2 → browser cache
Even after the page has loaded, JavaScript can create additional Requests on its own. For example, app.js might fetch the user’s profile information from an API:
app.js
│
└── GET /api/profile
↓
Web Application
↓
Database
So what we casually call “opening a website” is, from the browser’s perspective, a collection of different Requests and Responses.
But there’s still one question left: how does the browser turn all these files into the page we actually see?
11. From Code to Pixels
HTML, CSS, JavaScript, images, fonts... none of them alone is what we actually see on the screen. The browser has to bring them all together.
First, it parses the HTML into a tree-like structure called the DOM, and the CSS into the CSSOM. It then combines them to determine what should be visible and which styles apply to each element.
During Layout, the browser calculates where each element should be placed and how much space it should occupy. Finally, through Paint and Compositing, it turns that information into the pixels you see on the screen.
JavaScript can also modify the DOM or styles along the way, potentially causing parts of this process to run again.
In simple terms:
HTML ──> DOM ──┐
├──> Layout ──> Paint ──> Pixels
CSS ──> CSSOM ──┘One interesting detail here is that CSS required for the initial render can delay the final display of the page. That’s why the time it takes to load styles.css can directly affect how quickly the page becomes visible.
The details of these rendering stages are a whole world of their own, which the Browser Rendering page goes into. But for our journey, this mental model is enough:
The browser doesn’t just display HTML; it processes all the resources and uses them to build the page.
And that’s the moment when the URL we typed at the very beginning finally turns into the pixels we see on the screen.
12. Let’s Look at the Whole Journey
This path is a simplified model of a web request, and in the real world, you won’t always find exactly these components in exactly this order.
A Response might come directly from the browser’s Cache or a CDN, meaning the Request never reaches the Web Application at all. And a Web Application itself might be made up of multiple Services that communicate with each other.
The connection might not even use TCP. With HTTP/3, QUIC is used instead of TCP. QUIC runs over UDP and integrates TLS into the protocol itself.
The browser may already know that the server supports HTTP/3—for example, because the server announced it in a previous Response using the Alt-Svc Header, or through an HTTPS DNS record. If a QUIC connection can’t be established for some reason, such as the network blocking UDP, the browser may fall back to TCP.
But the core idea remains the same across these variations: a Request finds its destination, establishes a secure connection, passes through several layers, gets processed somewhere, and a Response makes its way back.
And understanding this journey isn’t just an answer to a curiosity.
The next time a page loads slowly, you’ll have a better idea of where to look: maybe DNS, maybe the Handshakes, maybe a slow Database Query, or maybe the dozens of resources the page was waiting for.
Because now, when you enter a URL and press Enter, you don’t just see a webpage.
You see a Request taking a journey.