Meta refresh redirects: what Google does with them, and what to use instead

Short answer

A meta refresh is a tag in the page's HTML, <meta http-equiv="refresh" content="5; url=/new-page">, that tells the browser to go to another address or reload the page after a number of seconds. Google follows it, treating an instant one (0 seconds) as a permanent redirect and a delayed one as temporary. But a server-side 301 redirect is clearer for search engines and better for visitors. Use a meta refresh only when you can't set up redirects on the server.

What a meta refresh is

The content holds a delay in seconds and, optionally, an address:

content="0; url=/new" Redirects straight away. Often used on hosting that doesn't allow server redirects.
content="5; url=/new" Shows the page for five seconds, then redirects. The classic "this page has moved, you'll be taken there shortly".
content="300" No address: reloads the same page every five minutes. Common on old news and live-score pages.

A server can send the same instruction as an HTTP header, Refresh: 5; url=/new. It behaves the same way.

How Google treats it

Google lists the ways to redirect in order of how reliably it understands them:

MethodGoogle reads it asUse it
301 or 308 from the serverPermanentWhenever a page has moved for good
302, 303 or 307 from the serverTemporaryWhen the move really is temporary
Meta refresh or Refresh header, 0 secondsPermanentOnly if you can't redirect on the server
Meta refresh or Refresh header, delayedTemporaryOnly if you can't redirect on the server
JavaScript redirectSeen only if the page rendersOnly if neither of the above is possible

The difference matters. A permanent redirect tells Google to replace the old URL with the new one in its results (a strong canonical signal). A delayed meta refresh is read as temporary, so Google may keep showing the old address. A server redirect also answers before any page is sent; a meta refresh tag is only found once the old page has been downloaded and read.

Why it's bad for people

  • A delayed redirect moves people without asking. Someone reading, or using a screen reader, is taken somewhere else mid-sentence.
  • A timed reload loses their place. The page reloads, so they can lose their place and anything typed into a form.
  • A delayed redirect breaks the back button. Going back lands on the page that sends them forward again.

The W3C lists a timed meta refresh redirect (F40) and a meta refresh that reloads the page (F41) as failures of WCAG's timing, interruption and change-on-request rules. An instant (0-second) redirect isn't a failure.

How to check by hand

1Run the snippet 30 seconds

An instant meta refresh moves you on before you can look, so this reads the page's HTML without following it. Open DevTools (F12), go to the Console and paste:

// Finds meta refresh tags (and a Refresh header) and says what each one does. Running it never follows them.
// For a page that redirects instantly, put its address here and run this from another page on the same origin.
const url = location.href;
const parse = content => {
  // Same rules as browsers: digits first, then an optional "url=", with or without quotes.
  const m = content.match(/^\s*(\d*)(\.[\d.]*)?\s*(?:[;,]\s*|\s+|$)(?:url\s*=\s*)?(['"]?)(.*)$/i);
  if (!m || (!m[1] && !m[2])) return null;
  let target = m[4].trim();
  if (m[3] && target.includes(m[3])) target = target.slice(0, target.indexOf(m[3]));
  return { delay: Number(m[1] || 0), target };
};
const describe = (where, content) => {
  const p = parse(content);
  if (!p) return { where, content, does: 'nothing: browsers ignore this value', google: '' };
  if (!p.target) return { where, content, does: 'reloads the page every ' + p.delay + 's', google: '' };
  let href;
  try { href = new URL(p.target, url).href; } catch { return { where, content, does: 'nothing: the address is invalid', google: '' }; }
  return {
    where, content,
    does: (p.delay === 0 ? 'redirects instantly to ' : 'redirects after ' + p.delay + 's to ') + href,
    google: p.delay === 0 ? 'treats it as a permanent redirect' : 'treats it as a temporary redirect',
  };
};
const tags = doc => [...doc.querySelectorAll('meta[http-equiv="refresh" i]')]
  .map(m => ({ content: m.getAttribute('content') || '', noscript: Boolean(m.closest('noscript')) }));
let res;
try {
  res = await fetch(url, { cache: 'no-store' });
} catch {
  throw new Error('Could not fetch ' + url + '. Run this from a page on the same origin (same protocol, domain and port).');
}
if (res.redirected) console.log('Note: ' + url + ' redirected on the server to ' + res.url + '. Results are for that page.');
const serverTags = tags(new DOMParser().parseFromString(await res.text(), 'text/html'));
const rows = serverTags.map(t => describe(t.noscript ? 'server HTML, inside <noscript> (only without JavaScript)' : 'server HTML', t.content));
if (url === location.href) {
  const fromServer = serverTags.filter(t => !t.noscript).map(t => t.content);
  tags(document).filter(t => !fromServer.includes(t.content)).forEach(t => rows.push(describe('added by JavaScript', t.content)));
}
if (res.headers.get('refresh')) rows.push(describe('Refresh header', res.headers.get('refresh')));
console.table(rows.length ? rows : [{ result: 'No meta refresh or Refresh header' }]);

It lists each meta refresh in the server HTML, any added by JavaScript, and a Refresh header if the server sends one, with what each does and how Google treats it. It never follows them. For a page that redirects instantly, open any other page on the same origin, change the address near the top to that page's, and run it there.

It can only fetch pages on the same origin (the same protocol, domain and port) as the page you run it on. For another site, or another subdomain, use method 2.

2View the source without following it 30 seconds

Type view-source: in front of the address in Chrome's address bar, for example view-source:https://example.com/old-page. The source opens without the redirect firing. Search it for refresh.

3Watch the Network tab 1 minute

Open DevTools, tick Preserve log on the Network tab, and load the page. A server redirect shows as a 301 or 302 on the first request. A meta refresh shows as a 200, followed a moment later by a second page loading. A Refresh header also shows as a 200: click the request and look for Refresh under Response Headers.

What to use instead

  1. Page moved: a 301 redirect on the server, or in your CDN or hosting settings. Most platforms have a redirects page, even ones that don't let you edit server config.
  2. Page that needs fresh content: update the content with JavaScript, without reloading, and give visitors a way to pause it.
  3. "This page has moved" notices: redirect straight away on the server. If you want to explain the move, do it on the new page.
  4. No server access at all: an instant meta refresh (0 seconds) is the next best option, since Google treats it as permanent. Update your internal links to the new address too, so visitors don't hit it at all (see how to find broken links).

Choosing a server redirect instead? See 301 vs 302 redirects.

How crawlers report this

If a crawl sent you here, these are the labels to look for. Our guide to Screaming Frog and Sitebulb issues covers other common ones.

Screaming FrogSitebulbWhat it means
Response Codes: Internal Redirection (Meta Refresh)Redirects using a Meta refreshThe page redirects through an HTML tag, not a server redirect.

Doing this on every page?

SEOSnip finds meta refreshes for you

Open the extension on any page and it reads the meta refresh tags in that page. An instant redirect moves you on before you can open the extension, so use the snippet above for those; the check reads the tag, not a Refresh header. You get:

  • A flag for a meta refresh that redirects, or one that reloads the page, with the delay and the address
  • The tag itself, with its content highlighted
  • A ticket with that evidence, and a "Check if fixed" button that re-runs the check