Service Worker Lifetime Rules Explained

Exactly when Chrome starts and stops an MV3 extension service worker: the 30-second idle timer, what resets it, the 5-minute event limit, slow fetch responses, ports, WebSockets, native messaging and DevTools.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

“My service worker keeps dying” is the most common MV3 complaint, and it usually comes with folklore: workers live five minutes, workers die after thirty seconds no matter what, keepalive pings are required, keepalive pings get you rejected. The real rules are specific and have changed across Chrome versions, mostly in the extension’s favour. Knowing them precisely tells you when you need to persist state, when a long task is safe, and when you are keeping the worker alive by accident. This guide sets them out. It belongs to service worker fundamentals.

The rules in current Chrome

An extension service worker starts when an event it has a listener for fires — a message, an alarm, a tab update, a web request — or when an extension page or content script connects to it. Once running, it is terminated after thirty seconds without activity. Activity resets that idle timer: receiving and dispatching an extension event, calling an extension API (since Chrome 110), receiving a message on an open port, and — since Chrome 116 — sending or receiving WebSocket messages. Separately, a single event may not run for more than five minutes: if the work started by one event is still pending after that, the worker can be terminated. And a fetch whose response takes more than thirty seconds to arrive can be cut off. Some connections keep the worker alive while they are open: native messaging ports (Chrome 105+), and an attached DevTools session — which is why bugs disappear while you debug.

One worker's lifeAn alarm starts the worker; events and API calls keep resetting the 30-second idle timer; after the last activity the timer runs out and the worker is terminated; a later message starts a fresh instance.alarm firesnext messageCold st…Events + API callstimer keeps resettingIdle30 sTerminatedmemory goneNew ins…last activityidle timer expires
The worker lives as long as something keeps happening — plus thirty seconds.
What keeps the worker aliveExtension events, extension API calls, port messages, open native messaging ports, WebSocket traffic, pending fetches, setTimeout and an open DevTools window, with their effect on the worker's lifetime in current Chrome.ActivityEffectSinceExtension event dispatchedResets idle timerMV3Extension API callResets idle timerChrome 110Port messageResets idle timerMV3Native messaging port openKeeps aliveChrome 105WebSocket messageResets idle timerChrome 116setTimeout / setIntervalNo effect—DevTools attachedKeeps aliveDebugging only
Timers alone never keep the worker alive; activity and certain open connections do.

Step-by-step: working with the rules

1. Assume termination between any two events

1// sw.js — state lives in storage, not in variables
2chrome.runtime.onMessage.addListener((msg, _s, reply) => {
3  if (msg?.type !== "counter:inc") return;
4  chrome.storage.session.get("count").then(({ count = 0 }) =>
5    chrome.storage.session.set({ count: count + 1 }).then(() => reply(count + 1)));
6  return true;
7});

Execution context: the service worker. Any idle gap of thirty seconds — common between user actions — ends the instance and its module-level variables. Keeping state in chrome.storage.session (in-memory, survives worker restarts, cleared at browser exit) or local (persistent) makes termination invisible. See rebuilding in-memory state after termination.

2. Register listeners synchronously

1// The event that starts the worker is dispatched after the first synchronous run of the script
2chrome.alarms.onAlarm.addListener(onAlarm);          // at top level
3// NOT: await init(); chrome.alarms.onAlarm.addListener(onAlarm);

Execution context: the service worker’s top level. Chrome starts the worker, runs the script, then dispatches the event to listeners registered by then. A listener added after an await misses the event that woke the worker. See registering listeners at the top level.

Why a late listener misses its wake-up eventAn alarm fires while the worker is stopped; Chrome starts it and runs the script, which awaits initialisation before registering onAlarm; Chrome dispatches the alarm after the synchronous pass, finds no listener, and the alarm is lost.ChromeWorker scriptstart (alarm pending)await init() …dispatch onAlarm → no listeneraddListener(onAlarm) — too …
Register first, initialise second.

