Server-Side Execution Models

The Challenge

Web servers were originally designed to serve static files. When a browser requests /about.html, the server finds that file and sends it back. Simple.

But what about dynamic content? Shopping carts, user authentication, database queries? The server needs to run code to generate responses. The question is: how does that code execute?

Static File Serving: ┌─────────┐ GET /page.html ┌─────────┐ read file ┌──────────┐ │ Browser │ ───────────────────────▶│ Apache │ ─────────────────▶│ page.html│ │ │ ◀─────────────────────── │ │ ◀───────────────── │ │ └─────────┘ HTML response └─────────┘ file contents └──────────┘ Dynamic Content (the question): ┌─────────┐ GET /cart.php ┌─────────┐ ??? ┌──────────┐ │ Browser │ ───────────────────────▶│ Apache │ ─────────────────▶│ PHP Code │ │ │ ◀─────────────────────── │ │ ◀───────────────── │ │ └─────────┘ HTML response └─────────┘ HTML output └──────────┘ How does this happen?

The Execution Models

Over the decades, several approaches have emerged. Each makes different trade-offs between simplicity, performance, and resource usage.

Model How It Works Examples
Fork-Exec CGI Server spawns a new process for each request C programs, Perl scripts, early web
Server Module Interpreter embedded in the server process mod_php, mod_perl
Application Server Persistent process handles many requests Node.js, Python WSGI/ASGI, Java Servlets
Serverless/FaaS Cloud platform manages execution AWS Lambda, Cloudflare Workers

1. Fork-Exec CGI (Common Gateway Interface)

The original (1993) solution. When a request arrives for a CGI script, the web server:

  1. Forks a child process
  2. Sets up environment variables (QUERY_STRING, REQUEST_METHOD, etc.)
  3. Execs the program (replacing the child with the script)
  4. Captures stdout as the HTTP response
  5. Process exits when done
CGI Execution Flow: ┌─────────────────────────────────────────┐ Request 1 ──▶ Apache ──▶ fork() ──▶│ Child Process │ │ exec("/cgi-bin/script.pl") │ │ Read ENV: QUERY_STRING, REQUEST_METHOD │ │ Execute script logic │ │ Print HTTP headers + body to stdout │ │ exit(0) │ └─────────────────────────────────────────┘ │ ▼ Response to client Request 2 ──▶ Apache ──▶ fork() ──▶ [New process, starts fresh] Request 3 ──▶ Apache ──▶ fork() ──▶ [New process, starts fresh]
Pros: Complete isolation between requests. Any language works. Simple to understand.
Cons: High overhead (process creation is expensive). No shared state between requests.

2. Server Module (Embedded Interpreter)

Instead of forking a new process, the interpreter runs inside the web server. Apache's mod_php is the classic example.

Module Execution (mod_php): ┌──────────────────────────────────────────────────────────┐ │ Apache Process │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ mod_php (embedded PHP) │ │ │ │ │ │ │ │ Request 1 ──▶ Parse cart.php ──▶ Execute ──▶ Output│ │ │ │ Request 2 ──▶ Parse user.php ──▶ Execute ──▶ Output│ │ │ │ Request 3 ──▶ Parse cart.php ──▶ Execute ──▶ Output│ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ (Same process handles many requests, no fork overhead) │ └──────────────────────────────────────────────────────────┘
Pros: Much faster (no fork per request). Can use opcode caching.
Cons: Language tied to server. Memory shared (potential security issues). Crashes can affect server.

3. Application Server (Persistent Process)

The application runs as its own long-lived process. The web server (or load balancer) proxies requests to it.

Application Server (Node.js example): ┌─────────┐ ┌─────────┐ ┌─────────────────────────────┐ │ Nginx │ ──────── │ Proxy │ ──────── │ Node.js Process │ │ (port │ forward │ to │ │ │ │ 80) │ ──────▶ │ :3000 │ ──────▶ │ const app = express(); │ └─────────┘ └─────────┘ │ app.get('/', (req, res) => │ │ res.send('Hello'); │ │ }); │ │ app.listen(3000); │ │ │ │ [Runs continuously] │ │ [Handles many requests] │ │ [Maintains state in memory]│ └─────────────────────────────┘
Pros: Excellent performance. In-memory caching. WebSocket support. Full control over request handling.
Cons: Must manage process lifecycle. Need reverse proxy for HTTPS/load balancing.

Comparison

Aspect CGI Module (mod_php) App Server (Node)
Startup per request Full process None None
Memory isolation Complete Partial None
State between requests Impossible Limited Easy (in-memory)
Requests per second Low (~100s) High (~1000s) Very High (~10,000s)
Crash impact One request One Apache worker All requests
Deployment Drop files Drop files Process manager

Working Demos

See each execution model in action. All demos show the same basic operations implemented in different languages.

Perl - Traditional CGI

Classic fork-exec CGI using Perl scripts.

C - Compiled CGI

CGI programs compiled from C. Maximum performance for the CGI model.

PHP - Server Module

PHP running via mod_php (embedded in Apache). No fork overhead.

Note: While PHP files are in /cgi-bin/ for organizational consistency, they're actually handled by mod_php (the server module model), not traditional CGI fork-exec.

Historical Context

The evolution of server-side execution reflects the web's growth:

Each model didn't replace the previous one entirely - they coexist based on use case requirements.


Back to Home | State Management | PHP Overview