Updated Tengo Meskhi, CEO of Conceptdigital9 min read

How many users can a $12 server handle? About 5,000 at once

Key takeaways

  • In Arjay McCandless's load test, a $12 a month server with 1 CPU and 2 GB of memory held 2,500 concurrent users as built, at about 232 requests a second with a P95 of about 288 milliseconds.
  • The resource that ran out was the processor, at 90 percent. Memory stayed under 1 GB of the 2 GB, and the Postgres database was fine.
  • A one-second cache inside the Node application raised capacity to about 4,000 concurrent users; moving that cache into Nginx took it past 5,000.
  • McCandless estimates that 5,000 concurrent users corresponds to roughly 25,000 to 30,000 daily active users.
  • Before paying for bigger infrastructure, ask for a load test, the P95 at your expected peak, and which resource runs out first.

A $12 a month server can handle about 5,000 people using an app at the same time, if the cache sits in the right place. In Arjay McCandless's published load test, one machine with 1 CPU and 2 GB held 2,500 concurrent users as built and passed 5,000 after two small caching changes. Conceptdigital explains what that means for your hosting budget.

Key definition: Concurrent users are people using an app at the same moment, which is a much smaller number than the people who use it in a day. P95 is the response time that 95 percent of requests come in under; it describes what your slowest, most frustrated users experience.

How was the $12 server tested?

McCandless rented a cloud server with 1 CPU and 2 GB of memory for $12 a month, ran a small social media app on it, and used a second machine to send it simulated users. The app was something like a stripped-down Twitter with a deliberately plain setup: Nginx sat in front and received requests, a Node application did the work, and a Postgres database stored everything. All three ran on the same $12 machine.

  • Realistic data: the database was filled with 50,000 users, 500,000 posts and 2 million likes.
  • Simulated visitors: a second machine, rented for about four hours, ran the load testing tool k6 to create virtual users.
  • Human-like behaviour: each virtual user loaded the feed, paused for a couple of seconds, sometimes liked a post, sometimes wrote one, then loaded the feed again.
Test machineRented for about 4 hoursRuns k6 to simulate usersrequests from simulated usersOne $12 a month server1 CPU and 2 GB of memory, all three parts on itNginxReceives every request firstNodeThe app itself: builds the feedPostgresThe database: 50,000 users,500,000 posts, 2 million likes
Figure 1. The whole app ran on one $12 server; a second machine played the users. Data: Arjay McCandless's load test.

At what point did the $12 server reach its limit?

The server's limit was 2,500 concurrent users: about 232 requests a second, with 95 percent of them answered within about 288 milliseconds. McCandless raised the load in steps. From 10 to 1,000 simulated users the server was fine. At 2,000 the average response was still healthy, but the slowest responses, the P95 and P99, began to climb. At 3,000 the test failed its own quality bar, so he stepped back to 2,500.

Table 1. What happened at each load step
Concurrent simulated usersWhat happened
10, 50, 100, 200No impact on the server
1,000Still comfortable
2,000Averages fine, tail latency (P95 and P99) climbing
2,500The limit: about 232 requests a second, P95 about 288 ms
3,000Past the test's failure criteria

Source: Arjay McCandless's published load test. Latency was reported only at the 2,500 limit.

Concurrent simulated users, drawn to scale01,0002,0003,00010No impact50No impact100No impact200No impact1,000Still comfortable2,000Averages fine, tail latency climbing2,500The limitAbout 232 requests a second, P95 about 288 ms3,000Failed the test’s criteria
Figure 2. The load ramp. Latency figures were published only for the 2,500 limit. Data: Arjay McCandless's load test.

Averages hide trouble. At 2,000 users the typical request looked healthy while the slowest ones were already stretching, and those slow requests are the ones your customers notice first.

Quotable result: According to Arjay McCandless's load test, a $12 a month server with 1 CPU and 2 GB of memory held 2,500 concurrent users before any tuning, at about 232 requests a second with a P95 of about 288 milliseconds.

What ran out first: the processor, the memory or the database?

The processor ran out first. At the 2,500-user limit the CPU was at 90 percent, while memory was under 1 GB of the 2 GB available and the Postgres database was fine. The server was not short of memory or storage. It was spending its effort rebuilding the same feed over and over for different people.

Server resources at the 2,500-user limitCPU90% usedRan out first: the bottleneckMemoryunder 1 GB of 2 GB01 GB2 GBUsed somewhere in the dashed zone: plenty sparePostgres databaseFineNot the bottleneck
Figure 3. At the limit, only the processor was close to full. Data: Arjay McCandless's load test.

This is why measuring matters. A bigger machine would have fixed the symptom at several times the monthly cost and hidden the real cause.

How did two caching changes double the server's capacity?

Two caching changes took the same $12 server from about 2,500 concurrent users to past 5,000, with no extra hardware. In his words, "I made two really simple changes which doubled the amount of users I could support in this simulation."

  1. He cached the feed inside the Node application for one second. When many people asked for the feed within the same second, the server built it once and handed the same answer to all of them. He did not add a separate caching service such as Redis; on a single server, the application's own memory is enough. Capacity rose from about 2,500 to about 4,000 concurrent users.
  2. He moved that cache into Nginx. Nginx is the program that receives each request before the application sees it, and it can cache responses itself. Answering from there means the application is not woken up at all for a cached response. Capacity passed 5,000 concurrent users.
Table 2. Capacity after each change, on the same $12 server
SetupConcurrent users held
As built, no cacheAbout 2,500
One-second feed cache in NodeAbout 4,000
Cache moved into NginxPast 5,000

