All docs

Limits & Quotas

Every limit that applies to fn0 Cloud, in one place. Self-hosted fn0 has none of these except the per-project compute egress quota and the outbound destination policy, which the worker enforces wherever it runs; run it yourself and the rest are yours to change.

fn0 Cloud is not open yet; values below are the planned launch limits and may be adjusted before general availability.

Per-request runtime limits

These apply to every invocation — HTTP requests, queue tasks, and cron runs — on every plan.

LimitValue
CPU time50 ms
Memory128 MB
Max duration15 seconds
Request headers128 KB
Request body100 MB
Response headers128 KB
Response bodyUnlimited
Subrequests50 per request

CPU time counts only time spent executing your code. Waiting on I/O — a database query, an upstream API call, a slow LLM response — costs nothing.

The 100 MB request-body value is a transport-size limit, not an in-memory buffering allowance. Request and response bodies must remain backpressured streams end to end. Buffering a body is still subject to the 128 MB application memory limit, and every request remains subject to the CPU and duration limits. Presigned object-storage URLs are recommended for durable file uploads, but applications may stream large bodies through compute when needed. Request and response streaming is implemented end to end; see the HTTP body streaming design.

A request larger than the limit is refused before your application runs. Through fn0's edge the refusal comes from the edge itself, so the 413 your client sees is an HTML page rather than the application's own response.

The unlimited response-body value is a size limit, not a time allowance. A response streams under backpressure within the same 15-second duration, so a client that cannot read the whole body before the deadline has its response cut off. Serve downloads that need longer than that from object storage instead of through compute.

Guest-to-host control requests have smaller buffering limits than the general transport limit. These are worker safety bounds rather than billing quotas; self-hosted operators can change them in the worker source.

OperationRequest body
Queue enqueue and cross-project enqueue128 KiB
Vault operations16 KiB
Static page cache purge256 KiB
WebSocket singleton connect256 KiB
WebSocket singleton activation and abort16 KiB
OTLP metrics, logs, and traces export8 MiB

The worker enforces these values while reading each body, including bodies without a declared length. An oversized control request receives HTTP 413 from the guest's outbound call. Object storage and regular WebSocket message bodies remain streamed and are not subject to these buffering limits.

The first request to reach a cold JavaScript instance runs on a much larger allowance, because it also pays for module instantiation and the renderer's first uncompiled pass — work later requests inherit for free. Requests that arrive while an instance is still cold get the same allowance. The limit above is what a warm instance is held to.

WebSocket limits

LimitValue
Inbound messageNo explicit fn0 byte cap
Outbound messageNo explicit fn0 byte cap
Waiting inbound messages per connection4
Active outbound send per connection1
Waiting outbound sends per connection4
Active invocations per project per worker32
Waiting invocations per project per worker128
Connections per project per worker1,000, provisional
Connections per worker process10,000, provisional
Singleton declarations per project1,000

Outbound bodies stream without a declared length. Inbound and outbound messages remain subject to transport backpressure, available worker and application memory, the per-connection queue limits, and the invocation's remaining 15-second duration. Inbound callback dispatch materializes a complete message before the application sees it. The absence of an explicit byte cap is not a promise of unlimited memory or bandwidth. WebSocket delivery is at-most-once and not durable. Reconnecting clients synchronize authoritative state through HTTP.

Outbound destinations

Application code may name any URL, but the worker opens the socket, so the worker refuses destinations that are not on the public internet. This covers Rust HTTP requests, JavaScript fetch, and outbound and singleton WebSockets. Refused ranges include loopback, private networks (10/8, 172.16/12, 192.168/16, fc00::/7), link-local and cloud metadata (169.254/16, fe80::/10), carrier-grade NAT (100.64/10), multicast, documentation, benchmarking, and reserved ranges, and IPv6 forms that embed one of those IPv4 addresses.

The worker resolves the hostname, drops non-public answers, and connects to the exact address it approved. TLS still verifies the original hostname. Every new connection and every singleton reconnect resolves again, so DNS that later points at a private address is refused at that point. A request refused this way fails with destination-IP-prohibited in Rust, a 403 response from JavaScript fetch, and WebSocketConnectError::DestinationForbidden for WebSockets.

Self-hosted operators who intentionally reach private services set FN0_ALLOW_PRIVATE_OUTBOUND_DESTINATIONS=true on the worker. The variable accepts only true or false; any other value stops the worker at startup. forte dev applies no destination policy and no egress quota.

Monthly quotas — one dollar plan

Projects & domains

QuotaValueNotes
Projects1
Custom domains1Automatic TLS — point a CNAME and you're done

Compute

