Average Ticket Age: The GLPI Metric Nobody Measures

Average ticket age reveals the forgotten backlog that mean time to resolution cannot see. Learn where the number comes from in GLPI, why the mean misleads, how to separate age from staleness with SQL, and how to automate the weekly backlog review.

Mean time to resolution measures who already left the queue. Average ticket age measures who is still in it - and that is where the forgotten backlog hides. This guide shows where the number comes from in GLPI, why the average alone misleads, how to separate age from staleness, and how to turn it into a weekly backlog review in the environments we support.

What average age measures - and what it hides

Average ticket age is the mean number of days between opening and today, counting only tickets that are still open. Unlike MTTR, which only sees what was already resolved, it photographs the current state of the queue. The catch is that the mean, on its own, is a poor summary of a real queue: a typical backlog is bimodal - dozens of brand-new tickets a few hours old live alongside a tail of tickets stuck for weeks. The mass of new tickets pulls the average down and hides exactly the old ones you need to find. That is why the average never travels alone: it comes with a distribution and a median.

Where the number comes from in GLPI

Everything lives in the glpi_tickets table. Age uses the date column (opening date); the "open" filter uses status. The states that matter:

  • 1 - New (not yet triaged)
  • 2 - Processing (assigned)
  • 3 - Processing (planned)
  • 4 - Pending (waiting on a third party)
  • 5 - Solved and 6 - Closed (out of the backlog)

The most common mistake here is throwing everything that is not 5 or 6 into the same pot. Tickets in Pending (status 4) are legitimately stopped waiting on a supplier, the customer or a part - they inflate the average age without anyone having forgotten anything. Segmenting by status is the first honest filter:

-- Average age by status (segment: "Pending" inflates the average)
SELECT
  status,
  ROUND(AVG(DATEDIFF(NOW(), date)), 1) AS avg_age_days,
  COUNT(*)                             AS total
FROM glpi_tickets
WHERE status NOT IN (5, 6)   -- exclude Solved and Closed
  AND is_deleted = 0
GROUP BY status
ORDER BY avg_age_days DESC;
-- status: 1 New, 2 assigned, 3 planned, 4 Pending

Age is not the same as staleness

A ticket opened 40 days ago but with a task logged yesterday is very different from one opened 40 days ago that nobody has touched. Age (since opening) measures seniority; staleness (since the last human action) measures abandonment - and it is staleness that points to the forgotten ticket.

In support work, the mistake we have seen cost dearly is using date_mod as the "last action". The ticket's date_mod column is updated by anything: a notification sent, a follower added, the SLA cron recomputing a deadline, a business rule firing. In other words, it gives the illusion of movement on tickets nobody actually worked. The real last human action lives in the followup and the task - glpi_itilfollowups and glpi_tickettasks. When we swapped date_mod for the maximum date of those two tables, one customer's stale list jumped from 3 to 17 tickets: the other 14 looked alive only because an automation brushed against them every day.

-- Real staleness: days since the last human interaction
-- date_mod is unreliable (notifications, followers and cron bump it)
SELECT
  t.id,
  t.name,
  DATEDIFF(NOW(), t.date) AS age_days,
  DATEDIFF(NOW(), GREATEST(
      t.date,
      COALESCE(MAX(f.date), t.date),
      COALESCE(MAX(k.date), t.date)
  ))                       AS idle_days
FROM glpi_tickets t
LEFT JOIN glpi_itilfollowups f
       ON f.itemtype = 'Ticket' AND f.items_id = t.id
LEFT JOIN glpi_tickettasks k
       ON k.tickets_id = t.id
WHERE t.status NOT IN (4, 5, 6)   -- exclude Pending, Solved, Closed
  AND t.is_deleted = 0
GROUP BY t.id, t.name, t.date
HAVING idle_days >= 7
ORDER BY idle_days DESC;

Distribution, not just the average

Before reacting to an average that went up, look at the shape of the queue. The query below breaks the backlog into age brackets and reveals the long tail the mean flattens:

-- Backlog distribution by age bracket
SELECT
  CASE
    WHEN DATEDIFF(NOW(), date) <= 3  THEN '0-3 days'
    WHEN DATEDIFF(NOW(), date) <= 7  THEN '3-7 days'
    WHEN DATEDIFF(NOW(), date) <= 15 THEN '7-15 days'
    WHEN DATEDIFF(NOW(), date) <= 30 THEN '15-30 days'
    ELSE '30+ days'
  END        AS bracket,
  COUNT(*)   AS tickets
