-
Notifications
You must be signed in to change notification settings - Fork 15
SLA Management
Module: Tickets β Settings β SLA tab
Engine: includes/sla.php (compute-on-read)
Cron worker: cron/sla_breach_check.php
In-app help: tickets/help-sla.php (full guide with worked examples)
Business-hours-aware Service Level Agreement tracking for tickets β per-priority response and resolution targets, per-priority business calendars, pause-the-clock statuses, configurable behaviour for edge cases, and per-department breach notification rules fired by a scheduled cron worker.
For a scenario-driven walkthrough with four worked examples (UK single-office, tiered SaaS, 24/7 follow-the-sun, school-aligned charity), see the Four service desks deep dive on freeitsm.co.uk.
| Choice | Why |
|---|---|
| Business hours, not wall clock | The industry standard. A P2 raised at 16:30 with a 1-hour response target doesn't breach at 17:30 if the office closes at 17:00; the clock resumes at 09:00 the next working day. |
| Pause-able | Statuses flagged pauses_sla = 1 stop the clock (e.g. Awaiting Customer, Awaiting Vendor). Resumes when the ticket moves back to a working status. |
| Compute-on-read | The engine recomputes SLA state from the ticket audit log every time it's accessed. No stored counters that drift. Changing a business calendar later recomputes every historical ticket against the new hours next time it's viewed. |
| Admin-controlled enforcement cutoff | The sla_enforce_from setting means SLAs only apply to tickets created on or after a chosen datetime. Existing tickets aren't suddenly retroactively breached when SLAs are turned on. |
Each calendar defines when the service desk is open. Configurable in Settings β SLA β Business Calendars.
| Field | Purpose |
|---|---|
name |
Human-readable identifier (e.g. UK Office, 24/7 Global) |
timezone |
IANA identifier (Europe/London, America/New_York, UTC) β DST handled automatically via PHP DateTimeZone
|
is_default |
Exactly one calendar is the default; new priorities inherit it unless otherwise specified |
is_active |
Toggle without deleting |
Weekly hours are stored separately in sla_calendar_hours (one row per weekday, ISO 8601: 1=Mon, 7=Sun) with start_time and end_time. Absence of a row for a weekday = closed that day. Holidays in sla_calendar_holidays override the weekly pattern.
Multiple calendars are supported. Common patterns:
- Single 9-5 office β one calendar, Mon-Fri 09:00-17:00, local bank holidays
- Tiered VIP β one calendar for standard customers, a second with extended hours for VIPs
- 24/7 operation β one calendar with every weekday ticked and 00:00-23:59 hours, no holidays
- Term-time (e.g. school IT) β one calendar with all school holiday days entered
Each priority can have:
| Field | Purpose |
|---|---|
sla_response_minutes |
Time from ticket creation to first analyst action |
sla_resolution_minutes |
Time from ticket creation to ticket closure |
sla_calendar_id |
Which calendar this priority's clocks run against (NULL = use default) |
Leaving both target fields blank disables SLA tracking for that priority β useful for an Informational or Backlog priority. Per-priority calendars enable patterns like "VIP P1 uses 24/7 calendar; Standard P1 uses 9-5 calendar".
The Statuses tab in Settings has a dedicated Pause SLA column. Any status flagged here will pause the clock when a ticket is in that status. The SLA engine walks the ticket's status-change audit log (ticket_audit with field_name = 'status') and excludes minutes spent in paused statuses from the consumption calculation.
Default seeded paused statuses:
- Awaiting Response (customer owes us information)
- Awaiting Vendor (waiting on third-party support)
Common additions:
- On Hold (Change Window) β scheduled for a future maintenance window
- On Hold (Parts) β waiting on hardware delivery
- Maintenance Window β planned outage scope
Generic On Hold is often deliberately left non-pausing to force analysts to be specific about why a ticket is parked. See the Watchtower section below for the paused-too-long safety net.
Global toggles stored in system_settings. Defaults shown in bold.
What happens to the SLA clock when a ticket's priority is changed mid-flight (e.g. P3 β P1).
| Value | Effect |
|---|---|
forward |
Accrued business minutes are preserved and re-targeted. A ticket with 2h used against a P3's 8h target is now 2h used against the new P1's 1h target β already breached. |
recompute |
Clock reset and the new target applied retroactively from ticket creation. Clean recalculation. |
reset |
New clock starts fresh from the moment of priority change. Most forgiving but customers may feel cheated. |
What happens to the SLA clock when a closed ticket is reopened.
| Value | Effect |
|---|---|
reset |
Fresh response and resolution clocks from the reopen moment. Reopen is effectively a new ticket. |
continue |
Resume from the elapsed time at closure. Original targets continue to apply. |
Which event stops the response clock.
| Value | Effect |
|---|---|
either |
First analyst action of any kind: outbound email, status change, or assignment. |
status_change |
Only a status change away from the default counts. Stricter β analyst has to have actually worked the ticket. |
outbound_email |
Only an email reply to the requester counts. Strictest β customer must have heard from us. |
sla_warning_threshold_percent β how far through the target before the inbox pill flips amber and warning-trigger breach notifications fire. Default 80 (60-min target β amber at 48 min).
Configurable per-department email rules fired by the cron worker. Rules are configured in Settings β SLA β Breach Notifications.
Each rule has four dimensions, all independent:
| Value | Behaviour |
|---|---|
Default (department_id IS NULL) |
Rule applies to tickets in every department |
Specific department (department_id = N) |
Rule applies only to that department. A specific rule wins over the default for tickets in that department. |
| Value | When |
|---|---|
warning |
Target percent β₯ warning threshold AND < 100 |
breach |
Elapsed > target |
If the cron is down when a warning would have fired and the ticket then breaches, the warning will still fire (catch-up) before the breach in the same run β so analysts always get the early-warning email even after an outage.
| Value | Behaviour |
|---|---|
response |
Watch the response SLA only |
resolution |
Watch the resolution SLA only |
both |
Watch both (one rule covers both, but a separate target-specific rule wins over a both rule for that target) |
Any combination of:
| Type | Source |
|---|---|
| Ticket's assignee |
analysts.email via tickets.assigned_analyst_id
|
| Members of the ticket's department teams | All active analysts in any team linked to the ticket's department (department_teams β analyst_teams) |
| Specific analyst | Picked from the analyst list |
| Custom email addresses | Free-text, comma/semicolon/newline separated. Useful for shared inboxes, Slack/Teams email bridges |
Each (ticket_id, target_type, trigger_type) combination fires at most once. Tracked in sla_notifications_sent with a UNIQUE key. The cron worker checks this table before sending. Reopened tickets keep the dedup rows β clear them manually to re-fire:
DELETE FROM sla_notifications_sent WHERE ticket_id = 12345;cron/sla_breach_check.php walks every open SLA-tracked ticket on a schedule (every 5 minutes is the sweet spot), decides which warnings/breaches should fire, and sends the emails via the ticket's originating mailbox (Microsoft Graph or Gmail β same OAuth tokens as inbound mail polling).
| Mode | Command | Auth |
|---|---|---|
| CLI (recommended) | php cron/sla_breach_check.php |
None β filesystem perms gate it |
| HTTP | curl http://host/freeitsm-app/cron/sla_breach_check.php?token=<TOKEN> |
Shared-secret token via ?token=
|
Get the token:
SELECT setting_value FROM system_settings WHERE setting_key = 'sla_cron_token';Auto-generated as bin2hex(random_bytes(16)) on first install (32 hex chars, ~128 bits of entropy). Rotate by UPDATE-ing the row.
| Layer | Default | Behaviour |
|---|---|---|
| Shared-secret token | random per install | Constant-time comparison via hash_equals()
|
| Per-IP failed-auth lockout | 10 attempts / 1 hour | >10 wrong-token requests from one IP in the past hour β 1-hour 429 lockout |
| Min interval between successful runs | 30s (sla_cron_min_interval_seconds) |
Even a valid request returns 429 if it arrives sooner than this after the last successful run. Defeats accidental double-scheduling and runaway loops. |
Every invocation (accepted or rejected) is logged to sla_cron_runs with started_at, ended_at, duration_ms, invocation (cli/http), client_ip, outcome (ok / rate_limited / auth_failed / config_missing / error), per-run counts, and notes. Auto-pruned after sla_cron_log_retention_days (default 30). Even at 1-minute cadence the table stays small (~43k rows / 30 days).
View the last 20 runs in the Cron Activity panel at the bottom of the SLA settings tab.
Windows Task Scheduler β see docs/sla-cron-setup.md for the full walkthrough. In short: create a task with trigger "Daily, every 1 day, repeat every 5 minutes for 1 day", action "Start a program", program = path to php.exe, arguments = path to sla_breach_check.php.
Linux cron:
*/5 * * * * /usr/bin/php /var/www/freeitsm-app/cron/sla_breach_check.php >> /var/log/freeitsm-sla-cron.log 2>&1Remote schedulers (cron-job.org, EasyCron) β use the HTTP form with the token.
The Watchtower dashboard surfaces "tickets paused over N hours" as an amber attention row on the Tickets card. Catches the ITIL antipattern of analysts parking tickets in On Hold to escape the SLA clock when stuck or busy. Threshold configurable via system_settings.watchtower_paused_too_long_hours (default 24 β allows overnight pauses, flags anything sitting through a full working day):
UPDATE system_settings
SET setting_value = '8'
WHERE setting_key = 'watchtower_paused_too_long_hours';Anchor for "how long has it been paused" is the latest ticket_audit row with field_name = 'status'. Falls back to tickets.created_datetime if no status-change history exists.
All SLA settings live in system_settings (key/value).
| Key | Default | Purpose |
|---|---|---|
sla_enforce_from |
NULL | Datetime cutoff. NULL = SLA enforcement disabled entirely. Set to a past datetime to turn on, future to schedule. |
sla_priority_change_behaviour |
forward |
forward / recompute / reset
|
sla_reopen_behaviour |
reset |
reset / continue
|
sla_first_response_definition |
either |
either / status_change / outbound_email
|
sla_warning_threshold_percent |
80 |
Amber pill + warning-trigger notifications fire at this % of target |
sla_notify_assignee_at_warning |
1 |
Legacy flag (notification rules supersede this) |
sla_notify_lead_at_breach |
1 |
Legacy flag (notification rules supersede this) |
sla_cron_token |
random | Shared secret for HTTP cron invocation |
sla_cron_min_interval_seconds |
30 |
Min seconds between successful cron runs |
sla_cron_log_retention_days |
30 |
How long to keep sla_cron_runs rows |
watchtower_paused_too_long_hours |
24 |
Threshold for the Watchtower paused-too-long attention row |
| Table | Purpose |
|---|---|
sla_calendars |
Business calendars (name, timezone, is_default, is_active) |
sla_calendar_hours |
Weekly working pattern per calendar (weekday, start_time, end_time) |
sla_calendar_holidays |
Per-calendar holiday dates (override weekly pattern) |
ticket_priorities |
Adds sla_response_minutes, sla_resolution_minutes, sla_calendar_id columns |
ticket_statuses |
Adds pauses_sla boolean column |
sla_notification_rules |
Per-department breach notification rules (scope, trigger, target, recipients) |
sla_notifications_sent |
Dedup log (one row per ticket+target+trigger that fired) |
sla_cron_runs |
Per-invocation audit log (timing, outcome, counts, client IP) |
- Tickets β parent module
- Watchtower β paused-too-long safety net
- Four service desks deep dive β scenario-driven walkthrough on freeitsm.co.uk
- In-app help:
tickets/help-sla.php(full guide with three worked examples including cross-timezone "fair" options) - Setup guide:
docs/sla-cron-setup.md(Windows Task Scheduler + Linux cron + remote schedulers)
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)