{"record":{"id":"f5b8a4b5ca61bbe3","repo":"nextcloud/server","slug":"too-many-calendars-created","errorCode":null,"errorMessage":"Too many calendars created","messagePattern":"Too many calendars created","errorType":"exception","errorClass":"TooManyRequests","httpStatus":429,"severity":"error","filePath":"apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php","lineNumber":64,"sourceCode":"\t\t}\n\t\t$user = $this->userManager->get($this->userId);\n\t\tif ($user === null) {\n\t\t\t// We only care about authenticated users here\n\t\t\treturn;\n\t\t}\n\n\t\t$pathParts = explode('/', $path);\n\t\tif (count($pathParts) === 3 && $pathParts[0] === 'calendars') {\n\t\t\t// Path looks like calendars/username/calendarname so a new calendar or subscription is created\n\t\t\ttry {\n\t\t\t\t$this->limiter->registerUserRequest(\n\t\t\t\t\t'caldav-create-calendar',\n\t\t\t\t\t$this->config->getValueInt('dav', 'rateLimitCalendarCreation', 10),\n\t\t\t\t\t$this->config->getValueInt('dav', 'rateLimitPeriodCalendarCreation', 3600),\n\t\t\t\t\t$user\n\t\t\t\t);\n\t\t\t} catch (RateLimitExceededException $e) {\n\t\t\t\tthrow new TooManyRequests('Too many calendars created', 0, $e);\n\t\t\t}\n\n\t\t\t$calendarLimit = $this->config->getValueInt('dav', 'maximumCalendarsSubscriptions', 30);\n\t\t\tif ($calendarLimit === -1) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t$numCalendars = $this->calDavBackend->getCalendarsForUserCount('principals/users/' . $user->getUID());\n\t\t\t$numSubscriptions = $this->calDavBackend->getSubscriptionsForUserCount('principals/users/' . $user->getUID());\n\n\t\t\tif (($numCalendars + $numSubscriptions) >= $calendarLimit) {\n\t\t\t\t$this->logger->warning('Maximum number of calendars/subscriptions reached', [\n\t\t\t\t\t'calendars' => $numCalendars,\n\t\t\t\t\t'subscription' => $numSubscriptions,\n\t\t\t\t\t'limit' => $calendarLimit,\n\t\t\t\t]);\n\t\t\t\tthrow new Forbidden('Calendar limit reached', 0);\n\t\t\t}\n\t\t}","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/CalDAV/Security/RateLimitingPlugin.php#L46-L82","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Space out or queue calendar creations and wait for the period (default 3600 s) to elapse before retrying","Raise the limit for the affected instance: occ config:app:set dav rateLimitCalendarCreation --value 100","Shrink the measurement window if a short burst is legitimate: occ config:app:set dav rateLimitPeriodCalendarCreation --value 600","For bulk provisioning, bypass HTTP and use CalDavBackend/occ tooling server-side instead of repeated MKCALENDAR requests"],"exampleFix":"// before: tight loop hitting the CalDAV endpoint\nforeach ($names as $name) {\n    $client->request('MKCALENDAR', \"/remote.php/dav/calendars/$user/$name/\");\n}\n\n// after: throttle client-side and back off on 429\n$created = 0;\nforeach ($names as $name) {\n    if ($created >= 10) { sleep(3600); $created = 0; }\n    $resp = $client->request('MKCALENDAR', \"/remote.php/dav/calendars/$user/$name/\");\n    if ($resp->getStatus() === 429) { sleep(3600); continue; }\n    $created++;\n}","handlingStrategy":"retry","validationCode":"// Client-side sliding-window counter before any MKCALENDAR\nconst WINDOW_MS = 3600_000, MAX = 10; // mirror dav defaults\nconst stamps = [];\nfunction mayCreateCalendar() {\n  const now = Date.now();\n  while (stamps.length && now - stamps[0] > WINDOW_MS) stamps.shift();\n  return stamps.length < MAX;\n}","typeGuard":null,"tryCatchPattern":"try {\n    await client.mkCalendar(`/remote.php/dav/calendars/${user}/${name}/`);\n} catch (e) {\n    if (e.status === 429) {\n        // honor the configured period (default 3600 s) before retrying\n        await sleep(rateLimitPeriodCalendarCreationMs);\n        return client.mkCalendar(`/remote.php/dav/calendars/${user}/${name}/`);\n    }\n    throw e;\n}","preventionTips":["Throttle calendar creation client-side to the configured rateLimitCalendarCreation / rateLimitPeriodCalendarCreation values","Raise the limit via occ config:app:set dav rateLimitCalendarCreation on servers used for provisioning or CI","Reuse existing calendars instead of delete-and-recreate cycles"],"tags":["caldav","dav","rate-limit","http-429","nextcloud","beforebind"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}