How a Web Request Reaches an ASP.NET Core Application

This page picks up where How a Browser Request Becomes an HTML Response leaves off. That article covers what happens once a request reaches ASP.NET Core: routing, the controller action, and the view being rendered back into HTML. This article covers everything that happens before that — the journey a request takes to find our deployed server at all, using surveysays.nickpearson.me as the example.

Flow diagram: browser sends request, DNS resolves the domain to an Elastic IP, the request reaches Nginx over HTTPS, Nginx forwards it to Kestrel on localhost, Kestrel hands it to the ASP.NET Core app, and the response travels back the same path.
The production request path for Survey Says: browser → DNS → Elastic IP → Nginx (HTTPS) → Kestrel (localhost) → ASP.NET Core MVC → response.

1. The Browser Needs an IP Address (DNS)

A browser can't send anything to a name like surveysays.nickpearson.me — the internet routes traffic using IP addresses, not domain names. So the very first step is a DNS lookup: the browser asks a DNS server "what IP address does this domain point to?" Our domain's DNS records live at Namecheap, and one of those records is an A record that maps surveysays.nickpearson.me to a specific IP address.

2. That IP Address Is an Elastic IP

The IP address the DNS record points to is an AWS Elastic IP, a static public address we allocated and attached to our EC2 instance. Without it, our server's public IP could change every time the instance restarts, which would break DNS every time that happened. The Elastic IP gives the server one address that stays the same, so the DNS record only has to be set once.

3. HTTPS and the Security Boundary

Once the browser has the Elastic IP, it opens a connection over HTTPS, meaning the traffic is encrypted using a certificate issued by Let's Encrypt. Only two ports are open to the public internet on this server: 443 for HTTPS and 80 for HTTP (which redirects to HTTPS). Our actual ASP.NET Core application is never exposed directly to the internet at all — the next section explains what stands in front of it instead.

4. Nginx Receives the Request First

The encrypted request lands on Nginx, which is running on the server and listening on ports 80 and 443. Nginx is a reverse proxy: its job is to take the incoming public request and forward it to our actual application, which is running privately on 127.0.0.1:5000 (localhost, port 5000) and cannot be reached directly from outside the server. This is deliberate. If our ASP.NET Core app were exposed to the internet directly, anyone could bypass HTTPS entirely and hit it over plain HTTP, and we'd lose Nginx's ability to handle TLS, logging, and traffic in one consistent place.

5. Kestrel Picks Up the Forwarded Request

Kestrel is the lightweight web server built into ASP.NET Core. It's what's actually listening on 127.0.0.1:5000 when Nginx forwards the request. Kestrel is the ASP.NET Core equivalent of the browser sending a request directly in a local development environment — the only difference in production is that Nginx sits in front of it instead of a request coming straight from a browser.

6. From Kestrel Into the MVC App

Once Kestrel receives the request, everything from here on is identical to what How a Browser Request Becomes an HTML Response already describes: ASP.NET Core's routing matches the URL to a controller action, the action returns a view, and that view is rendered into HTML.

7. The Response Travels Back

The rendered HTML goes back through the same chain in reverse: Kestrel hands it to Nginx, Nginx encrypts it and sends it out over HTTPS, and the browser receives and renders it. From the browser's point of view, none of this — the DNS lookup, the Elastic IP, Nginx, or Kestrel — is visible. It just looks like a normal HTTPS response.

Why This Matters

Understanding this chain makes debugging deployment issues far more manageable. A broken site could be failing at any one of these layers — DNS not resolving, the security group blocking a port, Nginx misconfigured, or the ASP.NET Core service itself down — and each layer can be checked independently rather than treating "the website is broken" as one big mystery.