The web server stack explained: nginx, PHP-FPM, app servers and databases

What each layer of a typical website stack does, how a request moves through it, and where caching fits.

Websites and apps21 min readUpdated

LayerWhat it does with this requestHow many it can hold at onceVisitor's browsera phone or a laptopSends GET /shop/ over HTTPS,then waits for the whole pagenginxweb server, reverse proxyEnds TLS, serves files from disk,checks its cache, passes the rest on1,024 connections, about 512 proxied requestsApplication poolPHP-FPM, GunicornRuns your code: each worker holdsone request from start to finish20 requests: pm.max_children = 20narrowest layerRedisobject cacheHands back results it already has;on a miss, your code asks the databaseshared by every workerDatabasePostgreSQL, MySQLFinds and changes the rows the pageneeds; slow queries hold up the rest100 connections: Postgres defaultBars drawn to scale for an example 2-core server: one nginx worker per core at the default 512 connections each,a PHP-FPM pool of 20 (pm.max_children), and PostgreSQL at its usual default of 100 connections.Visitor's browserphone or laptopSends GET /shop/ over HTTPS,then waits for the whole pagenginxweb server, reverse proxyEnds TLS, serves files from disk,checks its cache, passes the rest on1,024 connections (512 proxied requests)Application poolPHP-FPM, GunicornRuns your code: each worker holdsone request from start to finish20 requests: pm.max_children = 20narrowest layerRedisobject cacheHands back results it already has;on a miss, your code asks the databaseshared by every workerDatabasePostgreSQL, MySQLFinds and changes the rows the pageneeds; slow queries hold up the rest100 connections: Postgres defaultBars to scale for an example 2-coreserver: nginx and PostgreSQL at theirdefaults, a PHP-FPM pool of 20.

