---
url: /guide/upgrading.md
description: Upgrade Citizen without breaking the pages your wiki still has cached
---

# Upgrading

Citizen serves CSS and JS fresh on every page view, but wikis cache HTML —
in the parser cache, CDN or Varnish layers, and `$wgUseFileCache`. After an
upgrade, cached pages generated by the old version meet the new styles, which
can break their appearance until every cache has expired or been purged.

## Start with the release cache status

Releases state a cache status in their [notes](../changelogs/index.md), near the
top and above the highlights — a one-line note, or a warning callout when a
purge is needed. Check it first: it tells you whether the rest of this page
applies at all.

| Status | What it means for you |
| --- | --- |
| `clean` | No cached-HTML impact. Upgrade as usual — nothing else here applies. |
| `covered by compat` | Follow the steps below so cached pages stay readable. |
| `purge required` | Some pages cannot be bridged by styles alone, so plan a [cache clear](#clearing-the-old-cache). The steps below still cover everything else. |

Older releases predate this convention. If the notes state no status, treat the
release as `covered by compat` — or as `purge required` if they mention purging.
Moving up several releases at once? Go with the strictest status of the bunch.

## Plan ahead

If you know an upgrade is coming, lower `$wgParserCacheExpireTime` a few days
early. It applies to entries already in the cache, not just new ones, so pages
start re-parsing straight away and normal traffic works through the long tail
before you deploy instead of after. Write the old value down first, and put it
back once the upgrade has settled.

## Upgrade steps

### 1. Turn the compat flag on

Set [`$wgCitizenCompat`](../config/index.md#wgcitizencompat) **before** you
upgrade. It is safe to set on a version that predates it — nothing reads the
setting until the new code is in place. While it is on, Citizen also serves the
styles recent releases retired, so HTML cached by the old version keeps
rendering acceptably — readable and usable, though not always pixel-perfect.

```php [LocalSettings.php]
$wgCitizenCompat = true;
```

::: tip How far back it reaches
The flag covers HTML generated by versions up to **6 minor releases** back,
which is roughly six months of releases at the current pace. The window is
measured on the HTML, not on the size of your upgrade jump: what matters is the
oldest version still sitting in your caches, and that is decided by your longest
TTL. A 90-day CDN TTL can hold HTML from outside the window even on a
one-release upgrade, and anything older than the window needs a
[cache clear](#clearing-the-old-cache).
:::

### 2. Upgrade Citizen

Deploy the new version the way you normally would.

### 3. Purge, if the release asked for it

If the status is `purge required`, [clear the cache](#clearing-the-old-cache)
now — after the upgrade, not before. Purging first achieves nothing, because
those pages re-cache under the old version within minutes.

### 4. Let the old cache go

Stale pages last as long as the longest of these, counted from the deploy:

* `$wgParserCacheExpireTime` — the parser cache. One day by default.
* `$wgCdnMaxAge` — how long a CDN or Varnish may serve a page. Five hours by
  default, though your CDN's own configuration can override it.
* `$wgUseFileCache` has no expiry at all: entries stay until the page is edited
  or `$wgCacheEpoch` moves past them.

Leave the flag on for that long, or [clear the cache](#clearing-the-old-cache)
if you would rather not wait.

::: tip Saving `LocalSettings.php` already moves the epoch
On a default wiki, `$wgInvalidateCacheOnLocalSettingsChange` pushes
`$wgCacheEpoch` to the file's modification time. Save or `touch`
`LocalSettings.php` after the upgrade and MediaWiki's own caches turn over from
that moment, leaving only your CDN to wait for. Wikis that set that setting to
`false`, or keep their config in a file `LocalSettings.php` merely includes, do
not get this and need to
[set the epoch by hand](#move-wgcacheepoch-forward).
:::

### 5. Turn the flag off

Cached HTML carries the generation that rendered it, so you can check rather
than guess. Purge any page with `?action=purge`, view source, and note the
`data-mw-citizen-html` attribute on `<html>`. Then open a page you know has not
been edited since before the upgrade: if its attribute matches, that page is
current.

Once the stale pages are gone, set `$wgCitizenCompat = false;` again. Leaving it
on costs a few kilobytes of extra CSS and nothing else — the compat styles are
written not to match current HTML — so wikis that deploy continuously can simply
leave it on.

## Clearing the old cache

Just one or two pages looking wrong? Purge those individually with
`?action=purge` and skip the rest of this section.

::: warning These clear MediaWiki's own caches
Nothing here touches a CDN or Varnish in front of your wiki. That needs its own
purge, or its TTL to lapse — which is usually what decides when you can turn the
flag off.
:::

If your release is `purge required`, skip past the first option below: for those
pages, waiting means leaving them visibly broken until they expire.

### Just wait

Usually the right answer. Cached pages expire on their own, and the flag keeps
them readable until they do, so there is nothing to run and no re-parse spike to
absorb.

### Move `$wgCacheEpoch` forward

Anything cached before the epoch is treated as stale, so pages re-parse as
people visit them rather than all at once. This is also what clears the file
cache, which has no expiry of its own, and it works with any parser cache
backend rather than only `CACHE_DB`. Most wikis get it for free from saving
`LocalSettings.php` (see step 4) — set it by hand if yours does not.

```php [LocalSettings.php]
// The moment you upgraded, as a UTC MediaWiki timestamp
$wgCacheEpoch = '20260729120000';
```

::: warning
This invalidates every page on the wiki, not just the ones Citizen's styles
affect, so your whole hot set re-parses on first view. On a large or
template-heavy wiki, pick a quiet window. The timestamp is UTC — a local one
silently leaves hours of stale cache behind.
:::

### Purge the parser cache

Deletes entries outright, oldest first. `--age` is in seconds counted back from
now, so pace a large purge by re-running it with a smaller age each day:

```sh
# Older than a week, then older than three days, then older than a day
php maintenance/run.php purgeParserCache --age 604800
php maintenance/run.php purgeParserCache --age 259200
php maintenance/run.php purgeParserCache --age 86400
```

Add `--dry-run` to see what a run would delete before you commit to it — the
deletion cannot be undone — and `--msleep 500` to go easy on the database. If
you lowered `$wgParserCacheExpireTime` per [plan ahead](#plan-ahead), use
`--expiredate` instead: `--age` assumes that setting has not changed.

::: warning
[`purgeParserCache.php`](https://www.mediawiki.org/wiki/Manual:PurgeParserCache.php)
only works when the parser cache is in the database — `CACHE_DB`, which is the
default. If yours is in Memcached or Redis it stops with an error, so use
`$wgCacheEpoch` instead.
:::
