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:

  1. DNS — resolves the human-readable domain name into a numeric IP address.
  2. The public internet — the browser opens a TCP connection to that IP on port 443.
  3. AWS EC2 — a Linux server hosted in the cloud, receiving the request at its Elastic IP.
  4. Nginx — terminates HTTPS and forwards the request to a private local port.
  5. Kestrel — ASP.NET Core's own web server, running as a Linux service.
  6. The ASP.NET Core application — routes the request to a controller, which returns a view (see the HTTP Cycle article for that portion).
Production request flow from browser to ASP.NET Core A vertical flow diagram. A user's browser looks up mydomain.org through a DNS resolver, which returns the Elastic IP 203.0.113.42. The browser then opens an HTTPS connection to that IP on port 443 of an AWS EC2 instance. Nginx on the EC2 terminates TLS and forwards the request via proxy_pass to Kestrel on localhost port 5000. Kestrel runs the ASP.NET Core application as a systemd service under user www-data. The application routes the request to a controller and view, and the HTML response travels back through Nginx to the browser. Browser https://mydomain.org/Demographics/Edit Step 1: name lookup DNS Resolver mydomain.org → 203.0.113.42 (Elastic IP) Step 2: HTTPS to 203.0.113.42:443 AWS EC2 Instance (Ubuntu Linux) Elastic IP 203.0.113.42 · Security group opens ports 22, 80, 443 Nginx — Reverse Proxy (port 443) Terminates TLS via Let's Encrypt certificate location / { proxy_pass http://127.0.0.1:5000; } proxy_pass over localhost Kestrel — ASP.NET Core Web Server (port 5000, localhost) Managed by systemd as SurveySays.service (User=www-data) Not reachable from the public internet ASP.NET Core Application Routing → DemographicsController → Edit action → View (See the HTTP Cycle article for MVC lifecycle detail) Step 3: HTML response back over TLS
Production request flow: browser → DNS → Elastic IP → Nginx (:443) → Kestrel (:5000, localhost) → ASP.NET Core → HTML response back to the browser.

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.org resolves to the same place as mydomain.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:

  1. TLS termination. Nginx holds the Let's Encrypt certificate and decrypts the incoming HTTPS traffic, handing plain HTTP to the layer behind it.
  2. Routing. Its configuration file maps requests for mydomain.org to a specific backend.
  3. 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.

Nginx protecting Kestrel with the reverse proxy pattern A diagram showing that Nginx on port 443 is the only public-facing process on the EC2 instance. Public internet requests reach Nginx, which forwards them via proxy_pass to Kestrel on localhost port 5000. Kestrel is not accessible from the public internet because it is bound only to 127.0.0.1 and the AWS security group does not open port 5000. Public Internet (any client, anywhere) HTTPS on port 443 AWS EC2 Instance Nginx — public-facing (port 443) Only process listening on public network interfaces Holds the Let's Encrypt certificate; terminates TLS proxy_pass http://127.0.0.1:5000 Kestrel — private (port 5000, localhost only) Bound to 127.0.0.1 — no public network interface AWS security group does not open port 5000 Not reachable from the public internet
Nginx is the only process exposed to the public internet. Kestrel binds to the loopback address and is only reachable from the same machine.

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/Edit to DemographicsController.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:

  1. The HTML leaves Kestrel over the internal HTTP connection to Nginx.
  2. Nginx wraps the response in TLS, using the same encrypted channel the browser opened.
  3. The response leaves the EC2 instance and travels across the public internet.
  4. 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.