# Broken Links After a Confluence Cloud Migration: How to Find and Fix Them

October 14, 2026 ·

<!-- -->

7 min read

[NGPilot](https://ngpilot.com)

Quick Answer

A Cloud migration moves your content. It does not fix the links **inside** it. Anything a user typed by hand — an old Jira URL in a page body, a Server link pasted into a comment in 2021, a macro pointing at your old domain — survives the migration verbatim and now points at a host that no longer exists. Find them with advanced search on the old domain, define one mapping rule per domain, preview every match before rewriting, and sweep page bodies, blog posts, comments, macro parameters and attachment names separately.

The migration is done. Pages moved, spaces are there, the site is live and people are using it. Then someone clicks a link in an onboarding page and lands on a 404 that points at `jira.mycompany.com` — a hostname that stopped answering the day you cut over.

It is a common post-migration complaint, and it has a specific cause worth understanding, because the fix is different depending on where the link is.

What you'll learn

1. Which links the migration fixes and which it silently leaves behind
2. Why comments and macro parameters are where the damage hides
3. The search queries that actually find the stragglers
4. How to build mapping rules that are safe to run in bulk
5. Why Jira links inside Confluence need their own mapping

## Why the migration leaves links behind[​](#why-the-migration-leaves-links-behind "Direct link to Why the migration leaves links behind")

The Cloud Migration Assistant understands structure. It knows a page in space `ENG` called "Onboarding" should become a page with a new ID at a new URL, and it rewrites the links that point at *that page*. That is the part it is good at, and for content that was linked properly, it works.

What it cannot do is interpret a URL that someone typed as text. Those are just strings to it:

| Where the link lives                                         | Migration awareness                        | Result                              |
| ------------------------------------------------------------ | ------------------------------------------ | ----------------------------------- |
| Link to a migrated page, made via the editor picker          | Likely — the target resolves               | Usually fixed automatically         |
| Old Jira issue link typed into a page body                   | Partial — needs the Jira migration mapping | Broken unless you apply the mapping |
| Old-domain URL pasted into a comment                         | None — comments are historical text        | Broken                              |
| Old URL inside a macro parameter (chart, filter, Smart Link) | None — it is a config value                | Broken                              |
| Old URL in an attachment filename                            | None                                       | Broken when opened                  |

The pattern is that the further a link is from Confluence's own page model, the less the migration knows about it. And the further from the model it is, the more likely a human put it there by hand — which is exactly the case the tooling cannot handle.

## Step 1: find what is actually broken[​](#step-1-find-what-is-actually-broken "Direct link to Step 1: find what is actually broken")

Start with the old domain. Confluence CQL's `~` operator — which [Atlassian's operator reference](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) calls CONTAINS — does what you need here:

```
text ~ "jira.mycompany.com"
```

One caveat worth knowing before you trust the results: `~` is a fuzzy token match, not a literal string match. It will also return pages that merely *mention* the old domain rather than link to it. Budget for a manual pass over the hits — which you need anyway, because the next step is a rewrite.

Run it for three things:

* **Your old Confluence domain** — self-links that were typed rather than picked
* **Your old Jira base URL** — worth its own sweep, because Confluence pages reference Jira issues constantly
* **Any third-party host** that also moved, if you had a self-hosted plugin domain

Then sweep the places CQL will not reach. This matters because [`text` is a master field covering title, content body and labels only](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) — **comments are not part of it**, so a comment link will not appear in the query above at all.

* **Comments.** A link someone pasted into a comment three years ago is still sitting there verbatim, and the CQL text search will not find it. Check these in the UI.
* **Blog posts.** Selectable explicitly with `AND type = blogpost`.
* **Macro parameters.** A chart macro pointed at your old Grafana, or a filter macro pointed at your old Jira, is a config value the migration has no reason to touch. CQL has a `macro` field, so `macro = jira` lists every page carrying a Jira issue macro — a good place to check for stale issue links.
* **Attachment filenames and page properties.**

If you want to see the scale before you commit to a fix, run the query once per space and compare counts. Confluence CQL has no grouping operator at all — [its documented keywords](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) are `AND`, `OR`, `NOT` and `ORDER BY`, with `ASC` and `DESC` as sort modifiers — so there is no way to ask for a per-space tally inside a single query.

## Step 2: define the mapping rules before you touch anything[​](#step-2-define-the-mapping-rules-before-you-touch-anything "Direct link to Step 2: define the mapping rules before you touch anything")

