phacility/phabricator · error · Exception

Absolute TTL must be in the present or future, but TTL "%s"

Error message

Absolute TTL must be in the present or future, but TTL "%s" is in the past.

What it means

An absolute TTL was supplied as 'ttl.absolute', but the epoch timestamp is earlier than PhabricatorTime::getNow() (the server's current time). Phabricator refuses to create a file that would already be expired at creation time, because the TTL drives automatic deletion of expired files. The comparison is strict: a TTL exactly equal to now passes, anything earlier throws.

Source

Thrown at src/applications/files/storage/PhabricatorFile.php:1486

        'storageEngines' => 'optional list<PhabricatorFileStorageEngine>',
        'chunk' => 'optional bool',
      ));

    $file_name = idx($params, 'name');
    $this->setName($file_name);

    $author_phid = idx($params, 'authorPHID');
    $this->setAuthorPHID($author_phid);

    $absolute_ttl = idx($params, 'ttl.absolute');
    $relative_ttl = idx($params, 'ttl.relative');
    if ($absolute_ttl !== null && $relative_ttl !== null) {
      throw new Exception(
        pht(
          'Specify an absolute TTL or a relative TTL, but not both.'));
    } else if ($absolute_ttl !== null) {
      if ($absolute_ttl < PhabricatorTime::getNow()) {
        throw new Exception(
          pht(
            'Absolute TTL must be in the present or future, but TTL "%s" '.
            'is in the past.',
            $absolute_ttl));
      }

      $this->setTtl($absolute_ttl);
    } else if ($relative_ttl !== null) {
      if ($relative_ttl < 0) {
        throw new Exception(
          pht(
            'Relative TTL must be zero or more seconds, but "%s" is '.
            'negative.',
            $relative_ttl));
      }

      $max_relative = phutil_units('365 days in seconds');
      if ($relative_ttl > $max_relative) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Send a timestamp strictly in the future, e.g. PhabricatorTime::getNow() + $seconds, or just use 'ttl.relative' => $seconds which does that arithmetic for you.
  2. If you compute expires_at earlier in a long-running job, recompute it immediately before calling newFromParams().
  3. Check clock sync (NTP) between the machine building the timestamp and the Phabricator server; verify the value is epoch seconds, not milliseconds or a date string.

Example fix

// before
$params = array(
  'ttl.absolute' => $this->expiresAt, // computed minutes ago, may now be past
);

// after
$params = array(
  'ttl.relative' => $ttl_seconds, // server computes now + seconds
);
Defensive patterns

Strategy: validation

Validate before calling

if ($ttl !== null && $ttl < PhabricatorTime::getNow()) {
  $ttl = null; // or recompute: $ttl = PhabricatorTime::getNow() + $default_seconds;
}

Type guard

function isFutureTimestamp($ttl): bool {
  return is_int($ttl) && $ttl >= PhabricatorTime::getNow();
}

Try / catch

try {
  $file = PhabricatorFile::newFromParams($params);
} catch (Exception $ex) {
  if (preg_match('/is in the past/', $ex->getMessage())) {
    unset($params['ttl.absolute']);
    $file = PhabricatorFile::newFromParams($params);
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: PhabricatorFile::newFromParams() with 'ttl.absolute' set to a Unix timestamp in seconds that is less than the server's current time — e.g. time() - 60, a cached expires_at computed on a previous request, or a value derived from a row where ttl was already stored.

Common situations: Server clock skew: the web/daemon host generating the timestamp lags the Phabricator host (or PhabricatorTime is offset for tests), so a 'just now' timestamp reads as past; unit confusion (sending milliseconds instead of seconds gives a huge past-looking number only if truncated; sending seconds-from-epoch of 0 or a formatted date string instead of an epoch); retrying an old failed request with its original expiry.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/5063e5d302f3394c. Report an issue: GitHub.