QuotaValueNotes
CPU pool500 CPU-minutes / month≈ 2M server-rendered pages at ~15 ms each

The pool is monthly, not daily. A launch-day traffic spike draws on the whole month's budget instead of hitting a daily wall.

Network

QuotaValueNotes
Compute egress20 GB / monthBytes your application sends out of the platform; see below
Static asset downloadsUnlimitedServed through the CDN cache — never metered, never counted as egress

Compute egress counts, per project and per UTC calendar month:

  • HTTP response bodies the worker sends to clients.
  • WebSocket message payloads the application sends, on any connection kind.
  • Request bodies of the application's own outbound HTTP calls, from Rust and from JavaScript fetch.

It does not count headers, WebSocket control frames, received bytes, queue tasks, cron runs, or callbacks the platform invokes internally. The fn0 control project is not metered.

A byte is charged before it is handed to the network. When the monthly quota is exhausted, new HTTP requests receive 429 Monthly egress quota exhausted, a response or request body that is streaming stops at the next chunk, WebSocket sends fail with EgressQuotaExceeded, and the connection closes with code 1008. Bytes already written stay written. Workers take credit from the control plane in chunks of up to 64 MiB and credit is charged when it is granted, so a worker restart can count up to one unused chunk. When the control plane cannot be reached, egress is refused rather than allowed.

The quota is stored per project in the control database as ProjectEgressQuotaDoc, with monthly_egress_limit set to {"Bytes": <limit>} or "Unlimited". New projects are created with 20 GB. Operators change it by editing that document; a project without the document can send nothing. A raised limit takes effect within a minute on workers that already saw the old limit exhausted. Monthly usage is kept in ProjectEgressUsageDoc under YYYY-MM.

Static assets (your deployed build's files) are served from your own Cloudflare account through your own CDN hostname, so their bandwidth is between you and Cloudflare — R2 egress is free. Object storage downloads go directly to the storage endpoint and are not cached.

Document database

QuotaValueNotes
Active databases1
Storage500 MB
Row reads150M / month
Row writes1M / monthA busy community site writes ~300k a month

Object storage

Object storage, public objects, deployed static assets and cached pages all live in your own Cloudflare account, connected with forte cloud login and forte cloud init. Their storage and operation limits are whatever your Cloudflare plan gives you — R2's forever-free tier is 10 GB, 1M writes and 10M reads a month — and fn0 does not meter or cap them.

What fn0 still limits is how fast your app can mint presigned URLs and ask for purges, because those are actions the runtime takes on your behalf:

QuotaValueNotes
Presigned URLs minted1k / hourPer project; a runaway loop stops here, not on your bill
Presigned URL expiry5 minutes maximumLonger requested expiries are clamped, not rejected
Public object purges1k / hourExplicit public::purge calls; the purge a put triggers on its own is not counted

Pages cached by lazy static page caching live in a private bucket in your account.

Treat presigned URLs as opaque, short-lived strings: mint one right before use, and never store one or parse its structure — the URL format may change.

Exceeding the mint ceiling refuses new presigned URLs (429) for the rest of the hour; your deployed app keeps serving and already-minted URLs stay valid until they expire.

Metrics

Metrics your app records through the SDK are exported as OpenTelemetry series. A series is one metric name plus one distinct combination of label values — http_requests{route="/posts",status="200"} and http_requests{route="/posts",status="404"} are two series.

LimitValueNotes
Active series1,000 per projectA series counts as active while it keeps reporting
Metric names100 per project
Label values100 per label keyThe usual cause of an explosion — see below

A series that stops reporting for 5 minutes goes inactive and frees its slot.

Over the limit, existing series keep reporting and only new series are dropped, so a running app never loses the series it already had. Drops are reported back to you as fn0.metrics.dropped.

Nearly every cardinality explosion comes from putting an unbounded value in a label: a user ID, a request ID, a raw URL path. Label with values you could list in advance — route templates, status codes, regions — and 1,000 series is a comfortable ceiling. If your app legitimately needs more, contact us.

Queues & cron

Queue task execution and cron runs consume the shared monthly CPU pool — there is no separate billing for them.

LimitValueNotes
Cron jobs10 per project
Cron interval1 minute minimum
Queue message size128 KB
Queue backlog100k messages per project

Monthly quotas — free plan

To be announced. The free plan targets trying things out: one project on a custom domain from your own Cloudflare account, with quotas sized for development traffic.

When you outgrow these

  • Pay-as-you-go overage (planned) — keep growing past the included quotas without hitting a wall.
  • Bring your own resources (planned) — connect your own R2 bucket or Turso database; quotas for that resource become whatever your own account allows.
  • Self-host — fn0 is open source. Run it on your own infrastructure with no limits at all.