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
- Send a plain base-10 unix timestamp in seconds, e.g. X-OC-MTime: 1706544000 — verify with echo time();
- If the value comes from a date string, convert it first: strtotime($dateString) or (new DateTime($dateString))->getTimestamp().
- Check for invisible characters (BOM, whitespace, quotes) in generated headers; the whole header value must match is_numeric().
- 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
- Always derive X-OC-MTime from filemtime()/stat, not from formatted dates.
- Unit-test header generation against is_numeric() before release.
- Omit the header rather than sending 0 or placeholder values.
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
- X-OC-MTime header must be a valid positive unix timestamp gr
- Message exceeds allowed character limit of 1000
- Invalid actor "$actorType"
- Invalid input values
- Incompatible node types
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/388dcb4259966fa0.
Report an issue: GitHub.