1.2
What Is a Server? Hardware, Software and How One Machine Serves Thousands
The machine and the program: listening on a port, handling many clients at once, the kinds of server, and where servers live.
32 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Explain what a server is: a role played by a program, and the computer that program runs on.
- Describe what a server program does from the moment it starts: socket, bind, listen, accept, and why it fails with “address in use”, “connection refused” or a timeout.
- Follow one request through a server and name the resources each step uses (CPU, RAM, disk, network, file descriptors).
- Compare the ways one server handles thousands of clients at once: processes, threads, event loops and hybrids, with their failure modes.
- Say how server hardware and server operating systems differ from a PC, and why redundancy, ECC memory and remote management matter.
- Name the common kinds of server and their ports, and choose where to run one: bare metal, VM, container or serverless.
- Explain scaling, health checks and “nines” of availability, and recognise the usual ways servers fail in production.
#What a server is
In the previous lesson you saw that a client asks and a server answers. So what is the thing that answers?
The word server is used for two things, and both are correct:
- The program that waits for requests and answers them. Nginx, a Node.js app, PostgreSQL, a DNS resolver and a Minecraft server are all server programs.
- The computer that the program runs on. When people say “we bought three servers” or “the server is in Singapore”, they mean the machine.
The important idea is that “server” is a role, not a special kind of box. Any computer becomes a server the moment it runs a program that listens for requests from others. Your laptop is a server when you run npm run dev; an old Android phone can be a web server; a Raspberry Pi in a cupboard can serve a whole family’s photos. Data-centre servers are just computers built to do that job all day, every day, without stopping.
The role is also relative. A web application is a server to the browser, but it is a client of its database, of a cache and of a payment API. In a large system, almost every component is a server to someone and a client of someone else.
#The server program: a process that listens
A server program is a long-running process. A normal program (a calculator, a script) starts, does its job and exits. A server starts and then waits, sometimes for months, answering whatever arrives.
#Started and kept alive by the operating system
On Linux such background programs are called daemons (many names end in d: sshd, systemd, httpd); on Windows they are services. You rarely start them by hand. A service manager does it: systemd on almost every modern Linux, the Service Control Manager on Windows, or a container platform such as Docker or Kubernetes. The service manager:
- starts the program at boot, under a chosen user account (not root, for safety);
- restarts it if it crashes (
Restart=on-failure); - collects its output into logs (
journalctl -u myapp); - stops it politely on shutdown.
A minimal systemd unit for a Node.js server looks like this:
# /etc/systemd/system/myapp.service
[Unit]
Description=My API server
After=network.target
[Service]
User=myapp
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=PORT=8080
[Install]
WantedBy=multi-user.target
#Socket, bind, listen, accept
Only the operating system kernel may touch the network card, so a server asks the kernel for help through a short, fixed sequence of system calls. Every server ever written, in any language, does these four steps:
socket(): “give me a network endpoint”. The kernel returns a small number called a file descriptor.bind(address, port): “I want to receive connections on this IP address and port”, for example0.0.0.0:8080.listen(backlog): “start accepting connections for me”. From now on the kernel completes TCP handshakes by itself and queues finished connections, up to the backlog length.accept(): “give me the next waiting connection”. It returns a new socket just for that client; the listening socket stays open for others. If nobody is waiting, the process sleeps here, using no CPU.
Then, for each connection, the program reads the request, processes it, writes the response, logs it, and either closes the connection or keeps it open for the next request (keep-alive). Watch it happen, and the four ways it commonly goes wrong:
A server program comes to life
The program asks the operating system for a socket, claims a port, and waits. Pick a scenario to see the happy path and the four classic ways it fails.
Scenario
socket → bind → listen → accept, then one request and one response.
At boot, the service manager (systemd on Linux) starts the program as a background process. It gets a process ID (PID) and runs until someone stops it.
#Which address you listen on matters
A machine usually has several addresses: the loopback address 127.0.0.1, a LAN address such as 192.168.1.20, perhaps a public one. The address you bind to decides who can reach you:
| Bind address | Who can connect | Typical use |
|---|---|---|
127.0.0.1 (or ::1, or localhost) | Only programs on the same machine | Development servers, databases that only the local app uses, admin panels |
0.0.0.0 | Anyone who can reach any IPv4 address of the machine | Public servers |
:: | Any IPv6 address, and on most systems IPv4 too | Public servers (dual-stack) |
One specific address, e.g. 192.168.1.20 | Only traffic sent to that address | Machines with several networks, such as a public and a private one |
#Ports in one paragraph
You met ports in Addressing: the IP address gets a packet to the machine, the port gets it to the right program, and each connection is told apart by its 5-tuple. Two practical rules for servers: only one process can listen on a given IP and port (a second one gets EADDRINUSE, “address already in use”), and ports below 1024 are privileged on Linux, so a normal user cannot bind to 80 or 443. Servers solve this by starting as root and dropping privileges, by being granted the CAP_NET_BIND_SERVICE capability, or, most often, by listening on a high port such as 8080 behind a reverse proxy or load balancer that owns 443.
#When connecting fails: read the symptom
| What you see | What it means | First thing to check |
|---|---|---|
address already in use when starting the server | Another process already holds that port, often an old copy of the same server | ss -tlnp / netstat -ano to find it; stop it or change port |
| Connection refused, instantly | The machine is reachable, but nothing listens on that port and address (the kernel sends a TCP RST) | Is the process running? Is it bound to 127.0.0.1? Right port? |
| Timeout after many seconds | Packets are silently dropped: a firewall or cloud security group, or the host is down or unreachable | Firewall rules, security groups, routing, is the machine up? |
| Connects, then hangs with no response | The server accepted the connection but is stuck: overloaded, blocked, or waiting on a dependency | Server CPU, logs, slow database, thread pool (see below) |
#Inside one request
Once a connection is accepted, a typical web or API server runs the same pipeline for every request:
1. Read and parse
Read bytes from the socket and parse them into a method, path, headers and body. If TLS ends here, decrypt first. Costs CPU and a little memory for buffers.
2. Check and route
Middleware checks the basics: is the caller authenticated, over a rate limit, sending valid input? Then a router matches the path (
GET /products/42) to a handler function. Reject bad requests here, before any expensive work.3. Business logic and dependencies
The handler does the actual work, usually by calling other servers: a cache, a database, another internal service, a third-party API. While waiting for them the request uses no CPU, but it still holds memory, a connection from a pool and a file descriptor. This waiting time is usually most of the request’s total time.
4. Build and send the response
Turn the result into HTML or JSON (CPU), add headers and a status code, write it to the socket (network).
5. Log and clean up
Write an access-log line (disk or a log pipeline), record metrics, release the pooled connection, and close the socket or keep it alive.
Inside one request
What the server does between reading a request and writing the response, and what each step costs.
Scenario
Every step a typical API request goes through.
The request arrives on an accepted connection. Holding that connection costs one file descriptor and a few kilobytes of kernel buffer memory.
Every one of those resources is limited, and each one has its own way of running out:
| Resource | Used for | When it runs out |
|---|---|---|
| CPU | Parsing, TLS, templates, JSON, your code | Requests queue up and latency climbs; load average above core count |
| RAM | Buffers, per-connection state, caches, thread stacks | Swapping makes everything slow, then the OOM killer ends the process |
| Disk I/O and space | Logs, uploaded files, local databases | Slow writes block requests; a full disk makes writes fail |
| Network | Bytes in and out | Bandwidth limit reached, packets dropped, timeouts |
| File descriptors | Every open socket and file | Too many open files (EMFILE): new connections are refused |
| Pool connections | Database or upstream connections | Requests wait for a free connection, then time out |
#Serving thousands at once
A popular server has thousands of connections open at the same moment. The CPU can only run as many things at once as it has cores, so how does one machine keep thousands of conversations going? There are a few classic designs, called concurrency models.
#One at a time (iterative)
The simplest server loops: accept a connection, serve it completely, then accept the next. While it waits for the database, everyone else waits too. Fine for a toy, hopeless for real traffic.
#A process per connection
Accept a connection, then fork() a copy of the whole process to handle it. Each client gets its own process, fully isolated: if one crashes, the others are unaffected. This is how classic Unix daemons and Apache’s old “prefork” mode worked, and PostgreSQL still starts one process per database connection. The cost: each process takes several megabytes of memory and switching between hundreds of processes wastes CPU, so it tops out at hundreds to a few thousand connections.
#A thread per connection (and thread pools)
Threads are lighter than processes because they share the same memory. Each connection gets a thread, usually from a pool created in advance (for example 200 threads). The code stays simple, written top to bottom (“read, query, write”), and when a thread blocks on the database, the operating system runs another one. Classic Java servers such as Tomcat work this way.
The limits: each thread reserves its own stack (from about 1 MB to 8 MB of address space), switching costs CPU, and the pool is a hard ceiling. When every thread is busy, new requests wait in a queue, even if all the threads are only waiting on something slow. That is thread-pool exhaustion, and slow clients cause it as easily as a slow database.
#An event loop with non-blocking I/O
Instead of one thread per connection, use one thread for many connections. Sockets are switched to non-blocking mode and registered with the kernel through an efficient readiness API: epoll on Linux, kqueue on macOS and BSD, IOCP on Windows. The thread runs a loop:
- Ask the kernel: “which of my 10,000 sockets are ready?”
- For each ready one, do a little work: read what arrived, start a database query, write what is ready to send. Never wait.
- Go back to step 1.
A waiting connection costs only a few kilobytes of state, not a thread, so one loop can hold tens of thousands of mostly idle connections. Nginx, Node.js, Redis, HAProxy and Python’s asyncio are built this way.
The catch: the loop must never block. A single CPU-heavy job (resizing an image, hashing a password with a high cost, parsing a huge JSON file) or a synchronous call stops the loop, and every connection on it freezes for that time. Heavy work goes to worker threads, a background job queue or another service. Node.js, for example, quietly uses a small thread pool (4 threads by default) for file system work, DNS lookups and some crypto.
#Hybrids: one event loop per core
One event loop runs on one core. To use a 16-core machine, run 16 worker processes, each with its own event loop, all sharing the same listening port. Nginx does exactly this (worker_processes auto;), Node.js does it with its cluster module, and Python servers run several async workers. Many modern runtimes (Go, Java’s virtual threads, Rust’s Tokio) go further and let you write simple blocking-style code while the runtime multiplexes thousands of lightweight tasks onto a few threads.
How one server handles many clients
Four clients arrive at once. Each request needs about 10 ms of CPU and a 50 ms database call. Compare how each model spends its workers.
Scenario
An iterative server: one worker, everyone else waits.
All four clients connect at once. The kernel completes their handshakes and parks them in the accept queue.
| Model | Strength | Weakness | Seen in |
|---|---|---|---|
| Iterative | Trivial to write | One client at a time | Teaching examples, nc -l |
| Process per connection | Strong isolation; a crash hurts one client | Heavy memory; hundreds to low thousands of connections | Classic Unix daemons, Apache prefork, PostgreSQL |
| Thread per connection / pool | Simple blocking code; uses all cores | Stack memory and switching; pool exhaustion | Tomcat, classic Java and .NET servers |
| Event loop | Huge numbers of idle connections, little memory | Any blocking or CPU-heavy code freezes everyone; one core per loop | Node.js, Redis |
| Event loop per core | The best of both for network-heavy work | More complex; state is not shared between workers | Nginx, HAProxy, clustered Node.js |
#The C10K problem
Around 1999 engineer Dan Kegel asked how a single server could handle 10,000 concurrent connections, which the thread- and process-per-connection servers of the day could not. The answer was the event-driven design above, plus kernel APIs (epoll, kqueue) that report which of thousands of sockets are ready without scanning them all. Nginx was born from this problem; the story is in Web Servers and App Servers. Today a well-tuned machine holds millions of mostly idle connections.
#Queues, backlog and what “overloaded” looks like
Work that cannot be served right now waits in a queue, and there are queues at every level: the kernel’s accept backlog (the listen() argument; capped on Linux by net.core.somaxconn, 4096 by default on recent kernels), the thread pool’s queue, the database pool’s queue. Queues absorb short bursts. When traffic stays above capacity they only fill up, and you see, in order:
- Latency rises while throughput stays flat: requests spend longer waiting than being served.
- Timeouts start: clients give up, often retry, and add even more load (a retry storm).
- Errors: the accept queue overflows and new connections are dropped, the proxy returns 502/503/504, the process runs out of memory or descriptors.
A healthy server rejects early when overloaded (a fast 503 with Retry-After, or rate limiting) rather than accepting everything and timing out on everyone.
#Throughput, latency and Little’s law
- Latency is how long one request takes (watch the 99th percentile, the slowest 1%, not just the average).
- Throughput is how many requests are completed per second.
They are connected by Little’s law: the number of requests in progress equals the arrival rate times the time each one takes (L = λ × W). At 1,000 requests per second and 0.2 seconds each, about 200 requests are in flight at any moment, so a thread-per-request server needs at least 200 threads. If the database slows down and requests take 2 seconds, the same traffic needs 2,000: this is how one slow dependency exhausts a pool that looked generously sized.
Self-checkQuestion
A Node.js server handles 5,000 connections smoothly. After a release, every request suddenly takes about 900 ms longer, even simple health checks. CPU on one core is at 100%. What is the most likely cause?
#Server hardware: not just a big PC
Physically, a server has the same parts as a PC: CPU, RAM, storage, network card, power supply. The difference is that it is designed to run 24/7 for years, be repaired without being switched off, and be managed from far away.
| Desktop PC | Typical data-centre server | |
|---|---|---|
| Shape | Tower under a desk | Flat box in a rack: 1U (1.75 in / 44.45 mm tall) or 2U, 42 units per standard rack |
| CPU | 1 chip, 6–16 cores | 1–2 sockets, often 32–128+ cores each (Intel Xeon and similar server chips), lower GHz but many more cores |
| Memory | 16–64 GB, ordinary | 256 GB to several TB, ECC |
| Storage | 1 SSD | Many hot-swap SSD/NVMe or HDD bays, usually in RAID |
| Power | 1 power supply | 2 redundant hot-swap PSUs, on separate power feeds |
| Network | 1 port, 1 Gbps | 2+ ports at 10–100 Gbps, connected to different switches |
| Display | Monitor, keyboard, mouse | Headless: no monitor, managed over the network |
| Remote control | None | BMC with its own network port (IPMI; Dell calls it iDRAC, Hewlett Packard Enterprise calls it iLO) |
| Cooling and noise | Quiet | Loud fans, front-to-back airflow in a cooled room |
A few of these are worth understanding:
- ECC memory. RAM occasionally flips a bit by itself (electrical noise, even cosmic rays). On a PC that might crash a game once a year. On a server with a terabyte of memory running for years, it can silently corrupt a bank balance or a database page. Error-Correcting Code memory stores extra bits so it can fix single-bit errors and detect double-bit ones.
- Redundancy and hot-swap. Parts that wear out (disks, fans, power supplies) come in pairs and slide out of the front or back while the server keeps running.
- RAID. Several disks act as one so a disk failure loses nothing: RAID 1 mirrors two disks, RAID 5/6 spread parity over several disks to survive one/two failures, RAID 10 mirrors and stripes for speed. How many dead disks an array survives depends on its level, not on luck: a RAID 5 array loses data if a second disk dies before the first is replaced and rebuilt, and the rebuild of a large disk can take a day, during which the array is slower and exposed. RAID protects against a broken disk, not against deleting a file, ransomware or a fire. It is not a backup.
- Remote management. The BMC (Baseboard Management Controller) is a small separate computer on the motherboard with its own network port and power. Through it, using IPMI or the vendor’s web console, an admin can see the screen, power-cycle a frozen machine, change firmware settings and even reinstall the OS from a thousand kilometres away.
Built to survive broken parts
A server has two of everything that commonly breaks, and the system has two servers. Break a part and watch what keeps running.
Scenario
The load balancer sends user requests to both servers through switch A.
#Data centres
Servers live in data centres: buildings designed around three things servers need.
- Power: two independent feeds from the grid, UPS battery systems that bridge the gap for minutes, and diesel generators that take over for hours or days.
- Cooling: servers turn almost all their electricity into heat. Racks are arranged in hot and cold aisles, with industrial air conditioning or liquid cooling. Cooling can use nearly as much power as the servers themselves in older buildings.
- Network: several fibre connections from different providers, entering the building by different routes, plus physical security (guards, cameras, biometric doors).
#Server operating systems
Most servers on the internet run Linux: Ubuntu Server, Debian, Red Hat Enterprise Linux (RHEL) and its free rebuilds such as Rocky Linux, and Amazon Linux on AWS. You will also meet Windows Server (for Active Directory, Microsoft applications and .NET Framework apps) and FreeBSD (Netflix’s video caches and some network appliances).
What makes a server OS different is mostly what it leaves out and what it is good at:
- No graphical interface. No GUI means less memory used and less to break or attack. You manage the machine through a CLI over SSH (Windows Server is often managed with PowerShell or, carefully, RDP).
- Long support. Server releases get security updates for 5–10 years, so you are not forced to upgrade often.
- Services, logs and restarts are first-class:
systemctl status myapp,journalctl -u myapp -f, restart policies, log rotation. - Tuned for many connections and long uptime, with limits (open files, memory) you can raise.
ssh deploy@203.0.113.10 # log in to the server
systemctl status myapp # is it running? since when? last log lines
sudo systemctl restart myapp # restart it
journalctl -u myapp --since "10 min ago" # its recent logs
df -h # disk space (full disks cause many outages)
free -h # memory
#Kinds of server
“Server” describes the role, so servers are named after what they serve. One real product uses many kinds at once:
The servers behind one photo-sharing app
One product, many kinds of server, each with one job. Pick an action and watch which servers take part.
Scenario
First the browser needs an IP address. A DNS server answers “photos.example.com is 203.0.113.10”. DNS servers are servers too: they listen on UDP port 53.
| Kind | What it serves | Usual port(s) | Examples |
|---|---|---|---|
| Web server | Files and HTTP(S) responses; often also a reverse proxy | 80, 443 | Nginx, Apache, Caddy |
| Application server | Runs your business logic and produces dynamic responses | 3000, 8000, 8080 (behind a proxy) | Node.js, Spring Boot, Django, Tomcat |
| Database server | Stores data and answers queries | 5432 (PostgreSQL), 3306 (MySQL), 27017 (MongoDB) | PostgreSQL, MySQL, MongoDB |
| Cache server | Keeps hot data in memory for sub-millisecond reads | 6379 (Redis), 11211 (Memcached) | Redis, Memcached |
| DNS server | Turns names into IP addresses | 53 UDP/TCP | See the DNS lesson |
| Mail server | Sends, relays and stores email | SMTP 25 (server to server), 587 (submission); IMAP 993; POP3 995 | Postfix, Microsoft Exchange, Gmail |
| File server | Shared files and folders | SFTP 22, FTP 21, SMB 445, NFS 2049 | Samba, a NAS box |
| Object storage | Whole files (“objects”) by name over HTTPS, at huge scale | 443 | AWS S3, Google Cloud Storage, Ceph |
| Proxy / load balancer | Forwards requests to other servers, spreads load, ends TLS | 80, 443 | Nginx, HAProxy, cloud load balancers |
| Directory / authentication | Central users, groups, passwords and permissions | LDAP 389/636, Kerberos 88 | Active Directory (AD), open-source LDAP servers |
| Time server | Accurate time to keep clocks in sync | NTP 123 UDP | pool.ntp.org, time.google.com |
| Game server | The authoritative state of a multiplayer game | Usually UDP, game-specific (Minecraft 25565 TCP) | Dedicated game servers |
| Media streaming | Video and audio in small chunks | 443 (segments over HTTPS) | Video platforms, often via a CDN |
| Message broker | Queues messages between services so they can work asynchronously | Kafka 9092, RabbitMQ 5672 | Kafka, RabbitMQ |
| Print server | Shares printers and queues print jobs | 631, 9100 | Linux and Windows print services |
In one line each:
- Web servers speak HTTP to browsers and are very good at static files, TLS and holding many connections. Application servers run your code. They are often separate programs, with the web server in front. Exactly how Nginx, Apache, Caddy, Tomcat, Gunicorn and friends divide this work, and how a web server hands requests to your code, is the subject of Web Servers and App Servers.
- Database servers are the source of truth; they are usually the hardest part to scale because they hold state.
- Cache servers trade freshness for speed: they answer from memory and fall back to the database on a miss.
- Mail servers pass email from server to server with SMTP; your mail app reads it with IMAP (or the older POP3).
- File servers (SMB for Windows shares, NFS for Linux, SFTP for transfers) and object storage (simple, cheap, almost unlimited, but no editing in place) hold files.
- Proxies and load balancers sit in front of other servers; Firewalls, Proxies and VPNs covers forward and reverse proxies.
- Directory servers let one login work on every company computer and app.
- Time servers matter more than they sound: TLS certificates, logs from many machines and login tokens all break when clocks drift.
- Message brokers let a fast service hand slow work (emails, thumbnails, reports) to background workers.
#Where servers live
Every server ultimately runs on physical hardware in some building. What you choose is how much of the stack you own and manage.
#On-premises, colocation and dedicated servers
- On-premises: your own servers in your own server room. Full control and no rent, but you pay for everything up front and handle power, cooling, spare parts and 3 a.m. failures yourself. Banks and governments often keep sensitive systems this way.
- Colocation: you own the servers but rent rack space, power, cooling and network in a professional data centre.
- Dedicated / bare-metal server: you rent a whole physical machine from a hosting company (Hetzner and many others) or a cloud’s bare-metal offering. Full performance, no neighbours, but resizing means moving to another machine.
#Virtual machines and VPS
A hypervisor (KVM, Xen, Microsoft Hyper-V, VMware) splits one physical server into many isolated virtual machines (VMs), each with its own share of CPU cores, RAM and disk and its own operating system. A VPS (Virtual Private Server, from DigitalOcean, Linode, Vultr and others) and an AWS EC2 instance are both VMs. You get root access over SSH in about a minute and pay by the hour or second; the provider replaces broken hardware, often by moving your VM to another host. You still patch the OS and handle scaling.
How a hypervisor actually shares one machine (CPU rings and hardware support, overcommitting RAM and cores, how two VMs talk through a virtual switch) is explained in depth in chapter 4, Servers, Virtualization and the Cloud. For now, one VM is “one server” as far as your program can tell.
#Containers
A container is not a small VM. It is an ordinary process that the kernel isolates (with namespaces and cgroups) so it sees its own files, network and process list and gets a fixed share of CPU and memory. All containers on a machine share the host’s kernel, so a container starts in about a second and its image is megabytes, not gigabytes. Docker made containers easy: the image bundles your program with exactly the libraries it needs, so it runs the same on a laptop and in production. Containers usually run inside VMs.
When you have hundreds of containers across dozens of machines, an orchestrator decides where each one runs. Kubernetes is the standard: you declare “run 6 copies of this image, each with 1 CPU and 512 MB”, and it places them, restarts the ones that crash, replaces machines that die, and does rolling updates. Managed versions exist on every cloud, because running Kubernetes yourself is real work.
#Serverless (functions as a service)
With serverless computing (AWS Lambda, Google Cloud Functions, Azure Functions) you upload a function and the provider runs a copy for each event: an HTTP request, a file upload, a queue message. You never see a machine. It scales from zero to thousands of copies automatically, and you pay per request and per millisecond, nothing when idle.
The name is misleading: there are still servers, they are just not your problem. The trade-offs are real:
- Cold starts: when no warm copy exists, the provider must start a fresh environment first, adding from about 100 ms to more than a second.
- Limits: a Lambda invocation can run for at most 15 minutes, and nothing in memory is guaranteed to survive between calls, so state goes in a database or cache.
- Cost at scale: for steady, heavy traffic a fleet of VMs is often cheaper.
#The edge
Edge computing runs small pieces of code in a CDN’s points of presence, physically close to users: Cloudflare Workers, CloudFront Functions, Lambda@Edge. Good for redirects, authentication checks, A/B tests and personalising cached pages within a few milliseconds of the user; not for heavy work or anything that needs the main database.
Where your code runs, and who looks after each layer
Every option still runs on a physical server in a data centre. What changes is how many layers you manage yourself.
Scenario
You own or rent the whole machine.
A building with power, cooling and network. Your own server room (on-premises), or racks rented in someone else’s (colocation).
#IaaS, PaaS, SaaS, FaaS: who manages what
| You manage ↓ / Model → | On-premises | IaaS (EC2, Compute Engine) | PaaS (Heroku, App Engine, Elastic Beanstalk) | FaaS (Lambda, Cloud Functions) | SaaS (Gmail, Salesforce) |
|---|---|---|---|---|---|
| Building, power, network | You | Provider | Provider | Provider | Provider |
| Physical servers, storage | You | Provider | Provider | Provider | Provider |
| Virtualisation | You | Provider | Provider | Provider | Provider |
| Operating system, patches | You | You | Provider | Provider | Provider |
| Runtime (Node.js, Java…) | You | You | Provider | Provider | Provider |
| Scaling | You | You (with tools) | Mostly provider | Provider | Provider |
| Application code | You | You | You | You (functions only) | Provider |
| Data and users | You | You | You | You | You |
#Scaling and reliability basics
#Vertical vs horizontal scaling
Vertical scaling (scale up)Horizontal scaling (scale out)
Buy a bigger machine: more cores, more RAM, faster disks.
- Simple: no code changes, one machine to manage.
- Has a ceiling: the biggest machine money can buy.
- Still a single point of failure, and resizing usually means a restart.
- Good for databases, up to a point.
Add more machines and spread the load across them with a load balancer.
- Almost no ceiling; add capacity in minutes.
- Survives the loss of a machine.
- Requires stateless servers: any server must be able to answer any request.
- The usual way to scale web and app servers.
Horizontal scaling only works if servers do not keep a user’s session in their own memory; otherwise the next request lands on a server that does not know the user. Where that state should live (a shared cache, a database, or a signed token) is the subject of the next lesson on state, cookies and sessions.
#Load balancers, health checks and single points of failure
A load balancer is the one address clients connect to; it spreads requests across a pool of identical servers. It also runs health checks: every few seconds it calls something like GET /health on each server, and after a few failures in a row it stops sending that server traffic until it recovers. A good health endpoint is cheap, and checks that the process can actually serve (not just that it is alive).
A single point of failure (SPOF) is any part whose failure takes the whole system down: one server, one database, one load balancer, one power feed, one DNS provider, one region. High availability (HA) means finding each SPOF and duplicating it, which is why load balancers themselves run in pairs and databases have replicas.
#Availability in “nines”
Availability is the fraction of time a service works. Each extra nine cuts the allowed downtime by ten:
| Availability | Downtime per year | Per month (30 days) | Roughly means |
|---|---|---|---|
| 99% (“two nines”) | 3.65 days | 7.2 hours | One server, fixed by hand |
| 99.9% (“three nines”) | 8.76 hours | 43.2 minutes | Redundant servers, decent monitoring |
| 99.99% (“four nines”) | 52.6 minutes | 4.3 minutes | No SPOF, automatic failover, careful deploys |
| 99.999% (“five nines”) | 5.26 minutes | 26 seconds | Multiple regions, very expensive engineering |
Availabilities of components in a chain multiply: an app at 99.9% that depends on a database at 99.9% is at most about 99.8%. Duplicates in parallel do the opposite: two independent servers at 99% each are both down only 0.01% of the time.
Three terms you will hear:
- SLI (Service Level Indicator): what you measure, such as “percentage of requests that succeed in under 300 ms”.
- SLO (Service Level Objective): your internal target for it, such as “99.95% over 30 days”.
- SLA (Service Level Agreement): the promise in a customer contract, usually looser than the SLO, with refunds if it is broken.
#What goes wrong, and how you notice
Servers fail in a small number of recurring ways. Learn to recognise them:
| Problem | Symptom | Usual fix |
|---|---|---|
| Process crash (unhandled error) | 502 from the proxy, connection refused, restart in the logs | Supervisor restarts it; fix the bug from the stack trace |
| Crash loop (bad config, missing secret) | Restarts every few seconds, never healthy (CrashLoopBackOff in Kubernetes) | Read the startup logs; restarting will not help |
| Out of memory | Process killed with exit code 137, oom-kill in dmesg, saw-tooth memory graph | Find the leak; set memory limits; add RAM |
| Disk full (very often logs) | Writes fail, database refuses to commit, odd errors everywhere | Log rotation, alerts at 80%, clean up; df -h, du -sh |
| Too many open files | EMFILE errors, new connections refused under load | Raise the descriptor limit; find descriptor leaks |
| CPU at 100% | Latency rises for everyone; load average far above core count | Profile; move heavy work off the request path; scale out |
| Slow dependency | Requests pile up, pools exhaust, timeouts, 504s | Timeouts on every call, circuit breakers, caches |
| Bad deploy | Error rate jumps right after a release | Roll back first, investigate second |
When the server process dies, and when you replace it on purpose
A supervisor restarts what crashes; a careful deploy drains connections first so nobody notices.
Scenario
Normal traffic.
#Graceful shutdown and connection draining
Every deploy replaces running server processes. Done carelessly, it cuts requests in half. The careful sequence:
- Tell the load balancer to stop sending new requests to this server (draining or deregistration; AWS load balancers wait up to 300 seconds by default for in-flight requests).
- Send the process
SIGTERM. It stops accepting, finishes in-flight requests, closes database connections and exits. - If it is still running after a grace period (30 seconds by default in Kubernetes), it gets
SIGKILL. - Start the new version, wait for it to pass health checks, and only then send it traffic. Repeat server by server: a rolling deploy.
#Monitoring: the four golden signals
Google’s Site Reliability Engineering (SRE) book suggests watching four numbers for every service:
- Latency: how long requests take, separately for successes and failures (a fast error is not a fast success). Watch the 99th percentile.
- Traffic: how much demand: requests per second, connections, bytes.
- Errors: the rate of failed requests (5xx, timeouts, wrong answers).
- Saturation: how full the server is: CPU, memory, disk, pool usage, queue length. Saturation warns you before latency and errors go up.
On Linux, uptime and top show the load average: the average number of processes running or waiting to run (including those stuck waiting on disk) over 1, 5 and 15 minutes. Compare it with the number of cores: a load of 8 on an 8-core machine is fully busy; 30 on 8 cores means work is queuing.
#Servers in system design
In a design interview, servers appear in almost every answer. A few habits make you sound experienced:
- Separate the roles. Draw the load balancer, the stateless app servers, the cache, the database, object storage and background workers as different boxes, because they scale and fail differently.
- Keep app servers stateless so you can add and remove them freely; put state in the database, cache or object storage.
- Do the capacity math. “10 million requests a day is about 115 per second on average, maybe 5× at peak, so about 600 per second. If one server handles 300 at our latency, we need 2, so we run at least 3 for redundancy.” Use Little’s law for connection and thread counts.
- Name the concurrency model when it matters: an event-driven proxy in front to absorb slow clients; thread pools sized for the database pool behind them.
- Remove SPOFs and say what happens when each box dies: health checks, replicas, multiple availability zones.
- Choose where it runs and why: serverless for spiky, low or unpredictable traffic; containers on Kubernetes for many services and steady load; VMs for simple fixed workloads; bare metal for extreme performance or cost at scale.
- Mention operations: timeouts on every outbound call, graceful shutdown, the golden signals and an SLO.
#Try it yourself
1. Turn your computer into a web server in one line. Python ships with a tiny file server. In any folder:
python -m http.server 8000 # serves the current folder on all addresses
python -m http.server 8000 --bind 127.0.0.1 # now ONLY this computer can reach it
Open http://localhost:8000 in your browser. Then find your LAN address (ipconfig on Windows, ip addr on Linux, ipconfig getifaddr en0 on macOS) and open http://<that-address>:8000 from your phone on the same Wi-Fi. With the first command it works (Windows may ask you to allow Python through the firewall); with --bind 127.0.0.1 the phone gets an error. You have just reproduced the “works on my machine” bug.
2. Watch the conversation with curl:
curl -v http://localhost:8000/ # see the connect, the request and the response headers
curl -v http://localhost:8001/ # nothing listens on 8001: "Connection refused", instantly
3. Write a server yourself. Save as server.js and run node server.js:
const http = require('node:http');
let count = 0;
const server = http.createServer((req, res) => {
count += 1;
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`); // the access log
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ hello: 'world', path: req.url, requestNumber: count }));
});
server.listen(8080, '0.0.0.0', () => console.log('listening on :8080'));
// Graceful shutdown: stop accepting, finish in-flight requests, then exit.
process.on('SIGTERM', () => server.close(() => process.exit(0)));
Start it a second time in another terminal and you get EADDRINUSE. Then add a busy loop at the top of the handler, const end = Date.now() + 3000; while (Date.now() < end) {}, restart, and open the page in two tabs at once: the second tab waits for the first, because you blocked the event loop.
4. See which programs are listening:
# Linux
ss -tlnp # t=TCP, l=listening, n=numeric, p=process
# macOS
lsof -iTCP -sTCP:LISTEN -n -P
# Windows
netstat -ano | findstr LISTENING
Get-NetTCPConnection -State Listen | Sort-Object LocalPort
Look at the local address column: 0.0.0.0:8080 or [::]:8080 is reachable from the network, 127.0.0.1:8080 only from this machine.
5. The simplest server of all. Netcat listens on a port and prints whatever arrives:
nc -l 9000 # macOS / modern Linux (traditional netcat: nc -l -p 9000)
# in another terminal:
curl http://localhost:9000/hello
You will see curl’s raw HTTP request appear in the first terminal. Type a reply by hand and press Ctrl+C: you were the server.
6. Watch resources. Run htop (Linux/macOS) or open Task Manager → Performance (Windows) while you hit your server in a loop (for i in $(seq 1000); do curl -s localhost:8080 > /dev/null; done). Watch CPU per core, memory and the process list.
#Summary
Key takeaways
- A server is a role: a program that waits for and answers requests, and by extension the computer it runs on. Any machine can be one, and a server is often a client of other servers.
- A server program is a long-running process started and restarted by a service manager. It calls socket → bind → listen → accept, then reads, processes, writes and logs each request.
- The bind address decides who can connect:
127.0.0.1is local only,0.0.0.0/::is everyone. Refused means nothing listens; timeout means something drops the packets;EADDRINUSEmeans the port is taken. - Each request consumes CPU, memory, I/O, a file descriptor and pooled connections; any of them can run out.
- Concurrency models: process per connection, thread per connection / pool, event loop with epoll/kqueue/IOCP, and one event loop per core. Threads suffer pool exhaustion; event loops must never block.
- Overload shows up as rising latency, then timeouts, then errors. Little’s law: in-flight requests = arrival rate × time per request.
- Server hardware is built for 24/7 work and repair while running: many cores, ECC RAM, RAID, redundant PSUs and NICs, and a BMC for remote power control. Most servers run headless Linux managed over SSH.
- Servers are named after what they serve (web, app, database, cache, DNS, mail, file, object storage, proxy, directory, time, broker…), each with well-known ports.
- They run on bare metal, VMs, containers or serverless functions; IaaS → PaaS → FaaS → SaaS hands more layers to the provider.
- Scale out with stateless servers behind a load balancer with health checks, remove SPOFs, measure with the golden signals, and deploy with draining and graceful shutdown.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
Which sentence best describes what a server is?
Self-checkQuestion
In which order does every server program ask the kernel for a listening network endpoint?
Self-checkQuestion
A server should be reachable only by programs on the same machine. Which address should it bind to?
Self-checkQuestion
How does Nginx use all the cores of a 16-core machine?
Self-checkQuestion
What does ECC memory do that ordinary PC memory does not?
Self-checkQuestion
About how much downtime per year does 99.99% availability allow?
Self-checkQuestion
Which port does a PostgreSQL server listen on by default?
Self-checkQuestion
Choose two. Which statements about containers are true?
Choose 2
Self-checkQuestion
You start a server on a cloud VM. curl http://localhost:8080 on the VM works, but from your laptop the same port gives “connection refused” immediately. What is the most likely cause?
Self-checkQuestion
An API takes 0.5 s per request and receives 400 requests per second. Its server uses one thread per request from a pool of 100 threads. What happens?
Self-checkQuestion
Which statement about serverless platforms such as AWS Lambda is correct?
Self-checkQuestion
In the restaurant picture of a web request (you, the waiter, the kitchen), you open youtube.com and a video page appears. Which mapping is right?
Self-checkQuestion
A team says: our server has RAID, so even if two or three disks burn out while it runs, no data is lost and it never stops. The array is RAID 5 on six disks. What is true?
Self-checkQuestion
Your company has one big physical server and wants to run its website, its database and its mail server as separate machines with separate operating systems, without buying more hardware. What makes that possible?
Self-checkQuestion
You start your API with node server.js and it exits at once with EADDRINUSE. What does that mean?
Self-checkQuestion
Your API runs on a cloud VM, bound to 0.0.0.0:8080, and curl on the VM works. From your laptop, curl waits about 20 seconds and then reports a timeout. What is the most likely cause?
Self-checkQuestion
An API receives 2,000 requests per second and each takes 50 ms. By Little’s law, about how many requests are in flight at any moment?
Self-checkQuestion
A web app is 99.9% available. Every request also needs a database and a cache, each 99.9% available and failing independently. What is the best the whole chain can reach?
Self-checkQuestion
Two identical, independent servers each have 99% availability, and a load balancer sends traffic to whichever is healthy. Ignoring the load balancer, what is the availability of the pair?
Self-checkQuestion
A service expects 10 million requests a day, with peaks five times the average. One server handles 300 requests per second at the target latency. How many servers should you run?
Self-checkQuestion
After a config change, a Kubernetes pod restarts every few seconds and is never healthy; the status shows CrashLoopBackOff. What should you do first?
Self-checkQuestion
A Java service dies every few hours. Its exit code is 137, dmesg shows oom-kill, and the memory graph climbs steadily and then drops to zero. What is happening?
Self-checkQuestion
A server holds 5,000 keep-alive connections. Under load, new connections fail and the log shows Too many open files. The process limit is the common default of 1024. What is the fix?
Self-checkQuestion
Choose two. Which of these are among Google’s four golden signals for monitoring a service?
Choose 2
Self-checkQuestion
A thread-per-request server has a pool of 200 threads and handles 500 requests per second at 0.1 s each. The database slows down and each request now takes 2 s. What happens?
Self-checkQuestion
Choose two. Which statements about an event-loop server such as Node.js are true?
Choose 2
Self-checkQuestion
Choose two. A design has one load balancer, two app servers behind it, one database with no replica, and two independent power feeds. Which parts are single points of failure?
Choose 2
Self-checkQuestion
A serverless function answers in about 80 ms, but the first request after a quiet hour takes 1.2 seconds. What is the most likely explanation?
Self-checkQuestion
Choose two. Which statements about serverless functions such as AWS Lambda are true?
Choose 2
Self-checkQuestion
Every deploy cuts some requests in half. Which shutdown sequence avoids that?
Self-checkQuestion
The notes say a server is an extremely powerful computer that is never switched off. Which statement corrects this best?
Self-checkQuestion
Which of these does RAID NOT protect you against?