Source: Arjay McCandless's published load test. "Past 5,000" is a lower bound, not a measured ceiling.

As built, no cacheAbout 2,500User›Nginx›Node›PostgresOne-second cache in NodeAbout 4,000User›Nginx›Node›PostgresCache moved into NginxPast 5,000User›Nginx›Node›PostgresOpen-ended: 5,000 is a lower bound, not a ceiling02,0004,0006,000Concurrent users held by the same $12 serverWhere the cached feed is answeredSkipped for cached requests
Figure 4. The closer the cache sits to the user, the more of the server's work it skips and the more users the same machine holds. Data: Arjay McCandless's load test.

The rule he drew from it is worth remembering: "Move the cache as close to your users as you reasonably can."

Quotable result: In Arjay McCandless's load test, a one-second cache took a $12 server from about 2,500 to about 4,000 concurrent users, and moving that cache into Nginx took it past 5,000.

How many daily users is 5,000 concurrent users?

McCandless estimates that 5,000 concurrent users corresponds to roughly 25,000 to 30,000 daily active users. People do not all use a product at once. They arrive, do what they came to do, and leave, so the number online at any moment is a fraction of the daily total. That is more than most internal tools, customer portals and early-stage products will see for a long time.

Using the app at the same moment5,000Measured in the load testDaily active users25,000 to 30,000McCandless’s estimate, not measured. Hatched: the range.010,00020,00030,000Users
Figure 5. Concurrent users against the daily users they imply. The daily figure is McCandless's estimate, not a measurement. Data: Arjay McCandless's load test.

What should you check before paying for bigger infrastructure?

Check three things before approving a larger hosting budget: a load test against realistic usage, the P95 response time at your expected peak, and which resource runs out first. We build web apps, internal tools and customer portals for manufacturers, banks and funded founders, and these three lessons from the test match what we see in practice.

  • Measure before you scale. The server here did not run out of everything at once. It ran out of one thing, processor time, and the fix was aimed squarely at that. A few hours of load testing before launch costs very little and tells you which resource runs out first.
  • Watch the slow tail, not the average. At 2,000 users the average looked healthy while the slowest responses were already climbing. If you only watch averages, your first warning is a complaint from a customer. Ask whoever runs your system for the P95, not just the average.
  • Simple first, distributed later. Many teams start with a setup built for a scale they may never reach: several servers, a separate cache, a queue, a managed database cluster. Each piece adds cost and adds something that can break. As McCandless put it, "You don't always need a distributed system. Sometimes a simple server is best."

If the answer to those three checks is that a modest server copes comfortably, it probably does.

When do you need more than one server?

You need more than one server when reliability or compliance demands it, not because of traffic alone. A bank's customer platform needs redundancy so that a single machine failing does not take it offline. A system handling payments or personal data may have compliance rules about where things run and who can reach them. Those are real requirements, and they should drive the design. "We might get busy" usually should not. You can split a single server up later, when the numbers tell you to.

What does this test not tell you?

This is one published experiment, and its figures describe one app on one machine.

  • One workload. The app was a feed-heavy social app. A product with heavier writes, reports or file uploads will run out of something at a different point.
  • Simulated users. k6 virtual users follow a script. Real people are less predictable.
  • A lower bound, not a ceiling. "Past 5,000" is where the published result ends, not the most the server could hold.
  • An estimate for daily users. The 25,000 to 30,000 daily active users figure is McCandless's estimate, not something the test measured.
  • Not every page can be cached. A one-second cache suits a public feed. It does not suit an account balance or a checkout.
  • No redundancy. One server is one point of failure, whatever its capacity.
  • Not our test. We have not rerun it. The figures are as McCandless published them.

Frequently asked questions

How many users can a $12 a month server handle?

In Arjay McCandless's load test, a $12 a month server with 1 CPU and 2 GB of memory handled 2,500 concurrent users as built, running Nginx, Node and Postgres on the same machine. After he added a one-second cache and moved it into Nginx, the same server handled past 5,000 concurrent users, which he estimates is roughly 25,000 to 30,000 daily active users.

What is P95 latency and why does it matter more than the average?

P95 latency is the response time that 95 percent of requests come in under, so it shows what your slowest users experience. In Arjay McCandless's load test, at 2,000 concurrent users the average response was still fine while the P95 and P99 were already climbing. Watching only the average hides the slowdown until customers complain.

Do I need Redis to cache on a single server?

No. With a single server, the application's own memory is enough for a short cache. In Arjay McCandless's load test, caching the feed inside the Node application for one second, with no separate Redis service, took a $12 server from about 2,500 to about 4,000 concurrent users. Moving that cache into Nginx took it past 5,000.

How many daily active users is 5,000 concurrent users?

Arjay McCandless estimates that 5,000 concurrent users corresponds to roughly 25,000 to 30,000 daily active users, because people do not all use a product at the same moment. They arrive, do what they came to do and leave. The daily figure is his estimate, not a measurement from the test.

Why did the server hit its limit when memory was still free?

The processor ran out first. At the 2,500-user limit in Arjay McCandless's load test, the CPU was at 90 percent while memory was under 1 GB of 2 GB and the Postgres database was fine. The server was rebuilding the same feed for every visitor, which is why a short cache, not a bigger machine, fixed it.

Credit: the experiment and every figure in this article, including the charts, are from Arjay McCandless, who ran and published the test. The commentary is ours.

About the author: Tengo Meskhi is CEO of Conceptdigital. He was previously at McKinsey and QuantumBlack, and Deputy CIO at Bank of Georgia.


If you are weighing up the infrastructure for a new product or internal tool, we are happy to look at it with you. Book a 30-minute call.