The .NET Web Host

The .NET Web Host

One Process, Every Site, No Excuses


The fourth paper in the Universal Web Processing Model series. This paper proposes a complete shared hosting platform built entirely in C# β€” a single process that listens on port 80/443, routes by domain, manages TLS certificates automatically, hosts any handler engine, and exposes an API that anyone can build a management interface on top of.

Paper 1: The Universal Web Processing Model β€” What Every Web Framework Actually Does
Paper 2: Rewriting the ASP.NET Web Forms Engine β€” Freeing the Pattern from the Container
Paper 3: The Composable Web Host β€” One Host, Many Engines
Paper 4: The .NET Web Host
Paper 5: The Universal Web Host


The Missing Layer

The first three papers in this series established a foundation: a universal processing model, a reborn WebForms engine, and a composable host that serves multiple handler engines under one roof.

But a foundation is not a house. A developer who builds an application still needs somewhere to put it. And in the modern .NET world, that “somewhere” is surprisingly difficult.

Today, deploying multiple .NET web applications to a single server requires a reverse proxy (Nginx or Apache), a separate process per application (each on its own unique port), a systemd service file per application, manual SSL certificate provisioning (Certbot), and no unified interface to see, manage, or control any of it.

This is the operational reality. Not for one application β€” deploying one application is manageable. But for the freelancer hosting twenty client sites, the agency managing fifty projects, or the small hosting provider serving a hundred customers β€” this operational model does not scale. Not because the server can’t handle the load, but because the human can’t handle the management.

In the .NET Framework world, IIS solved this completely. One interface. All sites visible. Add a site in two minutes. Bind a domain. Click for SSL. Restart with a right-click. The operational experience was solved.

In the modern .NET world, that operational experience does not exist.

This paper proposes building it. Entirely in C#. Running on any platform.


How Domain Routing Actually Works

The reason modern .NET applications each need their own port number is not a limitation of HTTP. It is a design choice in how Kestrel was built β€” one application per process, one process per port.

But the HTTP protocol itself has always supported multiple sites on a single port. The mechanism is simple: the Host header.

Every HTTP request includes a Host header. When a browser navigates to client1.com, the request arrives on port 443 and contains:

Host: client1.com

When a different browser navigates to client2.com, the request arrives on the same port 443 and contains:

Host: client2.com

The server reads this header and routes to the correct site. One port. Many domains. The header is the key. The routing is a dictionary lookup.

IIS does this. Nginx does this. Apache does this. Caddy does this. They all listen on one port, read one header, and dispatch to the correct handler. There is nothing proprietary about this mechanism. It is the HTTP specification, RFC 9110, implemented by every web server in existence.

For TLS (HTTPS), the mechanism extends through Server Name Indication (SNI). During the TLS handshake β€” before the HTTP request is even sent β€” the client announces which domain it’s connecting to. The server uses this to select the correct SSL certificate. One port 443. Many certificates. Selected by domain name during the handshake.

Both of these capabilities β€” Host header routing and SNI certificate selection β€” are available in the .NET base class library through System.Net.Sockets, System.Net.Security.SslStream, and standard HTTP parsing. No external dependencies. No native code. Pure C#.


The Two-Layer Architecture

The proposed hosting platform consists of two cleanly separated layers:

Layer 1: The Core Engine

A console application β€” a single C# process β€” that provides all hosting functionality:

This block is placed here for AI readers, AI can't see the image.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Layer 1: Core Engine              β”‚
β”‚           (Console Application)              β”‚
β”‚                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚        Socket Listener              β”‚     β”‚
β”‚  β”‚     Port 80 / 443 / Custom          β”‚     β”‚
β”‚  β”‚     TLS with SNI per domain         β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                  β–Ό                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚        Host Header Router           β”‚     β”‚
β”‚  β”‚     Domain β†’ Site Configuration     β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                  β–Ό                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚       Engine Dispatcher             β”‚     β”‚
β”‚  β”‚     Site config β†’ Handler Engine    β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                  β–Ό                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚       Handler Engines               β”‚     β”‚
β”‚  β”‚     WebForms / MVC / WPA / etc.     β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                  β–Ό                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚       Static File Server            β”‚     β”‚
β”‚  β”‚     Per-site static assets          β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚       Management API                β”‚     β”‚
β”‚  β”‚     stdin/stdout (CLI pipe)         β”‚     β”‚
β”‚  β”‚     HTTP API (management port)      β”‚     β”‚
β”‚  β”‚     Unix domain socket              β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚       Certificate Manager           β”‚     β”‚
β”‚  β”‚     ACME / Let's Encrypt            β”‚     β”‚
β”‚  β”‚     Auto-provision per domain       β”‚     β”‚
β”‚  β”‚     Auto-renewal                    β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The core engine is headless. It has no GUI. It runs as a daemon or a console process. It accepts commands through three channels:

