nextcloud/server · error · Sabre\DAV\Exception\ServiceUnavailable

File is not updatable: %1$s

Error message

File is not updatable: %1$s

What it means

At the start of PUT, file_exists() on the file's view raised StorageNotAvailableException — the backend holding the file (external storage: SMB, SFTP, S3, ...) is unavailable. It is wrapped as ServiceUnavailable (HTTP 503) with the message 'File is not updatable: <backend message>'. Despite the wording, this signals storage unavailability, not a permission problem.

Solutions

  1. Check the external storage entry in the admin UI (it reports availability) and nextcloud.log for the underlying storage exception.
  2. Repair the backend: restore the server, update credentials, fix mount options.
  3. Retry the PUT once the storage is healthy; clients should treat 503 as retryable with backoff.
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $client->request('PUT', $fileUrl, $content);
} catch (ServiceUnavailable $e) { // 503 'File is not updatable: ...'
    // backend storage down — keep the content and retry later
    queueForRetryWithBackoff($fileUrl, $content);
}

Prevention

When it happens

Trigger: PUT to a file on an external storage that is offline, has expired credentials, or exceeds backend timeouts; misconfigured mounts whose storage cannot even stat the path.

Common situations: SMB server down; SFTP credential or host-key rotation breaking auth; S3 signature errors after key changes; slow mounts tripping availability checks.

Related errors


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

Appendix: source

Thrown at apps/dav/lib/Connector/Sabre/File.php:122

	 *
	 * @throws Forbidden
	 * @throws UnsupportedMediaType
	 * @throws BadRequest
	 * @throws Exception
	 * @throws EntityTooLarge
	 * @throws ServiceUnavailable
	 * @throws FileLocked
	 * @return string|null
	 */
	#[\Override]
	public function put($data) {
		try {
			$exists = $this->fileView->file_exists($this->path);
			if ($exists && !$this->info->isUpdateable()) {
				throw new Forbidden();
			}
		} catch (StorageNotAvailableException $e) {
			throw new ServiceUnavailable($this->l10n->t('File is not updatable: %1$s', [$e->getMessage()]));
		}

		// verify path of the target
		$this->verifyPath();

		[$partStorage] = $this->fileView->resolvePath($this->path);
		if ($partStorage === null) {
			throw new ServiceUnavailable($this->l10n->t('Failed to get storage for file'));
		}
		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);

		$view = Filesystem::getView();

		if ($needsPartFile) {
			$transferId = \rand();
			// mark file as partial while uploading (ignored by the scanner)
			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . $transferId . '.part';

View on GitHub (pinned to ecdeb153ff)