Infrastructure & Reliability · Middleware — Web · Chapter 20 of 33
Web servers, reverse proxies and application servers, from zero
What happens between a customer pressing Log in and a database being asked a question — the tiers, the pools, and the two settings that decide whether the estate survives a busy morning.
Start with one click. A customer opens the bank’s website and presses Log in. Before any business logic runs, that request crosses four or five pieces of middleware, each with a different job, and each able to fail in a way that looks, from the outside, exactly like every other failure: the page does not load.
The request, hop by hop
- Name lookup. The browser asks the Domain Name System (DNS) which address serves the bank’s hostname. Covered in DNS.
- Load balancer. The address usually belongs to a load balancer, not a server. It picks a healthy web server and forwards the connection. Covered in load balancing.
- Web server. Terminates the encrypted connection, serves anything static, and passes everything else inward.
- Application server. Runs the bank’s code: checks the password, calls the authentication service, builds the page.
- Back-end systems. A database, a queue manager in front of the core banking system, or another service’s API.
What a web server actually does
The name undersells it. A modern web server does six jobs, and in a bank the first three matter most:
| Job | What it means | Why a bank cares |
|---|---|---|
| Terminate TLS | Transport Layer Security (TLS) encrypts the connection and proves the server’s identity with a certificate. The web server holds the certificate and does the decryption. | The certificate is the bank’s identity on the internet; its private key is a crown jewel. |
| Reverse proxy | Sits in front of the real servers and forwards requests to them, so the outside world only ever sees the proxy. | Internal servers, their addresses and their software versions stay hidden. |
| Filter | Reject malformed requests, enforce size limits, hand traffic to a web application firewall that blocks known attack patterns. | The first line against the attacks in web vulnerabilities. |
| Serve static content | Images, scripts and style sheets straight from disk or cache. | Keeps expensive application servers for work that needs them. |
| Balance and route | Spread requests across application servers; send /cards to one cluster and /loans to another. | Lets teams deploy independently behind one address. |
| Log | Record every request: who, what, when, how long, what status. | Often the first evidence in an incident or a fraud investigation. |
A reverse proxy is so called because it is the mirror of the proxy most people know: a forward proxy acts on behalf of clients going out to the internet, a reverse proxy acts on behalf of servers receiving traffic from it.
What an application server adds
An application server is a runtime for business code. In banks that code is overwhelmingly Java, and the application server is a long-running Java process — a Java Virtual Machine (JVM) — that hosts many applications and gives them a set of services so each does not have to build its own:
- A request pipeline — the part that turns an HTTP request into a call to your code. In Java this is the servlet container; Apache Tomcat is only this part.
- Connection pools to databases and message brokers, so an application borrows an open connection rather than opening a new one per request.
- Transactions, including ones that span a database and a message queue and must succeed or fail together.
- Messaging through a standard Java interface, so code can put a message on an IBM MQ queue without being written for IBM MQ specifically.
- Security — logins, roles and single sign-on handled by the server, not re-implemented in every application.
- Clustering and administration — many copies of the server managed as one, with a console, deployment tooling and monitoring.
The full set of these services is a published specification. It was called Java EE (Java Enterprise Edition) under Sun and then Oracle. Oracle handed it to the Eclipse Foundation in 2017, and in 2018 it was renamed Jakarta EE. A server that implements all of it — IBM WebSphere, Oracle WebLogic, Red Hat JBoss — is a full application server. Tomcat implements only the web part. That distinction drives both architecture and licence cost, and later chapters return to it.
The rule the diagram encodes
Nothing in the DMZ should hold anything worth stealing. No business logic, no database credentials, no customer data at rest. The web server there is a guarded doorway, and a doorway that is broken into should open onto one more locked door. When an audit finds application code or a database password on a DMZ host, this is the principle it has broken.
The two settings that decide a busy morning
Every application server has a thread pool — the maximum number of requests it will work on at once — and a connection pool for each database or broker it talks to. Most web-tier incidents that are not certificate expiries come down to one of these filling up, and the arithmetic that predicts it fits on one line.
Little’s law says that the number of requests in flight equals the rate at which they arrive multiplied by the time each one spends in the system. It holds for any stable queue — a bank branch, a call centre, a thread pool — and needs no assumptions about the pattern of arrivals.
The defences follow directly from the arithmetic:
- Timeouts on every outbound call, set from the downstream system’s real latency, not a default of sixty seconds. If the database hangs rather than slows, each request's time in the system becomes the timeout: 200 a second × 60 seconds is 12,000 requests in flight; with a two-second timeout it is 400.
- Timeouts that compose. If the load balancer gives up after 30 seconds but the application waits 60 for the database, the customer has gone and the thread is still working. Each outer timeout must be longer than the inner one it wraps.
- Separate pools for separate dependencies (called bulkheads, after the watertight compartments in a ship), so one slow system cannot consume every thread.
- Fail fast when a dependency is sick — a circuit breaker stops calling a system that is timing out, and returns an immediate error or a cached answer instead.
Sessions: why logging in is a middleware problem
HTTP is stateless: each request arrives with no memory of the last. A logged-in session is therefore an illusion maintained by the server — a cookie in the browser carries an identifier, and the server looks up what that identifier means. Where that lookup lives is an architectural decision with a visible consequence:
| Where the session lives | What happens when a server dies | Trade-off |
|---|---|---|
| In one server’s memory, with sticky routing (the load balancer always sends a user back to the same server) | Everyone on that server is logged out | Fast and simple; every failure and every patch is visible to customers |
| Replicated between servers in the cluster | Another server already holds a copy | Survives failure; costs memory and network traffic, and replication has its own failure modes |
| In an external store (a database or an in-memory cache) | Any server can pick the user up | Servers become interchangeable; the store must now be highly available itself |
| In a signed token the browser carries | Nothing — the server holds no session | No server state at all; revoking a token before it expires is harder |
A bank that sees customers logged out during every monthly patch has chosen the first row, whether or not anyone decided to.
Health checks and draining
A load balancer needs to know which servers to use. Two kinds of check matter. A liveness check asks whether the process is running at all. A readiness check asks whether it should be sent traffic now — it may be alive but still starting, or alive but unable to reach its database. Conflating the two sends customers to a server that is up but useless.
Draining is the other half: before a server is patched, the load balancer stops sending it new requests and lets the in-flight ones finish. A cluster patched without draining drops every request that happened to be on the node at the moment it stopped.
Check yourself