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.
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.