Firefighter Shift Schedule Template and Maker
Fire service scheduling is the odd one out, and the reason is arithmetic. A 24-hour tour ends the next morning, so a firefighter can never work two calendar days in a row, and the rest rule decides how soon they come back: with 24 hours' rest the earliest next tour starts 48 hours after the last one began (24 on, 24 off); with 48 hours' rest it is 72 hours (24 on, 48 off — the classic 24/48, three platoons). Every other decision follows from that single number: it is why departments run three or four platoons, why a station needing four on duty on a 24/48 needs at least twelve people before you have even thought about rank, and why the roster is far more rigid than the headcount suggests.
The sample loaded here is a 21-day station on 24-hour tours: four on duty at all times, one
of whom must be a company officer and one a paramedic, drawn from twenty-five people. Its
MinRestHours is 48, so it is a strict 24/48: 24 hours on, then at least 48 off. Set it
to 24 if your department lets people come back every other day. The
shift is written 08:00 to 08:00, and that is not a typo or a special
setting — an end time that is not later than the start means the shift wraps, so
08:00 to 08:00 is a full 1,440 minutes. Rest, consecutive days and total hours all then work on
that block with no special-casing at all.
Read the continuity numbers differently on this page. Everywhere else on this site, a roster made entirely of single-day blocks is a warning sign — it means someone is being called in on scattered days. Here it is simply what 24-hour tours look like, because a tour that ends the next morning makes consecutive days impossible by construction. The results page detects that and stops flagging it. What you should watch instead is the tightest-turnaround figure and the per-person tour counts, which is where an unfair roster actually shows up in the fire service.
A 48/96 rotation needs a rule this tool does not have yet: rest after a run of tours
rather than after every tour. MinRestHours applies between any two tours, including
the two halves of a 48 (which have no rest between them at all), so it would have to be 0 —
and then nothing forces the 96 hours off. Kelly schedules
and other named rotations are a different kind of tool: if you want a published repeating
pattern rather than an optimized assignment, the rotation
generator lays those out directly.
shifts,
demand all, supply all, skills, one grid per demand and
per person, parameters. The template above has all of them filled in.Fire service scheduling questions
How do I enter a 24-hour shift? Set the start and end to the same time —
08:00 to 08:00. An end time that is not later than the start means the
shift crosses midnight, so identical times are read as a full 24 hours rather than as zero.
Why can't anyone work two days in a row? Because a tour that ends at 08:00 cannot be
followed by one starting at 08:00 the same morning with any rest rule above zero. The loaded
sample's MinRestHours is 48, so after a tour the earliest next one starts two mornings
later — 24 on, 48 off, the classic three-platoon 24/48. Set it to 24 to allow a tour every
other day.
Can it hold a minimum company staffing level? Yes — that is what the demand grid is. Four on an engine is a demand of four; if your department staffs differently by station or by day of week, every cell in that grid is independently editable.
How do I require an officer and a medic on every tour? Make each a separate demand
with the concurrent flag set and give only the qualified people a skill row for it.
Concurrent matters here: your company officer is one of the four on duty, not a fifth body, and
the flag is what says so.
⚠ Why did every firefighter come out capped at the same number of hours? Check
max_hrs_roster (called max_hrs_week in workbooks downloaded before
2026-08-17 — same field, still accepted). It applies across the whole roster,
so on a 21-day horizon you need roughly three times a weekly figure. Entering a fire-service
72-hour week caps each person at 72 hours for the entire three weeks, which looks exactly like a
short-staffed station and is not one. The giveaway is that every person lands on the
identical hours total — that is a contract cap, not a coverage failure.
Does it know about FLSA 7(k) overtime? No. It schedules hours; it does not compute pay periods, the 7(k) work-period exemption, or overtime liability. Use the exported hours as the input to whatever does.
The same solver, other settings
Each of these loads a different worked example and is written for that setting, but they all run the same cross-day engine — if none of them matches yours, start from whichever is closest and replace the data.
- correctional officer rosters — post-based staffing and the relief factor behind mandatory overtime
- police patrol schedules — watches, minimum beats, and every-other-weekend-off
- security guard posts — a small team covering one post, with weekend fairness switched on
- the general 24/7 shift schedule maker — any round-the-clock operation; start here if none of the others fit
How this differs from the other two modes
The staff scheduling optimizer on the home page works in half-hour slots inside a single day: you say how many people you need at 08:00, at 08:30, at 09:00, and it invents shift blocks to match. That is the right model when demand rises and falls through the day — a call centre, a restaurant, a shop — and the wrong model here, because every one of its rules is scoped to one day column. A 19:00–07:00 night becomes two disconnected half-shifts, and no rest rule can span the midnight boundary.
This page replaces the time axis with a shift axis. Your shifts are named blocks with real clock times, and an end time earlier than the start time simply means the shift crosses midnight. Rest between two shifts then becomes arithmetic on absolute minutes across the whole horizon rather than a special case, which is what makes cross-day rules — minimum rest, maximum consecutive days, maximum consecutive nights, no day shift after a night — expressible at all.
The third mode, the shift rotation generator, does not optimize anything: it lays out a named pattern — DuPont, Panama, Pitman, 4-on-4-off — across four crews. Use it when you want a known, published rotation and you do not need the schedule to respond to who is qualified for what, who is on holiday, or which shifts need three people instead of two.
What the solver actually decides
Skills, availability, pinned (f) assignments, maximum hours and days, minimum rest and the
consecutive-day and consecutive-night caps are hard — the model physically cannot
assign someone to work they are not qualified for, not available for, or not rested for. Filling every
post, reaching each person's minimum hours, weekend fairness and cross-person equity are
soft: each carries a penalty rather than a prohibition. That is a deliberate choice. A
roster tool that answers “infeasible” tells you nothing useful, so a post the rules cannot
fill comes back as an unfilled post, named in the warnings and the unfilled-posts table, not as a
failure. The one way to get “infeasible” is hard rules that contradict each other —
almost always an f cell that breaks a rest, consecutive-day or maximum-hours rule.
Why the continuity numbers are on the results page
A roster can be 100% covered and still be unliveable. Everyone in a pay grade earns the same rate and coverage pins the total hours, so rearranging who works when often costs the model literally nothing — the same ward solved twice with staff listed in a different order produced 21 different shifts at an identical objective. Left alone, the solver returns whichever scattered roster it reached first. So the model carries a small penalty on each new block of consecutive days worked, and another on flipping between days and nights, and the results page reports what came out: how many blocks, how long they are, how many are lone single days, and the tightest turnaround anyone actually got. Those are the numbers to argue with, not the objective value.
Common questions
Is it free? Yes — no account, no trial. Your workbook is read to solve the model and not kept afterwards. We do cache some data on the server to speed up responses and keep an anonymous count of runs.
How many people and days can it handle? Up to 60 people, 42 days (six weeks), 12 shifts and 12 demand streams per solve. The 28-day hospital ward sample builds roughly 4,300 variables and solves in well under a minute.
What if I want a shift to be someone specific? Put f in that person's grid cell.
Forced cells are hard — the solver schedules around them.
Is minimum rest really 11 hours? Only by default. 11 hours is the EU Working Time Directive
figure; the US has no federal equivalent and state rules vary, so it is a parameter
(MinRestHours) rather than a constant. Set it to whatever your jurisdiction or contract
says.