Serverless chat on AWS: $19 a month at 100 users, and its limits
With groups of ten, a single server becomes cheaper at about 1,200 users. New connections stay capped at 500 a second per account until AWS raises the quota.
Guides to building a chat app on AWS rarely say where it stops working. This post covers the architecture, what it costs at seven sizes, and the four limits that eventually force you off it. Paying more raises none of them: three are fixed, and the fourth, 500 new connections a second, moves only if AWS raises your quota.
The architecture
There are five pieces.
API Gateway WebSocket API holds the sockets. It is the only component that knows a connection exists.
Lambda handles at least four routes: $connect, $disconnect, $default and a send route. A function runs for the milliseconds it takes to process one message and then stops.
Redis holds the connection map, and in a chat app it holds more than that: presence, pub/sub for fanout across function instances, and typing indicators if you want them. Redis fits better than a document store here because the app broadcasts to the connection IDs as well as storing them.
DynamoDB or Postgres holds the messages themselves. Delivery and durability are separate problems, so keep chat history out of the connection store.
The Management API is how a Lambda pushes a message back down a socket it did not open. That detail is behind the cost surprise described next.
The $7.30 a month you pay before any traffic
A Lambda that reads ElastiCache has to be in a VPC. The API Gateway Management API is a public endpoint, and a Lambda inside a VPC can't reach it without a route out.
So you need either a NAT Gateway at $32.85 a month or an interface endpoint at $7.30 per availability zone. Neither scales with traffic, and both bill whether anyone sends a message or not.
I haven't seen this in any pricing calculator, and at small scale it is a large part of the invoice.
What it costs
API Gateway bills two meters:
| Rate | |
|---|---|
| Messages, either direction | $1.00 per million, metered in 32 KB increments |
| Connection minutes | $0.25 per million |
One always-on connection costs 1.08 cents a month, and one million messages cost a dollar. Every table below follows from those two numbers.
This is the monthly floor before any traffic:
| Serverless | Persistent server | |
|---|---|---|
Redis (cache.t4g.micro, Valkey) | $9.34 | $9.34 |
| VPC interface endpoint | $7.30 | — |
Instance (m7g.medium, not burstable) | — | $29.78 |
| Floor | $16.64 | $39.12 |
The server column uses a non-burstable instance. A t4g.small looks cheaper at $12.26, but it gives you 0.4 vCPU sustained. Fanout means a JSON.stringify and a socket write per recipient, and every reconnection costs 1–5 ms of CPU for the TLS handshake. With a few thousand users reconnecting every ten minutes, that is enough handshakes per second to drain the credits. Comparing serverless against a burstable instance flatters the server.
Group size multiplies the bill
Fanout is what sets a messaging app apart from other WebSocket workloads. API Gateway bills each message once on the way in and once for every recipient, so in a group of fifty, one person types once and API Gateway charges you fifty times.
Here are the same hundred users, each sending fifty messages a day, with only the group size changing:
| Group size | Messages billed/month | Serverless |
|---|---|---|
| 1:1 only | 300,000 | $17.41 |
| Groups of 10 | 1,500,000 | $18.72 |
| Groups of 50 | 7,500,000 | $25.27 |
The number of messages sent is the same in all three rows. Only the number of recipients changed, and the bill with it.
The bill at seven sizes
These figures use groups of ten, roughly the average for a real chat app:
| Users | Messages/month | Serverless | One server | HA pair |
|---|---|---|---|---|
| 100 | 1.5M | $18.72 | $39.27 | $85.48 |
| 500 | 7.5M | $27.02 | $39.82 | $86.03 |
| 1,000 | 15M | $37.49 | $40.51 | $86.72 |
| 2,500 | 37.5M | $69.07 | $42.58 | $88.79 |
| 5,000 | 75M | $121.70 | $46.04 | $92.25 |
| 10,000 | 150M | $226.95 | $52.95 | $99.16 |
| 25,000 | 375M | $542.70 | $96.83 | $166.18 |
At a hundred users everything costs less than nineteen dollars a month, and sixteen of those are the floor. The traffic itself costs two dollars.
Where servers become cheaper
| Group size | vs one server | vs HA pair | Messages/month at the HA crossover |
|---|---|---|---|
| 1:1 | 3,000 users | 9,000 | 27M |
| Groups of 5 | 1,900 | 5,700 | 43M |
| Groups of 10 | 1,200 | 3,550 | 53M |
| Groups of 20 | 700 | 2,000 | 60M |
| Groups of 50 | 300 | 900 | 68M |
The crossover measured in users falls as groups get bigger, from nine thousand for one-to-one chat to nine hundred for groups of fifty. The crossover measured in messages rises, from 27 million to 68 million.
So fanout lowers the number of users at which servers win and raises the number of messages. The reason is on the server side: a server broadcasting each message to fifty recipients burns CPU for every recipient, so it needs a bigger instance sooner. Fanout makes both architectures more expensive, and serverless cost rises in a straighter line.
In practice, if your app is one-to-one, serverless comfortably carries you past a few thousand users. If it has large groups, plan the move at around a thousand.
The four limits that decide when you leave
Unlike the price, these limits are mostly out of your hands: three of the four can't be raised at all, and the first only through a quota increase from AWS.
500 new connections per second, per account, per region
This is the limit that sets the ceiling, and it rarely gets mentioned.
Every connection is closed after two hours regardless of activity, and any socket idle for ten minutes is closed too. So clients reconnect constantly, and the steady-state reconnection rate is the number of concurrent users divided by the session length.
| Client reconnects every | Maximum concurrent users |
|---|---|
| 10 minutes | 300,000 |
| 30 minutes | 900,000 |
| 120 minutes (the hard maximum) | 3,600,000 |
Without a quota increase from AWS, the table above is the maximum. It also has a consequence for design: the longer a socket stays open, the more users the same account can hold. A client that sends pings to stay under the ten-minute idle limit, instead of reconnecting, gets three times the headroom with 30-minute sessions, and twelve times at the two-hour maximum.
The limit applies per account, so a second WebSocket API in the same region shares it.
2-hour maximum connection duration
Because every socket is closed after two hours, a user with the app open all day reconnects at least twelve times, and each reconnection writes a new entry in your connection store.
A design can't rely on a long-lived connection, so build reconnection in from the first commit instead of adding it later as a resilience feature.
10-minute idle timeout
A socket with no traffic is closed after ten minutes. In a chat app that describes most sockets most of the time, because people read far more than they type.
You need a keepalive with a clear margin below ten minutes; five to seven minutes is the usual choice. A keepalive is a message, so it is billed. At seven-minute intervals that comes to about 6,200 pings per user per month, roughly 0.6 cents per user. For a thousand users that is $6 a month of protocol overhead, more than half of what the same users' connection minutes cost.
128 KB per message, billed in 32 KB increments
A 33 KB message is billed as two. Attachments don't belong on the socket: put them in object storage and send the key.
Two softer limits
Lambda concurrency defaults to 1,000 per region. At 50 ms per invocation that is 20,000 messages a second before throttling, which you won't hit by accident, and the limit can be raised.
The integration timeout is 29 seconds, which only matters if a handler does something it shouldn't.
What I'd build at each size
Under a thousand users, I'd use this architecture without hesitation. At a hundred users it costs nineteen dollars a month, with no server to patch. Traffic is 10% of that invoice, so there is nothing to win by optimizing.
Between one and five thousand users, it is still fine for one-to-one chat and getting expensive for large groups. This is the point to check whether you're sending messages you don't need. Batching ten presence updates into one cuts their cost tenfold without touching infrastructure, and it is the only optimization in this model worth doing before you change architecture.
Above five thousand users, or above fifty million messages a month, I'd move the socket layer onto servers behind a load balancer and keep everything else. Redis, the message store and the client protocol all stay. You replace one component instead of rewriting the app, which is the reason to keep the connection layer thin from the start.
Above 300,000 concurrent users you reach the default account limit on new connections. Only a quota increase from AWS moves it, and a system that size needs a different architecture and a different post.
What serverless saves: about $23 a month
Serverless WebSockets are cheap because you don't pay for an idle instance. That saving is about $23 a month, the difference between the two floors.
Everything above the floor is metered. At twenty-five thousand users in groups of ten, you pay more than three times what a pair of servers costs.
Where the cost lines cross matters less than whether the price difference is worth more than the attention a server would take. At a hundred users, twenty dollars a month for never having to think about a server is clearly worth it. At twenty-five thousand users, serverless costs about three hundred and eighty dollars a month more than running two servers. Someone should make that choice on purpose instead of inheriting it from a prototype that worked.
All figures are US East list rates as of mid-2026, with no API Gateway free tier assumed and Lambda's permanent free tier applied. They exclude load balancer LCU charges, cross-AZ transfer, backups, reserved pricing and message storage, and several of those would move the server column up. This is arithmetic on published rates, not a measurement of a running system.
Writing
If you’re working on something similar, send us the details.
The first call takes 30 minutes, and you’re under no obligation.