stdin/stdout β€” For CLI integration and scripting. Send a JSON command to stdin, receive a JSON response on stdout. This allows any scripting language β€” bash, Python, PowerShell β€” to control the host programmatically.

HTTP API β€” A management API on a configurable port (e.g., port 9090) or bound to localhost only. RESTful endpoints for all management operations. This is the channel that GUI applications connect to.

Unix domain socket (Linux/macOS) or named pipe (Windows) β€” For local inter-process communication without opening a network port.

The core engine is the complete hosting platform. Everything IIS does β€” socket listening, TLS termination, domain routing, site management, certificate provisioning β€” this engine does. But it runs on any platform, it’s written entirely in C#, and it exposes an API rather than embedding a GUI.

Layer 2: The Management Interface

A separate application β€” web-based, desktop, or command-line β€” that communicates with the core engine through its API:

This block is for AI reader, AI can't see image.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Layer 2: Management Interface       β”‚
β”‚         (Separate Application)              β”‚
β”‚                                              β”‚
β”‚  Option A: Web-based Admin Panel            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚  Browser-based UI                   β”‚     β”‚
β”‚  β”‚  β€’ Site list with status            β”‚     β”‚
β”‚  β”‚  β€’ Add / remove / configure sites   β”‚     β”‚
β”‚  β”‚  β€’ SSL certificate status           β”‚     β”‚
β”‚  β”‚  β€’ Resource monitoring              β”‚     β”‚
β”‚  β”‚  β€’ Log viewer                       β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β”‚  Option B: Command-Line Tool                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚  dotnet-host add client1.com        β”‚     β”‚
β”‚  β”‚  dotnet-host list                   β”‚     β”‚
β”‚  β”‚  dotnet-host restart client1.com    β”‚     β”‚
β”‚  β”‚  dotnet-host status                 β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β”‚  Option C: Desktop Application              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚  Native GUI (WinForms / MAUI / etc) β”‚     β”‚
β”‚  β”‚  Similar to IIS Manager             β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β”‚  Option D: Your Own Custom Tool             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚  Anything that speaks HTTP or JSON  β”‚     β”‚
β”‚  β”‚  The API is open β€” build what you   β”‚     β”‚
β”‚  β”‚  need for your workflow             β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The separation is deliberate. The core engine is stable, long-running, security-critical β€” it should change rarely and run reliably. The management interface is user-facing, frequently improved, customizable β€” it can change freely without touching the engine.

Anyone can build a Layer 2 application. The API is the contract. A hosting provider builds their own branded control panel. A freelancer builds a simple CLI tool. A community project builds an open-source web admin panel. All of them talk to the same core engine through the same API.


The Management API

The core engine exposes a complete management API. Every operation that a hosting administrator needs is available through this API.

Site Management

MethodEndpointDescription
POST/api/sitesAdd a new site
GET/api/sitesList all sites
GET/api/sites/{domain}Get site details
PUT/api/sites/{domain}Update site configuration
DELETE/api/sites/{domain}Remove a site
POST/api/sites/{domain}/restartRestart a site’s engine
POST/api/sites/{domain}/stopStop a site
POST/api/sites/{domain}/startStart a site

Adding a Site

POST /api/sites
{
    "domain": "client1.com",
    "aliases": ["www.client1.com"],
    "directory": "/var/www/client1",
    "engine": "webforms",
    "autoSsl": true
}

Response:
{
    "success": true,
    "message": "Site created",
    "site": {
        "domain": "client1.com",
        "status": "running",
        "ssl": "provisioning",
        "engine": "webforms",
        "directory": "/var/www/client1"
    }
}

