nextcloud/server · warning · InvalidArgumentException

X-OC-MTime header must be a valid integer (unix timestamp),

Error message

X-OC-MTime header must be a valid integer (unix timestamp), got "%s".

What it means

Thrown by OCA\DAV\Connector\Sabre\MtimeSanitizer::sanitizeMtime() (apps/dav/lib/Connector/Sabre/MtimeSanitizer.php:19) when the mtime a client sends is not a usable number. Nextcloud lets upload clients set the file modification time via the X-OC-MTime (also X-OC-CTime and x-file-mtime in bulk upload) request header, and this sanitizer rejects anything non-numeric — including hexadecimal-looking strings like 0x1F, which would pass is_numeric() on old PHP versions.

Source

Thrown at apps/dav/lib/Connector/Sabre/MtimeSanitizer.php:19

<?php

declare(strict_types=1);

/**
 * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 */

namespace OCA\DAV\Connector\Sabre;

class MtimeSanitizer {
	public static function sanitizeMtime(string $mtimeFromRequest): int {
		// In PHP 5.X "is_numeric" returns true for strings in hexadecimal
		// notation. This is no longer the case in PHP 7.X, so this check
		// ensures that strings with hexadecimal notations fail too in PHP 5.X.
		$isHexadecimal = preg_match('/^\s*0[xX]/', $mtimeFromRequest);
		if ($isHexadecimal || !is_numeric($mtimeFromRequest)) {
			throw new \InvalidArgumentException(
				sprintf(
					'X-OC-MTime header must be a valid integer (unix timestamp), got "%s".',
					$mtimeFromRequest
				)
			);
		}

		// Prevent writing invalid mtime (timezone-proof)
		if ((int)$mtimeFromRequest <= 24 * 60 * 60) {
			throw new \InvalidArgumentException(
				sprintf(
					'X-OC-MTime header must be a valid positive unix timestamp greater than one day, got "%s".',
					$mtimeFromRequest
				)
			);
		}

		return (int)$mtimeFromRequest;

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Send a plain base-10 unix timestamp in seconds, e.g. X-OC-MTime: 1706544000 — verify with echo time();
  2. If the value comes from a date string, convert it first: strtotime($dateString) or (new DateTime($dateString))->getTimestamp().
  3. Check for invisible characters (BOM, whitespace, quotes) in generated headers; the whole header value must match is_numeric().
  4. If you intentionally do not want to set mtime, omit the X-OC-MTime / X-OC-CTime header entirely — the server then uses the upload time.

Example fix

# before
curl -T file.txt -H "X-OC-MTime: 2024-01-29 12:00:00" https://cloud/remote.php/dav/files/alice/file.txt

# after
MTIME=$(stat -c %Y file.txt)
curl -T file.txt -H "X-OC-MTime: ${MTIME}" https://cloud/remote.php/dav/files/alice/file.txt
Defensive patterns

Strategy: validation

Validate before calling

// before uploading, validate the header value exactly like the server does
function isValidMtimeHeader(string $value): bool {
    if (preg_match('/^\s*0[xX]/', $value) === 1) {
        return false; // hex notation rejected
    }
    if (!is_numeric($value)) {
        return false;
    }
    return (int)$value > 86400; // also covers the 'greater than one day' rule
}

$mtime = (string)filemtime($localPath);
if (!isValidMtimeHeader($mtime)) {
    $headers = []; // omit the header, let the server use upload time
} else {
    $headers = ['X-OC-MTime' => $mtime];
}

Type guard

function isValidMtimeHeader(string $value): bool {
    return preg_match('/^\s*0[xX]/', $value) !== 1 && is_numeric($value) && (int)$value > 86400;
}

Try / catch

try {
    MtimeSanitizer::sanitizeMtime($mtimeFromRequest);
} catch (\InvalidArgumentException $e) {
    // 400-class client error: fix the header, never retry unchanged
    throw new \Sabre\DAV\Exception\BadRequest($e->getMessage());
}

Prevention

When it happens

Trigger: A PUT /remote.php/dav/files/... with X-OC-MTime: abc, X-OC-MTime: 0x513A4B7F, an empty header, or a locale-formatted timestamp like '1.706.544.000'; also bulk upload (DELETION-list multipart POST) with a non-numeric x-file-mtime / x-oc-mtime per-file header, and chunked upload MOVE with X-OC-MTime from a client that formatted the value incorrectly.

Common situations: Desktop/mobile sync clients or shell scripts (curl) that copy a display-formatted date into the header instead of a unix timestamp; code doing date('Y-m-d H:i:s') instead of time(); leading/trailing garbage or a BOM in the header; clients porting from other WebDAV servers that accept ISO dates.

Related errors


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