In short

  • A dynamic page passes through several programs: nginx or Apache at the front, an application pool such as PHP-FPM, Gunicorn or Node.js behind it, and usually a cache such as Redis and a database behind that.
  • nginx holds many connections in a few event-driven worker processes. It ends TLS, serves static files itself and buffers responses, so slow visitors do not tie up your application.
  • A process-per-request pool such as PHP-FPM has a fixed number of seats, set by pm.max_children. When every worker is busy, new requests wait in a queue, and the site slows down or fails while the processor can look half idle.
  • Busy workers on average = requests per second × seconds per request (Little's law). A slower database query fills the pool just as surely as more traffic does.
  • Fix a full pool in order: find what made requests slow, cache what repeats, then add workers only as far as memory and the database connection limit allow.
On this page
  1. One request, five layers
  2. nginx and Apache: two ways to hold a connection
  3. TLS and static files: work nginx finishes alone
  4. The hand-off: reverse proxying and FastCGI
  5. The application pool: a fixed number of seats
  6. Caches: answering without doing the work
  7. How many workers you need: Little's law
  8. What runs out first, and what to do about it
  9. Questions
  10. Sources

A shop runs happily all month. Then a newsletter goes out, the site starts answering with 502 and 504 errors, and top shows the processor half idle. Nothing has crashed. One layer of the stack ran out of places to put requests, and the layers in front of it did what they are built to do: wait, and then give up.

Behind one page view sit four or five programs, each with its own limit on how many requests it can hold at once. Follow one request through them, from nginx to the database, and the same three questions come up at every layer: what does it do, how many requests can it hold, and what happens when it is full.

One request, five layers

Take a request for a shop's product listing, /shop/, arriving over HTTPS. The browser's side of the trip, from the address lookup to the TLS handshake, is covered in what happens when you open a website. This guide starts at your server's front door.

  1. nginx accepts the connection and ends TLS

    The encrypted connection stops at nginx. From here on, the request is plain HTTP inside your own server.

  2. nginx decides what kind of request it is

    A file such as style.css or logo.png is read from disk and sent straight back. That request never reaches your code.

  3. nginx checks its page cache, if you turned one on

    A stored copy of /shop/ that is still fresh goes straight back to the visitor. Only a miss carries on.

  4. nginx hands the request to your application

    Over FastCGI to a PHP-FPM worker, or over HTTP to Gunicorn, a Node.js process or a Go program. One worker is now busy with this request.

  5. Your code asks for data

    It checks the object cache, usually Redis, for results it has seen before, and asks the database for everything else.

  6. The response goes back up the stack

    The worker hands the finished page to nginx, which sends it to the visitor at whatever speed their connection allows.

The drawing at the top of this page puts what each layer holds on one scale for an example server: about a thousand connections for nginx, twenty requests for the PHP pool, a hundred connections for the database. The layer with the fewest seats for the work arriving fills first, and here that is the application pool.

nginx and Apache: two ways to hold a connection

nginx runs one master process, which reads the configuration and looks after the others, and several worker processes that do the actual work of handling requests.1 The configuration either fixes the number of workers or lets nginx match it to the number of processor cores.1

Each worker handles many connections at once without giving any of them a thread of its own. It asks the kernel which of its connections are ready to read or write, does the short piece of work each ready one needs, and asks again. On Linux the kernel interface is epoll, which nginx picks automatically,2 and which scales well to large numbers of watched connections.3 A connection that is waiting, for the visitor's next request or for your application's answer, costs the worker a little memory and nothing else.

The cap is worker_connections, 512 per worker by default, and it counts every connection the worker has open, including the ones to the servers it proxies to, not only visitors.4 A proxied request therefore uses two: one to the visitor and one to your application. The operating system's limit on open files applies on top.4 Two workers at the default give 2 × 512 = 1,024 connections, which is room for about 512 proxied requests in flight.

Apache: three answers to the same question

Apache HTTP Server puts the choice in a module called an MPM, a multi-processing module, and exactly one is loaded at a time.5 prefork is the classic model: a parent process keeps a pool of single-threaded child processes, and each one answers one request at a time.6 worker runs several threads inside each process, which serves many requests with fewer resources than a process each.7 event goes further and fixes what its documentation calls the keep-alive problem. Each process runs a fixed number of worker threads, set by ThreadsPerChild, plus one listener thread. Rather than tie up a worker thread waiting for an idle client to send its next request, the listener watches the idle keep-alive connections, and the ones that only need the rest of a response sent, and passes a connection to a worker thread when there is work to do.8

So nginx against Apache is less a question of speed than of what a waiting connection costs: in a process-per-connection design, a whole process or thread for as long as it waits; in an event-driven one, a little memory. The drawing below shows the same difference one layer further back, where it matters more.

Event loop (Node.js): one worker, four requests in flightRequest Awaiting on the databaseRequest BRequest CRequest DThe workerAABBCCDDfree for othersOne worker per request (PHP-FPM, Gunicorn sync): four workers heldWorker 1Aheld, waiting on the databaseAWorker 2BBWorker 3CCWorker 4DDtimeworking on the processorwaiting, worker freewaiting, worker held
Event loop (Node.js):one worker, four requests in flightRequest Adatabase waitRequest BRequest CRequest DThe workerAABBCCDDfreeOne worker per request(PHP-FPM, Gunicorn sync): four heldWorker 1Aheld, waitingAWorker 2BBWorker 3CCWorker 4DDtimeworking on the processorwaiting, worker freewaiting, worker held
The same four requests, each needing a moment of processor time, a long wait for the database or another service and another moment of processor time. An event-loop worker (top) runs the short pieces in turn and is free while the requests wait. A worker-per-request pool (bottom) needs four workers, and each one is held through its request's wait. Illustration.

A waiting worker in the bottom half is still one nobody else can use, which is why the layer that runs one request per process runs out first.

TLS and static files: work nginx finishes alone

When nginx holds the certificate, the encrypted connection ends there, and that costs processor time: nginx's documentation says SSL operations use extra CPU, and the handshake is the most expensive part.9It gives two ways to do fewer handshakes. Keep connections alive, so one connection carries several requests, and reuse session parameters, so parallel and repeat connections skip the full handshake.9The shared session cache that makes reuse work is cheap: one megabyte holds about 4,000 sessions, and entries last 5 minutes by default.9

Ending TLS at the front means everything behind nginx talks plain HTTP. It also means a filter in front of your server sees only encrypted bytes: it can judge HTTPS connections but cannot read the requests inside them, as how DDoS protection works explains.

Static files are the other job nginx finishes alone. Serving files such as images is one of a web server's basic tasks,1 and nginx does it from the same event loop, with no application worker involved. A product page might pull in a stylesheet, a few scripts and a dozen images; sent through PHP, they would take most of the pool's seats for work that needs no code.

The hand-off: reverse proxying and FastCGI

Everything else, nginx passes on. As a reverse proxy it forwards the request to another server and relays the answer. For an application that speaks HTTP, such as Gunicorn, a Node.js process or a Go program, the directive is proxy_pass. For PHP it is fastcgi_pass, which sends the request to a FastCGI server such as PHP-FPM instead.1 Neither sets a limit of its own on how many connections nginx opens to the application: the max_conns option of an upstream server is zero by default, which means no limit.12 The limit that bites is the application's.

Classic CGI started a new process for every request, used it once and let it exit. FastCGI was designed around long-lived application processes that stay running and serve request after request.10 PHP-FPM, the FastCGI Process Manager, runs PHP that way, managing those processes in pools, each of which can run under its own user, with its own settings and its own listening socket.11

Buffering: why nginx takes the whole answer at once

With proxy_buffering on, which is the default, nginx reads the application's response as fast as the application can send it, holding it in memory buffers and spilling a large one to a temporary file.13 It then feeds the response to the visitor at whatever speed their connection manages. With buffering off, the response goes to the visitor synchronously, as it arrives,13 so for any page bigger than what the kernel's socket buffers absorb, the worker that built it waits on a slow visitor. The FastCGI specification expected this split from the start: it treats slow clients as the web server's problem, to be absorbed by its output buffers.10

Without a buffering proxyApp workerworkheld: feeding a slow phonePhonedownloading slowlyWith nginx buffering in frontApp workerworkfree for the next requestnginxsends at the phone's pacePhonedownloading slowlytime
The same page sent to a visitor on a slow phone connection. Without a buffering proxy, the worker that built a large page is held while the phone downloads it. With nginx buffering in front, nginx takes the response at once and the worker moves on to the next request. Illustration.

Buffering matters most for servers that run one request per worker. Gunicorn's documentation lists a buffering proxy as a production requirement for its default sync worker,14 and the deployment guide spells out why: with sync workers, a proxy that does not buffer slow clients leaves Gunicorn open to denial of service.15 The exception is streaming. Long polling, WebSockets and other streamed responses need proxy buffering turned off, and Gunicorn recommends an asynchronous worker class for them.15 Turn buffering off in the one location that streams, not for the whole site.

The application pool: a fixed number of seats

Behind nginx runs your code. Some designs give each request in flight a process or thread of its own; others run many requests inside one process, and the difference decides what a slow request costs.

PHP-FPM: one child, one request

A PHP-FPM pool is a set of child processes, and each child works on one request at a time. The setting that matters most is pm.max_children, which PHP's documentation describes as the cap on how many requests the pool serves at the same time, the same role MaxClients played in Apache's prefork.16 The pm mode only decides how the children are started: static keeps exactly that many running, dynamic keeps a number between spare-server limits, and ondemand starts children only when requests arrive.16 None of them moves the ceiling.

Each child calls accept() on the pool's listening socket, as the FastCGI specification describes,10 and finishes one request before it accepts the next. So when every child is busy, the next connection from nginx waits in that socket's queue. PHP-FPM's status page counts exactly this: its listen queue field is the number of requests currently waiting for a free process, shown next to the active and idle processes and a max children reached counter.17 The queue has a length, listen.backlog, which defaults to 511 on Linux,16 as long as the kernel allows that much: it silently caps any backlog at net.core.somaxconn, 4,096 by default since Linux 5.4 and 128 before.18 When a socket's queue is full, the kernel refuses new connection attempts or ignores them so the client retries later.18

Two signs tell you the pool is the problem. A dynamic pool writes a warning to its log when it reaches its ceiling (an ondemand pool logs a similar line; a static pool logs nothing),19 and the status page, once you set pm.status_path and let nginx pass only local requests to it, shows the queue as it happens. One catch hides in PHP-FPM's source: it measures the listen queue only when the pool listens on a TCP port such as 127.0.0.1:9000, so on a Unix socket the queue figures stay at zero.19

sudo grep -h "max_children" /var/log/php*-fpm.log[28-Sep-2026 20:14:03] WARNING: [pool www] server reached pm.max_children setting (20), consider raising itcurl -s http://127.0.0.1/fpm-statuspool:                 wwwprocess manager:      dynamicstart time:           28/Sep/2026:06:02:11 +0000start since:          51472accepted conn:        1320519listen queue:         37max listen queue:     212listen queue len:     511idle processes:       0active processes:     20total processes:      20max active processes: 20max children reached: 14slow requests:        96

No idle processes, all 20 active and 37 requests waiting: the pool is full right now. (The log path is Debian's and Ubuntu's; other systems keep it elsewhere.) Two more settings earn a place in every pool. request_slowlog_timeout writes a PHP backtrace to a slow log for any request that runs longer than it, and request_terminate_timeout kills a worker stuck on one request too long.16 The slow log is how you find the query or API call that is holding seats.

Gunicorn and other pre-fork servers

Python's Gunicorn has the same shape: an arbiter process manages worker processes that handle the requests,14 and the default sync worker holds one request at a time, like an FPM child. The documentation suggests starting at (2 × CPU cores) + 1 workers and adjusting under load, and warns against the obvious fix for a slow site: workers are not clients, 4 to 12 workers usually handle heavy traffic, and too many waste resources and can lower throughput.14 For applications that spend their time in long blocking calls to external APIs or slow databases, it recommends asynchronous workers, and it also offers a threaded worker, gthread, that runs a pool of threads inside each process.14

Java: a thread pool with a queue behind it

Java servers such as Tomcat hold each ordinary request on a thread from the connector's pool until it is done, so maxThreads, 200 by default, sets how many requests Tomcat handles at the same time.24 When every thread is busy, Tomcat parks new connections until one frees up, as many as maxConnections allows. Past that they wait in the operating system's queue, sized by acceptCount, and once that is full they may be refused or time out.24 A thread costs less than a process, but one waiting on the database is still a seat nobody else can use.

Node.js and Go: many requests in one process

Node.js runs your JavaScript on a single thread, the event loop, and hands I/O to the kernel wherever it can, so one process holds many requests in flight.20 Node's own guide is named after the rule that follows: do not block the event loop. A handler that keeps the thread busy, parsing a large file for example, holds up every other request that process is serving.21 To use more than one core, the cluster module starts several processes that share the same server port.22 Go's standard HTTP server starts a new goroutine, a lightweight thread managed by the Go runtime, for every incoming connection.23

In both, a request waiting on the database no longer costs a process. It costs a pending callback or a parked goroutine, which is cheap. The ceiling moves: to the processor, if requests compute, and to the database's connections, if they all wait on it.

LayerWhat holds one requestThe limit you setWhen it is reached
nginxA connection in an event-driven workerworker_connections per worker, 512 by default, counting both sides of a proxied requestThat worker opens no more connections
Apache preforkA whole processMaxRequestWorkers, bounded by memoryRequests wait for a free process
Apache eventA thread while there is work; idle connections sit with a listener threadThreadsPerChild and MaxRequestWorkersRequests wait for a free thread
PHP-FPMA child process, for the whole requestpm.max_childrenRequests wait in the listen queue, 511 places by default on Linux if the kernel allows
Gunicorn, sync workersA worker process, for the whole requestThe worker count; start at (2 × cores) + 1Requests wait for a free worker
Java (Tomcat)A thread from the connector's pool, for the whole requestmaxThreads, 200 by defaultConnections wait for a thread, then in the operating system's queue (acceptCount)
Node.js, GoA callback or a goroutineNo per-request seat to set; the processor and the database pool limit itEverything in the process slows down together
PostgreSQLA connection, for as long as the client keeps itmax_connections, typically 100New connections fail

Defaults as documented by nginx, PHP, Tomcat and PostgreSQL; sources in the text.

Caches: answering without doing the work

Every layer so far charges something per request. What matters about a cache is which layer's charge it skips.

A page cache stores the whole finished response. nginx can keep one itself: fastcgi_cache is off by default, and once configured, nginx stores cached responses as files under fastcgi_cache_path and keeps their keys in a shared memory zone that every worker reads.25 The proxy module has the same feature, proxy_cache, for applications behind proxy_pass.13 A hit is answered by nginx and never reaches PHP, which makes the page cache the most effective cache in the stack and the most limited. It only works for pages that are the same for every visitor, so logged-in pages, baskets and anything personal have to bypass it.

An object cache stores pieces: the result of a query, a built menu, a computed price. The application checks it before it asks the database. WordPress shows why the kind of cache matters: its object cache exists to save trips to the database, but by default it lasts only as long as one request unless a persistent cache is installed.26 A store such as Redis fills that gap, one cache in memory shared by every PHP child. Redis's documentation describes this role, a cache in front of a slower database whose entries are copies of data stored elsewhere and so usually safe to evict.27 Give it a ceiling with maxmemory and an eviction policy such as allkeys-lru, which removes the least recently used keys first.27

CacheWhat it storesWhere it livesWhat a hit skips
Page cacheWhole responsesnginx, or a CDN in front of itThe application pool, the object cache and the database
Object cacheQuery results and computed valuesRedis, shared by every workerThe database query
Per-request cacheValues used more than once while building one pageThe worker's memory, gone when the request endsRepeat lookups within that page

How many workers you need: Little's law

Little's law says that the average number of items in a queueing system equals the average rate at which items arrive multiplied by the average time each one spends in the system, written L = λW.28 For an application pool the items are requests, and the law reads: busy workers on average = requests per second × seconds per request.

Take a shop that gets 40 requests a second for dynamic pages at its evening peak. Images and stylesheets come from nginx and do not count. Each PHP request takes 250 ms from start to finish, including its waits on the database.

  • A normal evening. Busy workers: 40 × 0.25 = 10. With pm.max_children at 20, half the pool is free, and the most it could finish at this speed is 20 ÷ 0.25 = 80 requests a second.
  • One slow query. A table grows, a query that was fine at ten thousand rows is slow at a million, and the average request now takes 750 ms. Traffic has not changed. The pool would need 40 × 0.75 = 30 busy workers on average, and it has 20, so there is no steady state: the queue only grows. The pool can finish at most 20 ÷ 0.75 ≈ 26.7 requests a second, so about 13.3 a second pile up in the listen queue, and its 511 places are gone in roughly 511 ÷ 13.3 ≈ 38 seconds. The web server's processor may look underused the whole time, because most of those workers are not computing. They are waiting on the database.
  • A page cache in front. If nginx answers 80% of those 40 requests from its page cache, PHP sees 8 a second: 8 × 0.25 = 2 busy workers.
PHP-FPM pool: pm.max_children = 20Waiting for a free worker (listen queue)empty on average

Busy workers on average: 40 × 0.3 s = 10 of 20. Most this pool can finish: 20 ÷ 0.3 s = 80 requests a second. Headroom: about 10 workers free on average. Bursts above the average still wait briefly.

Little's law on a PHP-FPM pool of 20 workers. Move the sliders or pick one of the cases above: the seats show the average number of busy workers, and once the load needs more than 20, the bar shows how fast the listen queue fills. Derived from Little's law and PHP-FPM's default listen.backlog of 511 on Linux.

Two cautions. The law gives averages and traffic arrives in bursts, so leave headroom above the average. And the slow-query case does not need more workers: thirty children would hold thirty requests waiting on the same slow query, with thirty database connections open. Fix the query.

Every extra worker costs memory and a database connection

A worker needs memory for as long as it lives. Apache's prefork documentation states the rule for its MaxRequestWorkers setting, and it holds for every process-per-request server: enough processes for the simultaneous requests you expect, and few enough that all of them fit in physical RAM.6 Measure what your workers really use rather than trusting a number from a forum:

ps -eo rss,comm | awk '/php-fpm/ {s+=$1; n++} END {printf "%d processes, %.0f MB average\n", n, s/n/1024}'21 processes, 58 MB average

Then divide. If 2 GB of the server's memory can go to PHP once the database, Redis and the operating system have theirs, and a child averages 58 MB, the pool can have about 2,048 ÷ 58 ≈ 35 children. Measure while the site is busy, because children grow as they serve bigger pages. RSS counts memory the children share more than once, so the average errs on the high side, which is the safe side. If children keep growing, pm.max_requests restarts each one after a set number of requests, which PHP's documentation suggests for memory leaks in third-party libraries.16

The database has seats too. Each busy worker that talks to the database holds a connection while it does. PostgreSQL's max_connections is typically 100, and raising it makes the server allocate more resources, including shared memory, for the extra connections it might have to serve.29 Each of those connections is a whole server process on the PostgreSQL side, a backend of its own,30 the same one-process-per-seat design as a PHP-FPM pool. Three web servers with 30 PHP children each can open 3 × 30 = 90 connections, which fits. Add a fourth and 4 × 30 = 120 does not, and its first busy evening ends in connection errors from the database, not from PHP. The database's own memory is covered in sizing a database server.

The arithmetic is the same for Gunicorn. On 2 vCPUs the starting formula gives (2 × 2) + 1 = 5 sync workers, which finish at most 5 ÷ 0.25 = 20 requests a second at 250 ms each. If that work is computation, five already keep both cores busy (vCPU vs CPU core explains the cores); if it is mostly waiting, threaded or asynchronous workers serve more requests in the same memory.

What runs out first, and what to do about it

When the pool or the database cannot keep up, the error the visitor sees comes from nginx, which is why nginx gets the blame. HTTP defines 502 Bad Gateway as a gateway or proxy receiving an invalid response from the server behind it, and 504 Gateway Timeout as one that got no answer in time.31 Both point behind nginx. nginx waits 60 seconds by default to connect to a proxied server, and 60 seconds between two reads of its response rather than for the whole of it;13 the FastCGI connect and read timeouts are also 60 seconds.25

The slow-query case above shows which state gives which code. A request at the back of a full 511-place queue waits about 511 ÷ 26.7 ≈ 19 seconds for a child: a very slow page, not yet an error. nginx has already handed it over, so that wait counts against the 60-second read timeout, and only a request whose wait and run time together pass 60 seconds, behind a query that stalls outright, comes back as a 504. Once the queue is full, nginx cannot hand requests over at all. On a Unix socket the connection attempt fails at once, and the visitor usually gets a 502 straight away. On a TCP port the kernel ignores the attempt, so nginx waits for a place, and if none opens within the connect timeout, the answer is a 504.

An order that works

  1. Measure first, at the busy hour: the FPM status page on a TCP listener (a static pool never logs the max_children warning), the slow log, and the database's slow query log.
  2. Take static files off the pool, and cache whatever is the same for every visitor.
  3. Make the slow request faster: an index, a smaller query, a timeout on the outside API call.
  4. Only then add workers, within what memory and the database's connection limit allow.
  5. When the processor is busy doing real work, more cores are the answer. The server calculator sizes a server from your traffic.

Questions

Is nginx faster than Apache?

Neither is always faster. They hold connections differently: nginx handles many connections per event-driven worker, while Apache's prefork gives each request a process and its event MPM parks idle connections on a listener thread. On a dynamic site the limit is usually the application pool or the database behind either of them.

What does "server reached pm.max_children setting" mean?

Every PHP-FPM child in that pool was busy at once, so new requests had to wait in the pool's listen queue. Before raising the number, check the slow log for what made requests take longer, and make sure the extra children fit in memory and in the database's connection limit.

Why does nginx show 502 or 504 errors when my site is busy?

Both codes mean a proxy did not get a usable answer from the server behind it: 502 for an invalid response, 504 for no answer in time. The cause is usually behind nginx, in a full or stuck application pool or a slow database.

How many PHP-FPM workers should I run?

Enough for your busy workers on average, which is requests per second times seconds per request, plus headroom for bursts. No more than your memory allows, which is the memory you can give PHP divided by what one child really uses.

Do I need Redis for a WordPress site?

Not to run one, but it helps a busy one. WordPress keeps its object cache only for the length of a single request unless a persistent cache is installed, and Redis is a common way to keep those results between requests and share them across every PHP worker.

Do I need nginx in front of Gunicorn or Node.js?

For Gunicorn with its default sync workers, yes: its documentation requires a buffering proxy so slow clients cannot tie up workers. For Node.js it is optional, but nginx in front still takes care of TLS, static files and buffering.

Sources

  1. Beginner's Guidenginx
  2. Connection processing methodsnginx
  3. epoll(7): I/O event notification facilityLinux man-pages (Debian)
  4. Core functionality: worker_connectionsnginx
  5. Multi-Processing Modules (MPMs)The Apache Software Foundation
  6. Apache MPM preforkThe Apache Software Foundation
  7. Apache MPM workerThe Apache Software Foundation
  8. Apache MPM eventThe Apache Software Foundation
  9. Configuring HTTPS serversnginx
  10. FastCGI SpecificationOpen Market (FastCGI archives)
  11. FastCGI Process Manager (FPM)The PHP Group
  12. Module ngx_http_upstream_modulenginx
  13. Module ngx_http_proxy_modulenginx
  14. DesignGunicorn
  15. DeployGunicorn
  16. FPM configurationThe PHP Group
  17. FPM status pageThe PHP Group
  18. listen(2): listen for connections on a socketLinux man-pages (Debian)
  19. php-src: sapi/fpm/fpm/fpm_process_ctl.cThe PHP Group (GitHub)
  20. The Node.js Event LoopOpenJS Foundation
  21. Don't Block the Event Loop (or the Worker Pool)OpenJS Foundation
  22. ClusterOpenJS Foundation
  23. net/http: func ServeThe Go Authors
  24. The HTTP Connector (Tomcat 10.1 configuration reference)The Apache Software Foundation
  25. Module ngx_http_fastcgi_modulenginx
  26. WP_Object_CacheWordPress.org
  27. Key evictionRedis
  28. Little's Law as Viewed on Its 50th Anniversary (Operations Research 59(3), 2011)John D. C. Little, INFORMS
  29. Connections and Authentication: max_connectionsThe PostgreSQL Global Development Group
  30. How Connections Are EstablishedThe PostgreSQL Global Development Group
  31. RFC 9110: HTTP SemanticsIETF

Put it to work on a server of your own.

Size a server for your workload with the calculator, or see which plan fits the job you have in mind.