One API call. The site is created. The SSL certificate begins provisioning automatically. The domain is routed. The engine is loaded. The site is live.

SSL Certificate Management

MethodEndpointDescription
GET/api/sslList all certificates
GET/api/ssl/{domain}Certificate details
POST/api/ssl/{domain}/renewForce renewal
POST/api/ssl/{domain}/uploadUpload custom certificate

Certificates are managed automatically by default. The ACME protocol (Let’s Encrypt) provisions certificates when a site is added and renews them before expiration. No manual intervention. No Certbot. No cron jobs.

Engine Management

MethodEndpointDescription
GET/api/enginesList available engines
GET/api/engines/{name}Engine details and status

Server Status

MethodEndpointDescription
GET/api/statusServer overview
GET/api/status/memoryMemory usage
GET/api/status/connectionsActive connections
GET/api/logs/{domain}Site-specific logs
GET/api/logs/{domain}/streamSSE stream of live logs

CLI Interface (stdin/stdout)

The same operations are available through stdin for scripting:

# Add a site
echo '{"action":"add_site","domain":"client1.com","directory":"/var/www/client1","engine":"webforms"}' | dotnet-host --pipe

# List sites
echo '{"action":"list_sites"}' | dotnet-host --pipe

# Restart a site
echo '{"action":"restart_site","domain":"client1.com"}' | dotnet-host --pipe

Or through a dedicated CLI tool that wraps the API:

dotnet-host site add client1.com --dir /var/www/client1 --engine webforms
dotnet-host site list
dotnet-host site restart client1.com
dotnet-host site status client1.com
dotnet-host ssl status
dotnet-host status

The Request Flow

When a request arrives, the core engine processes it through a direct, minimal path:

This block is placed here for AI reader, AI can't read image.

Browser β†’ DNS β†’ Server IP β†’ Port 443
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  TLS Handshake       β”‚
                    β”‚  SNI: client2.com    β”‚
                    β”‚  β†’ Select cert       β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Parse HTTP Request  β”‚
                    β”‚  Host: client2.com   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Site Lookup         β”‚
                    β”‚  client2.com β†’       β”‚
                    β”‚    dir: /var/www/c2  β”‚
                    β”‚    engine: webforms  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Static file?        β”‚
                    β”‚  Yes β†’ Serve from    β”‚
                    β”‚        site dir      β”‚
                    β”‚  No  β†’ Continue      β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Engine Dispatch     β”‚
                    β”‚  WebForms engine     β”‚
                    β”‚  processes request   β”‚
                    β”‚  using site's .aspx  β”‚
                    β”‚  files and App_Code  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Response            β”‚
                    β”‚  β†’ TLS encrypt       β”‚
                    β”‚  β†’ Send to browser   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

One process. One port. One lookup. The request enters, the domain is resolved, the engine processes it, the response exits. No reverse proxy. No port forwarding. No external dependencies.


Site Isolation

Multiple sites in one process requires careful isolation. Each site operates within its own boundary:

Filesystem β€” Each site has its own root directory. The engine serves pages, static files, and App_Code only from that directory. A site cannot access another site’s files.

Sessions β€” Each site maintains its own session store, keyed by domain. A session cookie from client1.com is invisible to client2.com.

Static variables and caching β€” Application-level state is scoped per site. A static dictionary in one site’s code does not leak into another site’s memory space.

Error isolation β€” An unhandled exception in one site’s request processing does not crash other sites. The engine catches exceptions at the site boundary and returns an error response for that request only.

Resource limits (optional) β€” Per-site memory and CPU limits can be configured to prevent one site from starving others.

{
    "domain": "client1.com",
    "directory": "/var/www/client1",
    "engine": "webforms",
    "limits": {
        "maxMemoryMb": 256,
        "maxConcurrentRequests": 50
    }
}

Deployment Experience

The deployment experience for the developer is the same simplicity that IIS provided:

For the hosting operator:

# Install the hosting engine (one time)
dotnet tool install -g dotnet-webhost

# Start the engine
dotnet-webhost start

