nextcloud/server · error · TooManyRequests

Too many calendars created

Error message

Too many calendars created

What it means

Thrown by the CalDAV RateLimitingPlugin, which hooks the Sabre 'beforeBind' event and fires whenever a new calendar or subscription is created at a path like calendars/<user>/<name>. It calls the OC rate limiter with the identifier 'caldav-create-calendar', the app config rateLimitCalendarCreation (default 10) and rateLimitPeriodCalendarCreation (default 3600 seconds), and converts the resulting RateLimitExceededException into a TooManyRequests DAV exception (HTTP 429). It is an anti-flood guard so one authenticated user cannot create unbounded calendars in a short window.

Solutions

  1. Space out or queue calendar creations and wait for the period (default 3600 s) to elapse before retrying
  2. Raise the limit for the affected instance: occ config:app:set dav rateLimitCalendarCreation --value 100
  3. Shrink the measurement window if a short burst is legitimate: occ config:app:set dav rateLimitPeriodCalendarCreation --value 600
  4. For bulk provisioning, bypass HTTP and use CalDavBackend/occ tooling server-side instead of repeated MKCALENDAR requests

Example fix

// before: tight loop hitting the CalDAV endpoint
foreach ($names as $name) {
    $client->request('MKCALENDAR', "/remote.php/dav/calendars/$user/$name/");
}

// after: throttle client-side and back off on 429
$created = 0;
foreach ($names as $name) {
    if ($created >= 10) { sleep(3600); $created = 0; }
    $resp = $client->request('MKCALENDAR', "/remote.php/dav/calendars/$user/$name/");
    if ($resp->getStatus() === 429) { sleep(3600); continue; }
    $created++;
}
Defensive patterns

Strategy: retry

Validate before calling

// Client-side sliding-window counter before any MKCALENDAR
const WINDOW_MS = 3600_000, MAX = 10; // mirror dav defaults
const stamps = [];
function mayCreateCalendar() {
  const now = Date.now();
  while (stamps.length && now - stamps[0] > WINDOW_MS) stamps.shift();
  return stamps.length < MAX;
}

Try / catch

try {
    await client.mkCalendar(`/remote.php/dav/calendars/${user}/${name}/`);
} catch (e) {
    if (e.status === 429) {
        // honor the configured period (default 3600 s) before retrying
        await sleep(rateLimitPeriodCalendarCreationMs);
        return client.mkCalendar(`/remote.php/dav/calendars/${user}/${name}/`);
    }
    throw e;
}

Prevention

When it happens

Trigger: Performing MKCALENDAR/MKCOL (any bind that creates a node) on a three-segment path calendars/<username>/<newname> more than 10 times within one hour as the same user; typical with migration/import scripts, provisioning loops, or CalDAV clients that delete-and-recreate calendars repeatedly.

Common situations: Bulk import or sync tools creating many calendars in a loop; automated e2e tests running against a real server with default limits; admins unaware of the rate limit seeing 429 responses in client logs after Nextcloud 27+ introduced the plugin.

Related errors


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/f5b8a4b5ca61bbe3. Report an issue: GitHub.

Appendix: source

Thrown at apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php:64

		}
		$user = $this->userManager->get($this->userId);
		if ($user === null) {
			// We only care about authenticated users here
			return;
		}

		$pathParts = explode('/', $path);
		if (count($pathParts) === 3 && $pathParts[0] === 'calendars') {
			// Path looks like calendars/username/calendarname so a new calendar or subscription is created
			try {
				$this->limiter->registerUserRequest(
					'caldav-create-calendar',
					$this->config->getValueInt('dav', 'rateLimitCalendarCreation', 10),
					$this->config->getValueInt('dav', 'rateLimitPeriodCalendarCreation', 3600),
					$user
				);
			} catch (RateLimitExceededException $e) {
				throw new TooManyRequests('Too many calendars created', 0, $e);
			}

			$calendarLimit = $this->config->getValueInt('dav', 'maximumCalendarsSubscriptions', 30);
			if ($calendarLimit === -1) {
				return;
			}
			$numCalendars = $this->calDavBackend->getCalendarsForUserCount('principals/users/' . $user->getUID());
			$numSubscriptions = $this->calDavBackend->getSubscriptionsForUserCount('principals/users/' . $user->getUID());

			if (($numCalendars + $numSubscriptions) >= $calendarLimit) {
				$this->logger->warning('Maximum number of calendars/subscriptions reached', [
					'calendars' => $numCalendars,
					'subscription' => $numSubscriptions,
					'limit' => $calendarLimit,
				]);
				throw new Forbidden('Calendar limit reached', 0);
			}
		}

View on GitHub (pinned to ecdeb153ff)