3. Keep each event’s work under five minutes

1chrome.alarms.onAlarm.addListener(async ({ name }) => {
2  if (name !== "reindex") return;
3  const done = await processBatch(500);              // bounded work per event
4  if (!done) chrome.alarms.create("reindex", { when: Date.now() + 30_000 });   // continue later
5});

Execution context: the service worker. Long jobs should be split into bounded batches, each its own event, with progress persisted between them. API calls made during the batch keep resetting the idle timer, but the five-minute ceiling on a single event’s work still applies. See persisting job progress across worker restarts.

4. Do not rely on timers

1// setTimeout does not keep the worker alive; it may never fire
2setTimeout(refresh, 60_000);                          // unreliable
3chrome.alarms.create("refresh", { delayInMinutes: 1 });   // reliable

Execution context: the service worker. Short timers are fine while something else keeps the worker busy — a debounce during a burst of events, for example. For anything that must happen later, use chrome.alarms, which persist outside the worker and wake it.

5. Use open connections deliberately, not as a hack

Ports from extension pages, native messaging ports and active WebSockets legitimately keep the worker alive while they are in use: a side panel streaming a response, a native host conversation, a real-time view. Opening a port from an offscreen document purely to prevent termination, or pinging an API every twenty seconds forever, keeps the worker resident for no user benefit — and costs memory and battery. Tie connections to visible features and close them when the feature is idle. See keeping service workers alive during long tasks.

6. Test with DevTools closed

11. chrome://serviceworker-internals → find the extension's worker → "Stop"
22. Close every DevTools window attached to the worker
33. Trigger the feature (message, alarm, navigation)
44. Confirm it works from a cold start

Execution context: manual or automated testing. An attached DevTools session keeps the worker alive, masking every lifetime bug. Test critical flows from a cold start and after idle periods, and automate it in end-to-end tests as described in driving service worker state from a test.

7. Watch for slow fetches

A fetch to a slow endpoint whose response headers take more than thirty seconds can be cut off when the worker is otherwise idle. Stream responses so headers arrive quickly, set explicit timeouts, and move genuinely slow work to a job-and-poll design. See streaming responses with fetch in the service worker.

Common mistakes

  • State in module variables. Lost after thirty idle seconds.
  • Listeners after await. The waking event is lost.
  • Timers for scheduling. They die with the worker.
  • Permanent keepalive hacks. Resource cost and review risk.
  • Testing with DevTools open. Hides every lifetime bug.

Cross-browser variation

  • Chrome / Edge: the rules above; specific behaviours arrived in Chrome 105 (native ports), 110 (API calls reset the timer) and 116 (WebSockets).
  • Firefox: the MV3 background is usually an event page, suspended after a period of inactivity with an onSuspend warning; the same stateless design works.
  • Safari: background contexts are suspended aggressively, sometimes faster than Chrome; persisting state is even more important.

Verification

  1. Open chrome://serviceworker-internals, trigger the extension, then wait without activity: the worker should stop after about thirty seconds.
  2. Stop the worker manually and trigger each event type the extension listens for; each should work from cold start.
  3. Run a long job and confirm it progresses in bounded batches without being terminated mid-batch.
  4. Open a side panel with a port and confirm the worker stays running until the panel closes.

FAQ

Is there a maximum total lifetime?

Not in current Chrome, as long as activity continues and no single event exceeds its limit. Older versions had a hard five-minute cap.

Do content script messages keep the worker alive?

Each message is an event and resets the idle timer.

Does the worker restart when the browser restarts?

Only when an event needs it; onStartup fires if you listen for it.

Do the rules differ between unpacked and store-installed extensions?

The lifetime rules are the same. What differs in development is that DevTools is often attached, which keeps the worker alive — always re-test with DevTools closed.

Other MV3 Architecture & Extension Lifecycle Resources