How a Web Request Reaches an ASP.NET Core Application
This article traces a real-world request from a user's browser to a Survey Says
production deployment on AWS. The path is longer than the "walking skeleton" described
in the HTTP Cycle article—in
a real deployment the request passes through DNS, the public internet, a reverse proxy,
and multiple layers of security before it ever touches C# code. Throughout this article
the domain mydomain.org is used as a generic stand-in for whichever team
member's deployment a reader might be visiting.
The Journey at a Glance
When a user types https://mydomain.org/Demographics/Edit into a browser, the
request travels through the following major components before returning as HTML:
- DNS — resolves the human-readable domain name into a numeric IP address.
- The public internet — the browser opens a TCP connection to that IP on port 443.
- AWS EC2 — a Linux server hosted in the cloud, receiving the request at its Elastic IP.
- Nginx — terminates HTTPS and forwards the request to a private local port.
- Kestrel — ASP.NET Core's own web server, running as a Linux service.
- The ASP.NET Core application — routes the request to a controller, which returns a view (see the HTTP Cycle article for that portion).
DNS and Domain Names
Every website is ultimately an IP address, but users type words instead of numbers.
DNS (Domain Name System) is the phonebook that translates between them.
When a browser needs to reach mydomain.org, it asks a DNS resolver:
"what IP address serves this domain?" The resolver returns something like
203.0.113.42, and only then can the browser open a network connection.
The domain itself was registered through SquareSpace, which acts as a
domain registrar. Inside SquareSpace's DNS configuration panel, two records point
mydomain.org and www.mydomain.org at the Elastic IP:
- An A record maps a name directly to an IPv4 address.
- A CNAME record aliases one name to another (used here so that
www.mydomain.orgresolves to the same place asmydomain.org).
An important nuance: DNS does not carry the request itself. It is only a lookup. Once the browser has the IP address, DNS is out of the picture—the browser talks directly to the server at that address.
The Public Address: Elastic IPs
The public IP address AWS assigns to a fresh EC2 instance is not stable. Every time the instance is stopped and started, AWS typically hands it a different IP. That behavior would be disastrous for a live website: users would follow a DNS record pointing to yesterday's address and reach nothing.
An Elastic IP is AWS's solution: a static IPv4 address allocated to
an AWS account and associated with a specific EC2 instance. The IP stays constant
across stop, start, and reboot cycles, so the DNS record can safely point at it once
and never need updating. Each Survey Says team member operates their own EC2 instance
with its own Elastic IP; the address 203.0.113.42 used throughout this
article is a placeholder from the IANA documentation range (RFC 5737) that stands in
for either team member's real address.
HTTPS and TLS Certificates
The browser does not send its request in plain text. It uses HTTPS, which is HTTP layered on top of TLS (Transport Layer Security). HTTPS provides two guarantees that plain HTTP does not:
- Encryption — nobody sitting between the browser and the server (an internet service provider, a coffee-shop Wi-Fi operator, a compromised router) can read the request or the response.
- Server identity — the server proves that it really is
mydomain.org, not an impostor pretending to be that domain.
The identity guarantee depends on a certificate: a cryptographically signed statement from a trusted third party called a Certificate Authority (CA). When the browser connects, the server presents its certificate; because the browser already trusts the CA, it can verify by signature that the certificate really was issued for this domain.
Survey Says uses Let's Encrypt, a free automated CA. The certificate itself was requested and installed by Certbot, a small program that runs on the EC2 instance. Certbot proves ownership of the domain by responding to an HTTP challenge on port 80, downloads the signed certificate, plugs it into the Nginx configuration, and schedules an automatic renewal every few months.
Nginx: The Reverse Proxy
The request now arrives at the EC2 instance's Elastic IP on port 443. The only process listening on the public network ports (80 and 443) is Nginx, a lightweight web server acting here as a reverse proxy.
Nginx has three responsibilities in this deployment:
- TLS termination. Nginx holds the Let's Encrypt certificate and decrypts the incoming HTTPS traffic, handing plain HTTP to the layer behind it.
- Routing. Its configuration file maps requests for
mydomain.orgto a specific backend. - Forwarding. The
proxy_pass http://127.0.0.1:5000;directive forwards the request to Kestrel on the same machine.
Why not let the ASP.NET Core application handle the public port directly? Several reasons:
- Smaller attack surface. Nginx is a mature, hardened web server. Exposing an application server directly to the internet is more risky.
- Isolation. The application listens only on
127.0.0.1:5000—the loopback interface. Even if an attacker tried to reach port 5000 directly, the AWS security group blocks it and Kestrel is not bound to any public network interface. - Flexibility. A single Nginx instance can host multiple applications behind different domains or paths.
Nginx also adds forwarded headers—X-Real-IP, X-Forwarded-For,
X-Forwarded-Proto—so the application knows the original client's IP address
and whether the outer connection was HTTPS, even though the inner hop from Nginx to
Kestrel travels over plain HTTP.
Kestrel and the ASP.NET Core Process
Behind Nginx sits Kestrel, ASP.NET Core's built-in web server. Kestrel
is the process that actually runs the compiled application
(SurveySays.dll). It was told to listen on
http://127.0.0.1:5000 via an environment variable in its service
configuration.
Kestrel does not run "just because someone typed dotnet SurveySays.dll."
It runs as a systemd service on the Linux server—defined by
SurveySays.service—which means:
- It starts automatically when the server boots.
- If it crashes, systemd restarts it after ten seconds.
- It runs as the low-privilege user
www-data, not as root. - An operator can start, stop, or inspect it using
systemctl, the same way any other Linux service is managed. - Its environment is set to
Production, which suppresses developer-friendly error pages that could otherwise leak stack traces to attackers.
Reaching the Application (Controllers and Views)
Once the request reaches Kestrel, everything from URL parsing to routing to a controller action to view rendering is described in detail by the HTTP Cycle article. In brief:
- Routing matches
/Demographics/EdittoDemographicsController.Edit(). - The controller returns a view (a Razor template).
- The Razor engine compiles the view into HTML.
The Response Path
The response now travels back the way the request came, in reverse:
- The HTML leaves Kestrel over the internal HTTP connection to Nginx.
- Nginx wraps the response in TLS, using the same encrypted channel the browser opened.
- The response leaves the EC2 instance and travels across the public internet.
- The browser decrypts the response, parses the HTML, and renders the page.
The user sees a rendered page. DNS, the Elastic IP, Nginx, Kestrel, and systemd have all done their jobs invisibly. This layered architecture—each component with a narrow, well-defined responsibility—is what turns a program that "runs on my laptop" into a real production web service.