Upstream Blocks: Defining Backend Pools

Nginx In Depth

Chapter 4 · Upstream Blocks: Defining Backend Pools

Web Servers Fundamentals Chapter 6 showed proxy_pass pointing at a single backend address. Once there's more than one backend instance to distribute traffic across, that single address is replaced with an upstream {} block — a named pool of servers, with real per-server behavior configurable on top.

The upstream {} Block

upstream backend_pool { server 10.0.0.11:3000; server 10.0.0.12:3000; } server { location / { proxy_pass http://backend_pool; } }

upstream names a group of backend servers, listed by their own address and port. That name — backend_pool here — is then used in place of a single address inside proxy_pass. Nginx distributes incoming requests across every server line inside that block, using round robin by default (Chapter 5 covers the other algorithms available).

server Entries Inside upstream

upstream backend_pool { server 10.0.0.11:3000 weight=3; server 10.0.0.12:3000; server 10.0.0.13:3000 max_fails=3 fail_timeout=30s; server 10.0.0.14:3000 backup; }

Each server line can carry its own parameters: weight shifts the distribution — a server with weight=3 receives roughly three times the traffic of a default weight=1 server, useful when backend instances have genuinely different capacity. max_fails/fail_timeout mark a server temporarily unavailable after a set number of failed attempts within a given time window, automatically routing around it until that window passes. backup marks a server as only used once every non-backup server is unavailable. down manually takes a server out of rotation without deleting its line — useful for planned maintenance, since the config still documents that server's existence.

Keepalive to Upstream Servers

upstream backend_pool { server 10.0.0.11:3000; server 10.0.0.12:3000; keepalive 32; }

Web Servers Fundamentals Chapter 9 covered keep-alive between the client and Nginx. This keepalive directive is a separate concept: it lets Nginx reuse its own already-open connections to a backend server across multiple proxied requests, rather than opening a fresh TCP connection to the backend for every single request it forwards — a real performance win on the backend-facing side, distinct from anything covered on the client-facing side.

ParameterControls
weightProportional share of traffic this server receives
max_fails/fail_timeoutTemporarily removing a failing server from rotation
backupOnly used once every primary server is unavailable
downManually excluded, kept documented in the config
keepalive (in upstream block)Reusing Nginx's own connections to the backend pool
A familiar shape if you've used Kubernetes
An upstream block naming a pool of backend addresses is conceptually similar to what a Kubernetes Service abstracts automatically over a set of Pods (Kubernetes Fundamentals' own territory) — both exist to let something in front of a backend pool treat "many instances" as one logical destination, just configured by hand here rather than generated automatically by an orchestrator.
max_fails/fail_timeout is not a real health check
It's easy to assume max_fails/fail_timeout gives real backend health monitoring. In open-source Nginx, this mechanism is entirely reactive — it only reacts after real client requests have already failed against a server, rather than actively probing backend health ahead of time the way a dedicated health-check system does. (Nginx Plus, the paid commercial version, does add active health checks that proactively probe backends — but that's a separate, paid capability, not something open-source Nginx's own max_fails mechanism provides.)

Hands-On Exercises

Exercise 1

Write an upstream block named api_pool containing three backend servers on 10.0.0.21:8080, 10.0.0.22:8080, and 10.0.0.23:8080, where the third server should receive twice as much traffic as the other two.

📄 View solution
Exercise 2

A team relies entirely on max_fails/fail_timeout and considers their backend pool "actively monitored for health" as a result. Using this chapter's own warning box, explain what's actually true and what's missing from this claim.

📄 View solution
Exercise 3

Explain why the keepalive directive inside an upstream block is a genuinely different concept from the client-facing keep-alive settings covered in Web Servers Fundamentals, even though both are called "keep-alive."

📄 View solution

Chapter 4 Quick Reference

  • upstream {} — names a pool of backend servers, referenced by name inside proxy_pass
  • weight — proportional traffic share; max_fails/fail_timeout — reactive failure tracking; backup — last-resort server; down — documented manual exclusion
  • keepalive inside upstream {} reuses Nginx's own connections to the backend — a separate concept from client-facing keep-alive
  • Open-source Nginx's failure tracking is reactive only — not a substitute for a real, proactive health-check system