nextcloud/server · warning · InvalidArgumentException

X-OC-MTime header must be a valid positive unix timestamp gr

Error message

X-OC-MTime header must be a valid positive unix timestamp greater than one day, got "%s".

What it means

Second guard in MtimeSanitizer::sanitizeMtime() (apps/dav/lib/Connector/Sabre/MtimeSanitizer.php:29): the header was numeric, but its integer value is <= 24*60*60 (86400, one day worth of seconds). Nextcloud rejects these because timestamps that small are never legitimate mtimes — they are almost certainly relative offsets (e.g. seconds elapsed since upload start) or garbage, and accepting them would make files appear to be from 1970-01-01/02 and break sync and versioning logic.

Source

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

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 an absolute unix timestamp in seconds greater than 86400, e.g. X-OC-MTime: 1706544000.
  2. If your value is in milliseconds, divide by 1000: intdiv($mtimeMs, 1000).
  3. Omit the header if you have no meaningful mtime — the server assigns the current time.
  4. In client code, derive the value from the local file: stat(file).st_mtime / filemtime().

Example fix

// before
$client->request('PUT', $url, ['headers' => ['X-OC-MTime' => (string)$elapsedSecondsSinceStart]]);

// after
$client->request('PUT', $url, ['headers' => ['X-OC-MTime' => (string)filemtime($localPath)]]);
Defensive patterns

Strategy: validation

Validate before calling

$mtime = filemtime($localPath);
if ($mtime === false || $mtime <= 86400) {
    $headers = []; // no meaningful mtime -> let server stamp upload time
} else {
    $headers = ['X-OC-MTime' => (string)$mtime];
}

Type guard

function isPositiveUnixTimestamp(string $value): bool {
    return is_numeric($value) && (int)$value > 24 * 60 * 60;
}

Try / catch

try {
    $mtime = MtimeSanitizer::sanitizeMtime($headerValue);
} catch (\InvalidArgumentException $e) {
    // header rejected as not-a-valid-timestamp; regenerate from filemtime() or drop it
    $mtime = null;
}

Prevention

When it happens

Trigger: PUT / chunked-upload MOVE / bulk upload with X-OC-MTime: 3600, X-OC-MTime: 0, X-OC-MTime: 86400, or a negative value; also millisecond overflow mistakes are not caught here but second-vs-millisecond confusion can produce small values after (int) truncation in client code, e.g. sending (time() % 100000).

Common situations: Client code that computes mtime as a duration or offset instead of an absolute epoch; hardcoded 0 or 1 as a placeholder; unit tests sending arbitrary small integers; scripts where a variable was never initialized and casts to 0.

Related errors


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