Skip to main content
nestrs exposes security controls as explicit opt-in builder calls on NestApplication. Nothing is enabled by default — small services that don’t need a particular control don’t pay for it. This page covers each control in order of how frequently you’ll need it, followed by a checklist you can run through before shipping.

Security headers

Call use_security_headers with a SecurityHeaders value to inject protective HTTP headers on every response. SecurityHeaders::default() sets the most broadly applicable headers:
The defaults set:
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • X-XSS-Protection: 0
  • Permissions-Policy: geolocation=(), microphone=(), camera=()
For browser-facing APIs that need Helmet-style hardening, use SecurityHeaders::helmet_like(), which adds Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, X-DNS-Prefetch-Control, X-Download-Options, and X-Permitted-Cross-Domain-Policies on top of the defaults:
helmet_like() does not set CSP or HSTS automatically — configure both explicitly for your deployment. nestrs runs behind a reverse proxy in most production topologies; HSTS may already be set at the edge.

CORS

CORS is off until you call enable_cors. Pass a CorsOptions value with an explicit origin allowlist for browser clients:
For local development only, CorsOptions::permissive() allows all origins. nestrs emits a tracing WARN at startup if you use permissive CORS when NESTRS_ENV, APP_ENV, or RUST_ENV is set to production.
You cannot combine allow_credentials(true) with a wildcard * origin — browsers reject it. Always set an explicit list when you need credentialed cross-origin requests.

Rate limiting

use_rate_limit accepts a RateLimitOptions value. The defaults allow 100 requests per 60-second window per client IP:
For shared rate limits across multiple instances, enable the cache-redis feature and call .redis(url, key_prefix):

Route-level throttling

Where the global rate limiter applies one budget to everything, use_throttler applies per-route budgets that individual handlers declare with decorators:
Per route:
  • #[throttle(n, "second" | "minute" | "hour")] overrides the global spec for that route; rejected requests get 429 with Retry-After and X-RateLimit-Remaining headers.
  • #[skip_throttle] exempts a route entirely.
  • ThrottlerOptions.global is the fallback for undecorated routes — None means only explicitly decorated routes are throttled.
  • Like the rate limiter, the throttler supports a shared Redis backend (cache-redis feature) and inherits the trusted-proxy hop count (below).

Proxy topology and client identity

Both the rate limiter and the throttler key on the client IP, and the ClientIp extractor exposes it to handlers. Behind a reverse proxy or load balancer, the socket address is the proxy’s — so client identity has to come from X-Forwarded-For / X-Real-IP. Those headers are attacker-spoofable, so nestrs never trusts them unless you declare your topology:
  • use_trusted_proxy_headers(hops) declares how many trusted proxies sit in front. The client address is read right-most-first: with the common client → LB → app topology, the entry appended by your load balancer is used and any attacker-supplied prefix is ignored.
  • Forwarded headers are never consulted without this setting — trusting them by default would allow trivial IP-based rate-limit bypass.
  • The rate limiter and throttler inherit the hop count automatically, so they key on the same identity as ClientIp. Divergent per-component overrides are logged as a tracing WARN.
  • Common topologies: direct exposure → omit the call (default 0 hops); one load balancer → 1; LB + CDN → 2 (count only hops you control and trust).

CSRF protection

CSRF protection targets cookie-based browser flows. Bearer token APIs in Authorization headers are not CSRF-bound and do not need this.
1

Enable the csrf feature

2

Enable cookies and CSRF middleware

CsrfProtectionConfig uses a double-submit pattern: your app sets a cookie on safe requests, and the client must echo the same value in an X-CSRF-Token header on POST/PUT/PATCH/DELETE.
If you enable use_cookies() or use_session_memory() without wiring use_csrf_protection, nestrs emits a tracing WARN at router build time. Treat this as a release blocker for any browser-facing endpoint that mutates state.

Cookies and sessions

Always pair cookie or session middleware with use_csrf_protection for any endpoint that accepts browser-originated mutations.

Guards and authentication

For token validation you implement CanActivate (HTTP guards) or AuthStrategy (credential-validation strategies) and compose them on controllers or individual routes. For full OAuth2 — client flows, JWKS-based resource-server validation, social providers — see the OAuth2 guide; for per-row authorization see the authorization guide.

CanActivate guard

BearerToken extractor

For routes that unconditionally require a bearer token, use the BearerToken extractor directly — it returns 401 when the header is absent or malformed:
Use OptionalBearerToken when the header is optional:

AuthStrategyGuard

AuthStrategyGuard<S> wraps any type that implements AuthStrategy and can be derived as Default. Wire it onto a controller or individual route with #[use_guards]:

Body limits and timeouts

Set a maximum request body size and a per-request timeout for any public endpoint:

Production error sanitization

5xx JSON bodies are sanitized by default whenever the runtime environment is production — the first non-empty value of NESTRS_ENV, APP_ENV, or RUST_ENV is compared (case-insensitively) against production / prod. Sanitization replaces the internal message with "An unexpected error occurred", drops any errors payload, and inserts the canonical reason phrase, so stack traces and internal detail never reach clients. No builder call is needed in production; enable_production_errors_from_env() re-reads the environment (kept for explicitness), and enable_production_errors() forces sanitization on in any environment — useful for staging that runs with NESTRS_ENV=staging:
disable_production_errors() opts out even in production — for internal admin services behind a trusted boundary that want detailed errors.

Pre-production checklist

Run through this list before deploying a browser-facing or multi-tenant API:
  • use_security_headers(SecurityHeaders::default()) is called, or helmet_like() for richer isolation.
  • CSP is set explicitly for HTML-serving endpoints.
  • enable_cors(...) uses an explicit origin allowlist — not CorsOptions::permissive().
  • allow_credentials(true) is not combined with a wildcard origin.
  • Cookie or session flows have use_csrf_protection(...) wired.
  • The csrf Cargo feature is enabled alongside cookies.
  • No tracing WARN about missing CSRF at startup.
  • use_rate_limit(...) is configured (or enforced at the edge).
  • use_throttler(...) covers expensive routes with tighter #[throttle] budgets where needed.
  • use_body_limit(...) is set per endpoint class.
  • use_request_timeout(...) is set for public routes.
  • use_trusted_proxy_headers(hops) matches the real proxy chain — 0 if directly exposed.
  • No component overrides trusted_proxy_hops to a divergent value (WARN at startup if so).
  • 5xx sanitization is active (default in production; disable_production_errors() only behind a trusted boundary).
  • cargo audit is passing locally and in CI.
  • Secrets are loaded from env / secret manager, not committed to source.
  • Logs do not contain tokens, passwords, or API keys.
The defaults for every control are listed in docs/src/secure-defaults.md (in the repository), which also includes the full secure-by-default matrix.