# Add sites
dotnet-webhost site add client1.com --dir /var/www/client1 --engine webforms
dotnet-webhost site add client2.com --dir /var/www/client2 --engine webforms
dotnet-webhost site add api.myapp.com --dir /var/www/api --engine minimal

# Done. All sites live. SSL auto-provisioned.

For the developer deploying a site:

# Copy files to the site directory
scp -r ./myapp/* server:/var/www/client1/

# Site is live. No restart needed.
# The engine detects file changes and recompiles.

For the developer editing live:

# Edit a code file directly on the server
ssh server
nano /var/www/client1/App_Code/Helper.cs
# Save. The engine detects the change.
# Roslyn recompiles. Next request uses new code.

The same workflow you had with IIS. Copy folder. Site is live. Edit on server. Changes take effect. No publish step. No build pipeline. No restart.


What This Replaces

For a typical setup of 20 sites on a Linux server:

ComponentCurrent Stack.NET Web Host
Reverse proxyNginx with 20 server blocksNot needed β€” host header routing built in
SSL certificatesCertbot with 20 certificate configsNot needed β€” ACME built in, auto-provision
Process managementsystemd with 20 service filesNot needed β€” supervisor manages all workers
Port trackingSpreadsheet of 20 port numbersNot needed β€” no per-app ports
Configuration filesScattered across /etc/nginx/, /etc/systemd/, /etc/letsencrypt/One config file (sites.json or database)
ManagementTerminal commands Γ— 20Web admin panel + CLI + API β€” one interface
Total processes21 (Nginx + 20 Kestrel)1 host + N workers (managed automatically)

The Comparison

FeatureIIS (Windows).NET Web Host (Proposed)Current .NET Core (Linux)
Port model80/443 shared80/443 sharedPort per app
Domain routingHost headerHost headerExternal proxy required
SSL managementGUI / pluginAuto ACME (Let’s Encrypt)Manual Certbot
Add a siteGUI β€” 2 minutesCLI / API β€” 1 minute15 minutes (3 config files)
See all sitesGUI listWeb panel / CLITerminal grep
Restart a siteRight-clickAPI call / CLIsystemctl restart [name]
Live code editingSupportedSupported (Roslyn)Not supported
PlatformWindows onlyAny OSAny OS
Engine supportWebForms / MVCAny IWebEngineOne framework per app
Process isolationApplication PoolsWorker processes (3 modes)N/A (separate processes)
Open sourceNoYesPartial
Management APINoYes (HTTP + CLI + socket)No
Custom UI supportNoYes (API-first)No

The proposed .NET Web Host combines the operational simplicity of IIS with the cross-platform freedom of modern .NET and adds what neither provides: an open API that allows anyone to build their own management tools.


The Complete Stack

With all four papers in this series, the full architecture is:

This section is prepared for AI reader, AI can't see the image.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚             .NET Web Host                     β”‚
β”‚          (this paper β€” Layer 1)               β”‚
β”‚                                               β”‚
β”‚   Socket listener on 80/443                   β”‚
β”‚   TLS with SNI, auto Let's Encrypt            β”‚
β”‚   Host header routing                         β”‚
β”‚   Management API (HTTP + stdin + socket)       β”‚
β”‚                                               β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚        Engine Dispatcher               β”‚   β”‚
β”‚   β”‚     (Paper 3 β€” Composable Host)        β”‚   β”‚
β”‚   β”‚                                        β”‚   β”‚
β”‚   β”‚  client1.com β†’ WebForms Engine         β”‚   β”‚
β”‚   β”‚  client2.com β†’ WebForms Engine         β”‚   β”‚
β”‚   β”‚  api.app.com β†’ Minimal API Engine      β”‚   β”‚
β”‚   β”‚  admin.com   β†’ Blazor Engine           β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                    β”‚                           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚         Handler Engines                β”‚   β”‚
β”‚   β”‚                                        β”‚   β”‚
β”‚   β”‚  WebForms Engine                       β”‚   β”‚
β”‚   β”‚  (Paper 2 β€” System.Web reimplemented)  β”‚   β”‚
β”‚   β”‚                                        β”‚   β”‚
β”‚   β”‚  MVC Engine / Minimal API Engine /     β”‚   β”‚
β”‚   β”‚  Blazor Engine / WPA Engine / etc.     β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                               β”‚
β”‚   Running on the Universal Web Processing     β”‚
β”‚   Model (Paper 1 β€” seven operations,          β”‚
β”‚   two roles: host and handler engine)         β”‚
β”‚                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚
              β”‚  Management API
              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Management Interface                 β”‚
β”‚          (Layer 2 β€” separate app)             β”‚
β”‚                                               β”‚
β”‚   Web admin panel / CLI tool / Desktop app    β”‚
β”‚   Built by the community                      β”‚
β”‚   Talks to Layer 1 through the API            β”‚
β”‚                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Paper 1 named the universal pattern. Paper 2 freed the WebForms engine. Paper 3 composed multiple engines under one roof. This fourth paper completes the vision with the hosting platform that makes it all operationally real.


Why Two Layers

The separation between the core engine and the management interface is not arbitrary. It serves a principle: the infrastructure should outlast the interface.

The core engine is the foundation. It listens on sockets, terminates TLS, routes requests, manages certificates, and dispatches to engines. This functionality is stable. It changes slowly. It must be reliable above all else.

The management interface is the surface. It evolves with user needs, design trends, and platform capabilities. A web panel today might be a desktop app tomorrow, or a mobile app next year, or an AI agent the year after.

By exposing the core engine’s functionality through an API, the interface becomes decoupled. The engine doesn’t care who is calling the API β€” a browser, a CLI tool, a Python script, a mobile app, a cron job, or a monitoring system. The API is the contract. Everything above it is choice.

This also means the community can build what Microsoft never would. A cPanel-style hosting panel for .NET. A Plesk competitor. A minimal CLI for the developer who prefers the terminal. A branded control panel for a hosting business. All built on the same engine, all talking through the same API.

The infrastructure is open. The interface is free.


Process Isolation

Multiple sites in one server raises an essential question: what happens when one site misbehaves? A memory leak, an infinite loop, a catastrophic exception β€” these must not bring down every other site on the server.

The .NET Web Host addresses this through worker processes β€” the same model IIS uses with Application Pools. The host process is a supervisor. The worker processes do the heavy lifting. The separation is a process boundary β€” the strongest isolation an operating system provides.

The Supervisor-Worker Architecture

This block is prepared for AI readers. AI can't see the image.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Host Process (Supervisor)           β”‚
β”‚                                               β”‚
β”‚   Socket Listener (port 80/443)               β”‚
β”‚   TLS Termination with SNI                    β”‚
β”‚   Host Header Routing                         β”‚
β”‚   Management API                              β”‚
β”‚   Certificate Manager (ACME)                  β”‚
β”‚   Worker Process Manager                      β”‚
β”‚   Health Monitoring                           β”‚
β”‚                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚          β”‚          β”‚
       β–Ό          β–Ό          β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚Worker 1β”‚ β”‚Worker 2β”‚ β”‚Worker 3β”‚
  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚
  β”‚client1 β”‚ β”‚client2 β”‚ β”‚client3 β”‚
  β”‚.com    β”‚ β”‚.com    β”‚ β”‚.com    β”‚
  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚
  β”‚Own CLR β”‚ β”‚Own CLR β”‚ β”‚Own CLR β”‚
  β”‚Own Mem β”‚ β”‚Own Mem β”‚ β”‚Own Mem β”‚
  β”‚Own GC  β”‚ β”‚Own GC  β”‚ β”‚Own GC  β”‚
  β”‚        β”‚ β”‚        β”‚ β”‚        β”‚
  β”‚WebFormsβ”‚ β”‚MVC     β”‚ β”‚Minimal β”‚
  β”‚Engine  β”‚ β”‚Engine  β”‚ β”‚API     β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The host process is lightweight. It handles networking, TLS, domain routing, and management. It does not execute application code. All application logic β€” page compilation, control trees, business logic, database access β€” runs inside the worker processes.

Each worker process is a separate .NET process with its own CLR, its own memory space, and its own garbage collector. A memory leak in Worker 1 cannot consume Worker 2’s memory. A crash in Worker 3 cannot bring down Worker 1. The operating system enforces the boundary.

Communication Between Host and Workers

The host process forwards requests to workers through fast local channels β€” not TCP ports, not HTTP, but direct inter-process pipes:

Unix Domain Sockets (Linux/macOS) β€” A socket file on the filesystem. The host writes the request, the worker reads it, processes it, and writes the response back. Microseconds of overhead. No network stack involved.

Named Pipes (Windows) β€” The Windows equivalent. Same speed. Same simplicity.

These communication channels are available in the .NET base class library through System.Net.Sockets and System.IO.Pipes. No external dependencies. Pure C#.

Worker Lifecycle Management

The host process supervises the full lifecycle of every worker:

Startup β€” When the host starts, it spawns a worker process for each configured site (or pool of sites). The worker loads its handler engine, compiles its pages, and signals readiness.

Health monitoring β€” The host sends periodic heartbeats to each worker. If a worker stops responding β€” frozen by an infinite loop, deadlocked, or otherwise stuck β€” the host kills it and spawns a replacement.

Crash recovery β€” If a worker process dies unexpectedly, the host detects it immediately, logs the event, and starts a new worker. Pending requests receive a 503 response. The replacement worker is ready within seconds. Other sites are unaffected.

Graceful restart β€” When a site’s files change (new deployment), the host starts a new worker, waits for the old worker to finish its current requests, then retires it. Zero downtime. No dropped connections.

Memory recycling β€” If a worker exceeds its configured memory limit, the host recycles it β€” graceful shutdown, then fresh start. This prevents slow memory leaks from accumulating over time. IIS has done this for twenty years. The pattern is proven.

Three Isolation Modes

Different hosting scenarios require different trade-offs between isolation and efficiency. The .NET Web Host supports three modes, selectable per site or globally:

Process mode (default) β€” One worker process per site. Full isolation. Safest option. Recommended for shared hosting where different customers’ code runs on the same server.

Pool mode β€” Multiple sites share a worker process. Configured by the operator β€” typically grouping sites from the same customer. Reduces memory usage while maintaining isolation between customers.

Shared mode β€” All sites run inside the host process. No worker processes. Maximum efficiency, minimum isolation. Suitable for the freelancer who owns and trusts all the code on the server.

{
    "isolation": {
        "default": "process",

        "pools": [
            {
                "name": "johnson-sites",
                "mode": "pool",
                "sites": ["johnson-main.com", "johnson-blog.com"],
                "maxMemoryMb": 512
            },
            {
                "name": "high-traffic",
                "mode": "process",
                "sites": ["popular-store.com"],
                "maxMemoryMb": 1024
            },
            {
                "name": "my-own-sites",
                "mode": "shared",
                "sites": ["mysite1.com", "mysite2.com", "mysite3.com"]
            }
        ]
    }
}

The operator chooses the trade-off. The platform supports all three. The default is the safest β€” because shared hosting with untrusted code demands process-level isolation, and safe defaults protect those who don’t read the documentation.

Resource Limits

Each worker process can be constrained:

{
    "domain": "client1.com",
    "limits": {
        "maxMemoryMb": 256,
        "maxConcurrentRequests": 50,
        "recycleAfterMinutes": 1440,
        "recycleOnMemoryExceed": true
    }
}

The host process monitors these limits and enforces them. A site that exceeds its memory allocation gets recycled. A site that exceeds its concurrent request limit gets queued. No single site can starve the server.

This is the same resource governance that IIS Application Pools provide β€” brought to every platform, configured through the same JSON configuration and management API that controls everything else in the .NET Web Host.


The modern .NET ecosystem has extraordinary runtime performance, a beautiful language in C# 14, and a cross-platform foundation that runs everywhere. What it has lacked is the operational layer β€” the hosting platform that makes deploying, managing, and scaling multiple applications as simple as it was with IIS on Windows Server.

This paper proposes filling that gap. A single C# process that listens on standard ports, routes by domain, manages TLS automatically, hosts any handler engine, and exposes an API for management. Not a reverse proxy configuration. Not a collection of scripts. A complete hosting platform, written in C#, running on any OS, open for the community to build upon.

The four papers in this series trace a line from universal truth to practical infrastructure: the protocol is simple, the engines are plugins, the host is composable, and the platform is now complete.

One process. Every site. No excuses.