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
- Send an absolute unix timestamp in seconds greater than 86400, e.g. X-OC-MTime: 1706544000.
- If your value is in milliseconds, divide by 1000: intdiv($mtimeMs, 1000).
- Omit the header if you have no meaningful mtime — the server assigns the current time.
- 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
- Convert millisecond timestamps with intdiv($ms, 1000).
- Never send relative offsets or durations in X-OC-MTime.
- Assert the value is > 86400 in client upload pipelines.
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
- X-OC-MTime header must be a valid integer (unix timestamp),
- 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/febabac14be4b81b.
Report an issue: GitHub.