Serverless-first. Built for cold starts, tight memory, and one binary.Deploy guide →

Limitations

Fairyfly trades breadth for speed and auditability. This page lists every hard cap; each guide links back here from its own summary table.

HTTP server

AreaLimitWhen hit
Concurrent connections512The 513th waits for a free slot
Request body~4 KB (shares the 4 KB read buffer)Beyond → 413 Payload Too Large
Response body10 MB hard cap (64 KB stage buffer spills to the heap)Beyond 10 MB → 500
Response headers2048 bytesHeaders beyond the cap are dropped
Hung handler30sConnection fails with 504
Bind addressAlways 0.0.0.0No host option, no FF_PORT
Plain listener protocolHTTP/1.1No HTTP/2 framing without TLS
HTTP/2TLS + ALPN only (nghttp2)Plain sockets stay HTTP/1.1
FF_ECHOAny value set (even empty — only unsetting disables)Every request gets canned 200 {"message":"ok"}

Fetch (client)

AreaLimitWhen hit
Concurrent fetches16The 17th waits for a free slot
Body stage per slot64KBLarger bodies spill to chunks/heap
I/O timeout30sSlow servers fail
RedirectsFollowed, max 5 hopsThrows Too many redirects
Abort / signalNot supportedsignal / timeout / redirect init keys are ignored
ProtocolHTTP/1.1No HTTP/2 client
ProxyHTTP proxies via env vars (HTTP_PROXY / HTTPS_PROXY / ALL_PROXY); NO_PROXY bypassNo SOCKS support; no https:// proxy URLs (TLS to the proxy); env is read once at launch

WebSocket

AreaLimitWhen hit
Client sockets64 per processExtra connects fail
Message size16384 bytesLarger frames truncate
Handshake URL path512 bytesLonger paths fail
Server socket APIsend / sendBinary onlyNo id, readyState, or close on server sockets
ProxySame env vars as fetchws:// needs a CONNECT-capable proxy

UDP

AreaLimitWhen hit
Concurrent sockets64 (shared pool)udp.socket: no socket slots available
In-flight sendto calls64send: SendQueueFull
Maximum datagram65507 bytessend: MessageTooLong
Pending receives per socket1recvfrom: RecvPending
AddressesIP literals onlyHostnames rejected (...: invalid address)
setsockopt / multicastMissingNo socket options

Timers and workers

AreaLimitWhen hit
Live timers128TypeError: too many timers (max 128)
Extra timer args8 after msExtras ignored
Workers8too many workers (max 8)
Worker heap32 MB + 1 MB JS stack (8 MB thread stack)—
Worker message4 MB per messageLarger postMessage payloads are rejected
process.nextTickMissingUse queueMicrotask
// instead of process.nextTick(fn):
queueMicrotask(fn);

Filesystem

AreaLimitWhen hit
Path length4096 bytesLonger paths fail
Read per call10MB, syncLarger reads fail
API styleSync + six *Async Promise variants (readFileAsync, writeFileAsync, existsAsync, mkdirAsync, rmAsync, readdirAsync)No fs.watch — poll with timers

Console

AreaTypeDescription
Arguments printedAll per callEvery argument prints, joined with spaces
Destinationstderrconsole.log > out.txt captures nothing

Modules and language

AreaLimitWhen hit
Module systemESM onlyNo require(), no CommonJS
Relative extensionsOptional for .js./x probes ./x.js, then ./x/index.js; other extensions must be written out
Bare specifiersResolved via node_modulesNeeds ff imprint install first
BufferMissingUse Uint8Array
DOM / windowMissingBackend only
Subprocess APISeparate globalchild_process.spawn / exec / execSync — see the child_process section below

SQLite

AreaLimitWhen hit
db.execFirst statement onlyMulti-statement SQL needs db.execNoArgs
ParametersPositional ? onlyNamed :x / $x are not supported
Integer precision±2⁵³Beyond → precision loss in JS

Postgres

AreaLimitWhen hit
Pool10 connectionsExtra queries queue
TLS (sslmode)Not supported—
Connection/socket timeoutsNoneSlow queries wait indefinitely
rowCountReturned rows onlyINSERT/UPDATE row counts are not reported
COPYNot supported—

Process

AreaLimitWhen hit
process.envSnapshot at bootAssigning to it does not affect the OS environment
stdout/stdin streamsMissingNo process.stdout.write or stdin reads
hrtime, killMissing—
Runaway JS watchdogNoneAn infinite loop hangs the process

child_process

AreaLimitWhen hit
Concurrent children8The 9th spawn throws / exec rejects
maxBuffer1 MB per stream (default)Overflow SIGTERMs the child; exec rejects, spawn sets truncated
PlatformPOSIX only (macOS/Linux)Windows throws
stdin, streams, eventsMissingUse wait() + captured stdout / stderr
ff exitChildren are not killedCall kill() yourself

TLS and bytecode

AreaLimitWhen hit
TLS versionsTLS 1.2 (BearSSL build)No TLS 1.3 in the default build
ff compile input20MB sourceLarger inputs fail
ff test file10MBLarger test files are not loaded
REPL line8192 bytesLonger lines truncate
ff initAlways overwritesBack up ff.json if you customized it

Packages

AreaLimitWhen hit
Package typePure-JS ESM onlyrequire(, module.exports, or node: imports are rejected
Version graphOne flat node_modulesA conflicting transitive version is a hard error
Direct sourcesPublic HTTPS only; single package at the archive rootPrivate repos, auth, monorepo subdirectories unsupported
Direct archive size128 MB download capLarger archives fail
Raw archive URLsMust end in .tar.gz or .tgzOther URL forms are rejected
Build scriptsNot runThe archive must already contain runnable JS
Direct refsDefault to HEAD; branch/tag names are mutableThe lock's integrity check fails closed if the archive changes

What is not limited (common misconceptions)

  • res.blob() and res.formData() work on Request and Response.
  • HTTP redirects are followed (up to 5 hops) by fetch.
  • Port shorthand http.serve(3000, handler) works; the default port is 3000.
  • make install targets ~/.local/bin/ff, not /usr/local/bin.