FROM glpi_tickets
WHERE status NOT IN (4, 5, 6)
  AND is_deleted = 0
GROUP BY bracket
ORDER BY MIN(DATEDIFF(NOW(), date));

As an honest reference range (calendar days, not business days): under 3 days is healthy, 3 to 7 days is acceptable, 7 to 15 days raises a flag, and above 15 days calls for a ticket-by-ticket review. These are not universal targets - the fair number depends on the contract and the type of operation.

From the number to action: a decision matrix

High age is not a problem; it is a symptom with several causes. What fixes it is reading the technical signal and classifying before acting:

Technical signalClassificationAction
status 1 (New) and age > 3 daysNot triagedAssign an owner/group now
high idle_days, no external followerForgottenReassign and chase
status 4 (Pending) with an overdue returnExpired blockChase the third party or reopen
high age and low idle_daysLegitimate (complex)Review SLA / scope
high age and a recurring categoryProblem candidateOpen a Problem / KB article

One silent case worth highlighting: tickets stuck in New because the assignment business rule did not match the category and never set a group. They age invisibly - they drop off the per-group dashboards and only surface when you look at the average age with no group filter.

Automating the weekly review

Running the query by hand does not scale. We save the staleness query in a .sql file and schedule it in the system cron, reading credentials from a .cnf file (chmod 600) so a password never sits on the command line. The result is a weekly CSV that becomes the agenda of the backlog review:

# /etc/cron.d/glpi-idade  ->  every Monday 07:30, as www-data
# Builds the list of stale tickets (7+ days) for the backlog review
30 7 * * 1 www-data /usr/bin/mysql --defaults-file=/etc/glpi/kpi-ro.cnf glpi \
  < /opt/glpi-kpi/estagnados.sql \
  > /var/log/glpi-kpi/estagnados-$(date +\%Y-\%m-\%d).csv 2>&1

Common field mistakes

  • Looking only at the mean: it flattens the tail. Always track the distribution and, when possible, the median.
  • Mixing Pending into the pot: status 4 is a legitimate wait; on its own it lifts the average and triggers a false alarm.
  • Using date_mod as the last action: automations update it. Use the followup and task dates.
  • Comparing entities with different calendars: DATEDIFF counts calendar days; entities with different working hours are not comparable one to one.
  • Forgetting is_deleted = 0: the GLPI trash bin does not leave the table and contaminates any average.

Next step

Combine average age with the rest of your Service Desk KPIs and put it all on a dashboard in Metabase or Grafana with entity and group filters. That way age stops being a number at month end and becomes an alert that fires while there is still time to act.

At NexTool, backlog age and staleness go into the monthly support report we deliver to clients, along with the list of tickets to recover. If you want that oversight on your GLPI, take a look at our support and sustaining service.


This content was produced with the aid of artificial intelligence and reviewed by the Nextool Solutions team.

Frequently Asked Questions

It is the mean number of days between the opening date (the date column) and today, counting only open tickets (status other than 5 Solved and 6 Closed). It photographs the current state of the queue, unlike mean time to resolution, which only measures tickets already closed.

Because the backlog is usually bimodal: many new tickets live alongside a tail of old ones. The mass of new ones pulls the average down and hides the few stuck for weeks. That is why it needs the distribution by bracket and, when possible, the median.

Age is the time since opening; staleness is the time since the last human action (a followup or a task). An old ticket worked on yesterday is not forgotten; an old one with no interaction is. Staleness is the signal that points to abandonment.

Because date_mod is updated by notifications, added followers, the SLA cron and business rules - automatic movements that are not human work. Use the maximum date in glpi_itilfollowups and glpi_tickettasks to know when the ticket was actually touched by someone.

As a reference range in calendar days: under 3 days is healthy, 3 to 7 acceptable, 7 to 15 raises a flag and above 15 calls for a ticket-by-ticket review. It is not a universal target - it depends on the contract, the working calendar and the type of operation.

Not directly. The Statistics module helps with one-off queries, but it does not keep the historical series nor separate age from staleness. For that you use SQL with a read-only user or a Metabase/Grafana dashboard fed by these queries.

Need help?