Files
2026-04-17 23:26:01 +00:00

4.8 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Enable Rate Limiting for API Protection MEDIUM Prevents abuse, brute-force attacks, and DoS production, security, rate-limiting, abuse-prevention

Enable Rate Limiting for API Protection

PocketBase v0.23+ includes built-in rate limiting. Enable it to protect against brute-force attacks, API abuse, and excessive resource consumption.

v0.36.7 behavioral change: the built-in limiter switched from a sliding-window to a fixed-window strategy. This is cheaper and more predictable, but it means a client can send 2 * maxRequests in quick succession if they straddle the window boundary. Size your limits with that worst case in mind, and put Nginx/Caddy rate limiting in front of PocketBase for defense in depth (see examples below).

Incorrect (no rate limiting):

# Running without rate limiting
./pocketbase serve

# Vulnerable to:
# - Brute-force password attacks
# - API abuse and scraping
# - DoS from excessive requests
# - Account enumeration attempts

Correct (enable rate limiting):

# Enable via command line flag
./pocketbase serve --rateLimiter=true

# Or configure specific limits (requests per second per IP)
./pocketbase serve --rateLimiter=true --rateLimiterRPS=10

Configure via Admin Dashboard:

Navigate to Settings > Rate Limiter:

  • Enable rate limiter: Toggle on
  • Max requests/second: Default 10, adjust based on needs
  • Exempt endpoints: Optionally whitelist certain paths

Configure programmatically (Go/JS hooks):

// In pb_hooks/rate_limit.pb.js
routerAdd("GET", "/api/public/*", (e) => {
  // Custom rate limit for specific endpoints
}, $apis.rateLimit(100, "10s")); // 100 requests per 10 seconds

// Stricter limit for auth endpoints
routerAdd("POST", "/api/collections/users/auth-*", (e) => {
  // Auth endpoints need stricter limits
}, $apis.rateLimit(5, "1m")); // 5 attempts per minute

Rate limiting with reverse proxy (additional layer):

# Nginx rate limiting (defense in depth)
http {
    # Define rate limit zones
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=auth:10m rate=1r/s;

    server {
        # General API rate limit
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://pocketbase;
        }

        # Strict limit for auth endpoints
        location /api/collections/users/auth {
            limit_req zone=auth burst=5 nodelay;
            proxy_pass http://pocketbase;
        }

        # Stricter limit for superuser auth
        location /api/collections/_superusers/auth {
            limit_req zone=auth burst=3 nodelay;
            proxy_pass http://pocketbase;
        }
    }
}
# Caddy with rate limiting plugin
myapp.com {
    rate_limit {
        zone api {
            key {remote_host}
            events 100
            window 10s
        }
        zone auth {
            key {remote_host}
            events 5
            window 1m
        }
    }

    @auth path /api/collections/*/auth*
    handle @auth {
        rate_limit { zone auth }
        reverse_proxy 127.0.0.1:8090
    }

    handle {
        rate_limit { zone api }
        reverse_proxy 127.0.0.1:8090
    }
}

Handle rate limit errors in client:

async function makeRequest(fn, retries = 0, maxRetries = 3) {
  try {
    return await fn();
  } catch (error) {
    if (error.status === 429 && retries < maxRetries) {
      // Rate limited - wait and retry with limit
      const retryAfter = error.response?.retryAfter || 60;
      console.log(`Rate limited. Retry ${retries + 1}/${maxRetries} after ${retryAfter}s`);

      // Show user-friendly message
      showMessage('Too many requests. Please wait a moment.');

      await sleep(retryAfter * 1000);
      return makeRequest(fn, retries + 1, maxRetries);
    }
    throw error;
  }
}

// Usage
const result = await makeRequest(() =>
  pb.collection('posts').getList(1, 20)
);

Recommended limits by endpoint type:

Endpoint Type Suggested Limit Reason
Auth endpoints 5-10/min Prevent brute-force
Password reset 3/hour Prevent enumeration
Record creation 30/min Prevent spam
General API 60-100/min Normal usage
Public read 100-200/min Higher for reads
File uploads 10/min Resource-intensive

Monitoring rate limit hits:

// Check PocketBase logs for rate limit events
// Or set up alerting in your monitoring system

// Client-side tracking
pb.afterSend = function(response, data) {
  if (response.status === 429) {
    trackEvent('rate_limit_hit', {
      endpoint: response.url,
      timestamp: new Date()
    });
  }
  return data;
};

Reference: PocketBase Going to Production