Do not start with find-and-replace. Start with a rule table, one row per domain:

| Old                                          | New                                                      |
| -------------------------------------------- | -------------------------------------------------------- |
| `jira.mycompany.com/browse/`                 | `your-domain.atlassian.net/browse/`                      |
| `jira.mycompany.com/secure/ViewProfile.jspa` | `your-domain.atlassian.net/jira/secure/ViewProfile.jspa` |
| `mycompany.atlassian.com/wiki/`              | `your-domain.atlassian.net/wiki/`                        |

Rules are safer than replacements for three reasons. They are explicit about which paths are in scope. They are auditable — you can print the rule set and check it. And when one rule is wrong, you fix one rule instead of hunting through every page it already damaged.

The mistake to avoid is a blanket old-domain-to-new-domain replacement applied to every URL. Some old paths are genuinely gone and should become something else entirely. Blanket replacement turns those into confidently-wrong new URLs, which is harder to detect later than the original 404.

## Step 3: preview, then fix[​](#step-3-preview-then-fix "Direct link to Step 3: preview, then fix")

Every candidate match should show you three things before you approve it: the page it lives on, the URL as it is now, and the URL it would become. Then you can accept, skip or fix each one.

This is worth insisting on even when the rule looks obviously safe, because of the Jira case below.

### The Jira links inside Confluence need their own mapping[​](#the-jira-links-inside-confluence-need-their-own-mapping "Direct link to The Jira links inside Confluence need their own mapping")

If your Confluence pages link to Jira issues and Jira migrated separately, a domain swap is not enough on its own. The Cloud Migration Assistant records an issue key to cloud-ID mapping for the migration; apply that mapping rather than guessing, so the rewrite follows the real transformation instead of a pattern match.

Getting this wrong produces links that look right and go nowhere: correct domain, wrong key.

## Step 4: verify with a re-scan[​](#step-4-verify-with-a-re-scan "Direct link to Step 4: verify with a re-scan")

Re-run the same searches after the fix. Two things you want to see:

* **Zero matches on the old domain.** Anything left is a rule you did not write.
* **No new breakage.** Spot-check a handful of pages you rewrote by following a few links out from them, especially into Jira. A page can be fully rewritten and still contain one link the rule did not match because of a trailing slash or a slightly different casing.

Keep the record of what changed — which page, which URL, which rule, when. When someone reports a broken link in month three, that record turns a search into a lookup.

## Doing this on a schedule[​](#doing-this-on-a-schedule "Direct link to Doing this on a schedule")

Migration is not the only way legacy URLs appear. An app is renamed, a domain is rebranded, a third-party service is retired. Any of those leaves the same residue.

Running the old-domain sweep a few weeks after cutover — and once more after the first quarter — catches the stragglers that the first pass missed. Comments surface slowly: people find old links months later and report them one at a time, because nothing tells them the link is dead until they click it.

## Sources[​](#sources "Direct link to Sources")

* [Advanced searching using CQL — Atlassian Developers](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) — the `~` CONTAINS operator, the `text` master field, and the absence of a grouping operator
* [Advanced searching using CQL — Atlassian Developers](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) — `~` CONTAINS, the `text` master field, keyword list
* [Cleanup guide — Atlassian Documentation](https://confluence.atlassian.com/clean/cleanup-guide-1018788576.html) — Atlassian's framing of cleanup as routine maintenance

## Related guides

* [How to Migrate Confluence Content](/blog/how-to-migrate-confluence-content.md)
* [How to Find Stale Pages in Confluence](/blog/find-stale-pages-confluence.md)
* [How to Find Unused Workflows, Schemes and Custom Fields in Jira](/blog/find-unused-workflows-schemes-jira.md)

Explore NGPILOT[Browse Solutions](/solutions.md)[All Apps](/apps.md)[Atlassian Marketplace →](https://marketplace.atlassian.com/vendors/1226848/?utm_source=ngpilot.com\&utm_medium=blog\&utm_campaign=hub-links)

**Tags:**

* [confluence](/blog/tags/confluence.md)
* [cloud-migration](/blog/tags/cloud-migration.md)
* [broken-links](/blog/tags/broken-links.md)
* [confluence-admin](/blog/tags/confluence-admin.md)
* [how-to](/blog/tags/how-to.md)
