---
title: Upgrading
description: How to upgrade the Meraki Dashboard Exporter, the breaking changes to expect at 1.0, and where breaking metric and configuration changes are announced.
tags:
  - upgrade
  - migration
  - versioning
  - metrics
---

# Upgrading

Upgrading the exporter safely means checking a few things before and after each release; this page
covers those steps. It also documents the deliberate, one-time breaking changes made immediately
before **1.0**, so dashboards and alerts built against pre-1.0 metric names can be migrated cleanly.

If you only read one thing: the 1.0 release includes a **single, deliberate breaking sweep** of
metric names and labels. After 1.0, Stable metrics only change through the dual-publish
deprecation window in the [Metric Stability & Deprecation Policy](../stability.md).

## How to upgrade

The exporter ships as a Docker image and a Helm chart, both published per release (see the
[Release Process](../development/release-process.md)). Upgrading is a matter of moving to the new
image tag.

1. **Read the [Changelog](../changelog.md) first.** Every release's breaking changes, renames, and
   deprecations are listed there. Do this before bumping the tag - it is where breaking metric
   and configuration changes are announced (see [Where breaking changes are
   announced](#where-breaking-changes-are-announced)).
2. **Pin to a specific version tag**, not `:main` (which is a rolling edge build). Use the
   `vX.Y.Z` release tag for reproducible upgrades.
3. **Roll out to a non-production instance first** if you run one, scrape it, and confirm your
   dashboards and alert rules still resolve against the new metric surface.
4. **Apply the new tag to production.** The exporter is stateless (all state is derived from the
   Meraki API on each collection cycle), so a rolling restart is safe - there is no migration
   step or persistent store to convert.

!!! tip "Verify image signatures"
    Release images and charts are signed. See [Security](../security.md) for how to verify
    signatures before deploying.

## Changes at 1.1

### Configuration compatibility changes

Review these settings before deploying 1.1; several configurations accepted by 1.0.2 are now
refused or behave differently:

- `meraki.api_base_url` must use HTTPS. Local HTTP mock endpoints and plaintext proxies are no
  longer accepted.
- A base URL outside the built-in Meraki regional URL set requires
  `meraki.allow_custom_api_base_url=true`. Corporate proxies and egress gateways must opt in and
  must still expose an HTTPS URL.
- `collectors.collector_timeout` must be at least `api.per_fetch_deadline_seconds`; values from
  30 through 119 seconds therefore fail with the shipped 120-second fetch deadline.
- Disabling every collector is refused. This also means a webhook-receiver-only deployment must
  leave at least one collector enabled.
- An active `network_filter` that resolves successfully to no networks is refused.
- Enabling the webhook receiver with `webhooks.require_secret=false` is refused unless
  `webhooks.allow_insecure=true` explicitly acknowledges the unauthenticated configuration. The
  recommended action is to configure `webhooks.shared_secret` and keep secret validation enabled.
- `api.rate_limit_burst` now defaults to 10 instead of 20.
- The effective global collector concurrency may be lower than
  `collectors.max_concurrent_collectors` because it is bounded by SDK executor capacity. With the
  shipped `api.executor_workers=10` and `api.concurrency_limit=5`, the effective cap is 2.
- The two control POST endpoints require `server.api_token`, as detailed below.

In adaptive scheduler mode, an unset collection profile continues to collect the full endpoint-group
surface while the solved `full` plan fits the effective API-budget target. Above that
shape-aware threshold, startup refuses to proceed until
`MERAKI_EXPORTER_COLLECTORS__PROFILE` is set explicitly to `availability` (priority 1), `standard`
(priorities 1–3), or `full` (all priorities). The error reports the estimated demand and budget
target. The selected profile is then solved honestly: if it still exceeds budget, priorities 1 and 2
keep their volatility floors while lower-priority groups are shed, and
`meraki_exporter_scheduler_over_budget` remains alertable. Fixed scheduler mode never applies this
adaptive profile-selection gate.

The API client also strips its `Authorization` header on every cross-origin redirect, including
when a custom API base URL was explicitly allowed. Proxies must complete authenticated redirects
without forwarding the Meraki credential to a different origin.

### Network filtering and collected series

`NetworkFilter` is now applied before switch port and power data is emitted. If a switch's network
is excluded, its `meraki_ms_port_*` and `meraki_ms_power_*` series disappear after upgrading; this
is the intended correction. Update alerts or dashboards that previously depended on those leaked
series, or include the network in the filter. Network inventory, device collection, and endpoint
group cost calculations now use the same filtered network set.

### Self-observability metric semantics

- `meraki_exporter_api_requests_total{endpoint,method,status_code}` counts outbound SDK request
  **attempts**, including retries. `method` is derived from the SDK operation. `status_code` contains
  HTTP status values only; calls that fail without an HTTP response are represented by
  `meraki_exporter_api_request_attempts_total{operation,status="exception"}` instead of placing an
  exception name in `status_code`. A facade-owned 429 retry increments
  `meraki_exporter_api_retry_total` once per retry.
- Endpoint-group last-success and scheduler-run state advance only after the owning endpoint group
  succeeds. A group with no applicable work is neither a success nor an endpoint failure, while an
  attempted group with no explicit success remains fail-closed. This can make old false-success
  timestamps stop advancing.
- The shared task-admission metrics `meraki_exporter_tasks_active`,
  `meraki_exporter_tasks_pending`, `meraki_exporter_task_queue_wait_seconds`, and
  `meraki_exporter_task_expired_before_start_total` use bounded `phase` values:
  `collector_admission` for whole collector runs and `task_group` for bounded child work. They
  describe exporter scheduling pressure, not Meraki endpoint failures.
- Reverse-DNS timeouts increment `meraki_exporter_client_dns_lookups_timeout_total`; other resolver
  failures increment `meraki_exporter_client_dns_lookups_failed_total`, never both for one lookup.
  `meraki_exporter_client_dns_queue_depth` is the peak producer backlog from the most recently
  started completed batch, with overlapping older batches prevented from overwriting newer data.

`meraki_exporter_cardinality_product_series` now counts only `meraki_*` product-data samples.
Exporter instrumentation and Python/process runtime samples move to the new
`meraki_exporter_cardinality_exporter_series` bucket; the cardinality monitor's own samples remain
in `meraki_exporter_cardinality_self_series`. The three buckets still reconcile exactly to
`meraki_exporter_cardinality_exposed_series` and the compatibility alias
`meraki_exporter_total_series`.

Webhook metrics now separate authenticated deliveries from unique accepted alerts:
`meraki_webhook_delivery_attempts_total` includes Meraki retries,
`meraki_webhook_unique_alerts_total` counts first acceptance, and
`meraki_webhook_replays_rejected_total` counts TTL-cache duplicates. Deliveries outside the
configured freshness window are acknowledged without applying device state and increment
`meraki_webhook_stale_rejected_total`. The bounded replay cache is per process, so deduplication is
not shared between replicas or retained across restarts.

## Breaking changes at 1.0

The 1.0 release carries a **one-time, pre-1.0 breaking sweep**. This churn is explicitly
permitted by, and described in, the [Metric Stability & Deprecation
Policy](../stability.md#one-time-pre-10-sweep) - it happens exactly once so that the disruptive
renames land before the compatibility promise takes effect, rather than trickling out afterwards.

### Metric name and unit renames

Metric names were corrected so that suffixes and units are consistent and base-SI:

- `_total` suffixes now denote **only** true monotonic counters. Windowed and snapshot gauges
  that previously carried `_total` were renamed to `_count` or bare-plural forms, with any
  measurement window documented in the metric's `# HELP`.
- Non-base units were converted to base-SI (`bytes`, `seconds`, `joules`). The only retained
  non-base units are the documented exceptions that carry their unit in the name: device memory
  in KiB (binary `×1024`), link speed as `_mbps`, and the radio-native `_mhz` / `_dbm` units.
- `_percent` and other unit suffixes were standardised.

The authoritative, per-metric list of current names, labels, and units is the generated
[Complete Metrics Reference](../metrics/metrics.md). Diff your dashboard and alert queries against
it after upgrading.

### Mutable name labels moved to `_info` join metrics

Mutable, human-readable **name** labels - `org_name`, `network_name`, device `name`,
`port_name`, `description`, `hostname`, and similar - were **dropped from numeric metric
series** and moved onto id-keyed `*_info` join metrics. Per-client series were reduced to
ID-only.

This was done because a rename (of an org, network, or port) would otherwise change the label
set of every affected series, starting a *new* Prometheus time series and orphaning the old one -
breaking `rate()` / `increase()` continuity. Keying numeric series by the stable **ID** and
carrying the mutable name on a separate `_info` metric means a rename touches exactly one info
series instead of the whole fleet.

**What you must change:** any query or dashboard panel that selected or displayed a name label
directly off a numeric series. Join to the info metric on the ID to pull the name back in:

```promql
meraki_device_up
  * on (serial) group_left (name)
  meraki_device_status_info
```

The same pattern applies to organizations (`on (org_id) group_left (org_name) meraki_org_info`)
and to clients (join to `meraki_client_info`). The info carriers are `meraki_org_info`,
`meraki_device_status_info`, `meraki_network_info`, `meraki_ms_port_info`, `meraki_mv_zone_info`,
and `meraki_client_info`. Full detail is in the stability policy's [Name labels are not part of
numeric series](../stability.md#name-labels-are-not-part-of-numeric-series) section.

### Single-org deployment contract (BREAKING)

**From 1.0, each exporter instance polls exactly one Meraki organization (1 poller = 1 org).**
This is a deliberate breaking change: it makes each instance's rate-limit budget, inventory cache,
and metric cardinality well-defined and scoped to a single organization, so per-instance capacity
can be reasoned about.

How the organization is resolved at startup:

- **`MERAKI_EXPORTER_MERAKI__ORG_ID` set** - the exporter polls that organization. This is the
  recommended production configuration.
- **`org_id` unset and the API key sees exactly one organization** - that organization is
  **auto-selected** and the exporter starts normally, so a single-org key needs no `org_id`.
- **`org_id` unset and the API key sees several organizations** - the exporter **fails fast at
  startup** (it aborts before serving) with an error listing the visible organizations and telling
  you to set `MERAKI_EXPORTER_MERAKI__ORG_ID` to one of them.

**What changed:** previously an unset `org_id` caused the exporter to discover and poll *every*
organization the API key could see. That multi-org polling behaviour is gone. If you relied on a
single instance covering multiple organizations, split it into **one instance per organization**
(shard by org). See the [Scaling Guide](../scaling-guide.md) for shard-by-org / HA recipes and the
Helm chart's multi-instance example (`charts/meraki-dashboard-exporter`) for running one release
per organization, and the [Configuration](../config.md) reference for the `org_id` key.

### Control API now requires an API token (BREAKING)

The manual **Run Now** and **Clear DNS Cache** controls, and their POST endpoints
(`/api/collectors/trigger` and `/api/clients/clear-dns-cache`), now fail closed when
`server.api_token` is unset. Tokenless requests return HTTP 401 and the UI controls are disabled
with a configuration tooltip. Configure `MERAKI_EXPORTER_SERVER__API_TOKEN` and have API clients
or the authenticating reverse proxy supply `Authorization: Bearer <token>`. There is no
unauthenticated compatibility flag. `/metrics`, `/health`, and `/ready` are unchanged.

### Startup configuration validation

The exporter now adds six deterministic configuration refusals before serving: a non-HTTPS API base
URL, a custom API base URL without the explicit opt-in, an active Network Filter that successfully
resolves to zero networks, no effective enabled collectors, a
`MERAKI_EXPORTER_COLLECTORS__COLLECTOR_TIMEOUT` lower than
`MERAKI_EXPORTER_API__PER_FETCH_DEADLINE_SECONDS`, or an invalid profile value. Each error names the
setting to correct.
Temporary Meraki API failures during startup verification do not abort the process; collection
loops continue and retry when the API recovers.

## Where breaking changes are announced

Breaking metric and configuration changes are announced in the [Changelog](../changelog.md), which
is generated from Conventional Commits by release-please (see the [Release
Process](../development/release-process.md)).

The conventions the project follows so you can spot them:

- **Breaking changes** are flagged with a `!` in the commit type (e.g. `feat(metrics)!: ...`) or
  a `BREAKING CHANGE:` footer. These surface as a dedicated breaking-changes section in the
  changelog for that release and drive the semantic-version bump.
- **Metric deprecations and removals** are called out in the changelog and follow the
  dual-publish deprecation window described in the [Metric Stability & Deprecation
  Policy](../stability.md#post-10-rename-deprecation-process): a Stable metric is emitted under both
  the old and new name for at least one full minor release before the old name is removed, and the
  old metric's `# HELP` is prefixed with a `DEPRECATED:` note naming the replacement.

Always read the changelog entry for the version you are moving to before upgrading production.
