Known Limitations
Every framework has edges. This page collects the ones worth knowing before you ship, in one place, so you don’t discover them in production. Each item links to the page that covers it in depth — nothing here is new behavior, it’s the honest fine print pulled together.
The theme is the same across most of them: several built-in protections keep their state in the process, not in a shared store. That’s fast and zero-config for a single instance, and exactly what you want in development. It stops being a single source of truth the moment you run more than one replica.
Single-instance state
Section titled “Single-instance state”These features are correct on one node and degrade silently when you scale horizontally. None of them error — they just enforce a weaker guarantee than you might assume.
- Per-route rate limiting (
middleware.rateLimit) is process-local. Each replica counts independently, so withNreplicas the effective cap isN × max. For a genuinely shared limit, configure@fastify/rate-limit’s Redis store throughhttp.rateLimit. See Security → the protective middleware. - Concurrency limiting (
middleware.concurrencyLimit) is process-local too. The in-flight cap is per-process. For a cluster-wide ceiling, reach for a distributed lock via@warlock.js/cache. See Security. - Idempotency (
middleware.idempotency) dedupes against the configured cache. If that cache is the in-memory driver, a retry that lands on a different replica won’t see the first request’s key and will execute twice. Point idempotency at a shared cache (Redis) in production. See Security and theEC100/EC101codes in Error handling. - The global rate limit (
http.rateLimit) is a separate, coarser backstop from the per-route middleware — and it has the same single-instance store caveat unless you wire a shared store. See Security → the global rate limit is separate.
Rule of thumb: if a protection counts requests or remembers keys, and you run more than one instance, give it a shared store (Redis) or treat its guarantee as best-effort.
Transport & proxy trust
Section titled “Transport & proxy trust”- The CORS default is permissive and currently wins over config. The plugin is registered as
{ ...config.get("http.cors"), ...defaultCorsOptions }withdefaultCorsOptions = { origin: "*", methods: "*" }spread last — so todayhttp.corscannot tightenorigin/methodsthrough config alone. If you need a locked-down origin, confirm the current behavior againstsrc/http/plugins.tsbefore relying on config. See Security → CORS. X-Forwarded-For/X-Real-IPare trusted because Fastify runs withtrustProxy: true. Those headers are client-settable.request.detectIp(),middleware.ipFilter(), and IP-scoped rate limiting all read them — so they’re only trustworthy behind a proxy you control that overwrites the header. A directly internet-exposed instance can be spoofed. See Security.
API surface
Section titled “API surface”- There is no OpenAPI / Swagger generator yet. seal schemas carry
.describe(...)metadata and JSON-schema output, and validation is declarative, but the framework does not emit an OpenAPI document from your routes today. If you need a spec, generate it from your schemas yourself. See Validation.
Repository nuances
Section titled “Repository nuances”listCached/getCachedcache against the configured cache driver. On the in-memory driver the cache is per-process, so two replicas can serve different cached snapshots until each expires. Use a shared cache driver if read-after-write consistency across instances matters. See Repositories and Cache.request.all()feeds the repository’sfilterBydirectly. Only the keys you declare infilterBybecome query filters — undeclared inputs are ignored, which is the intended safety boundary, but it also means a typo’d filter key silently does nothing rather than erroring. See Repositories.
Shutdown
Section titled “Shutdown”SIGKILLskips graceful shutdown entirely. Reverse-order connector teardown only runs forSIGINT/SIGTERM(andSIGHUPon Windows). A hard kill gives connectors no chance to close — configure your supervisor to send a terminable signal with a short grace period. See Deployment and Health checks.
See also
Section titled “See also”- Security — the protective middleware, the CORS default, proxy trust, and the full hardening checklist.
- Production Hardening Checklist — the actionable counterpart to this page: what to set before you ship.
- Deployment & production — build/start, environment selection, graceful shutdown.
- Error handling — the
HttpErrorCodescatalog, including the idempotency codes. - Health checks —
/health,/ready, and request draining on shutdown.