Response header · non-standard
< Refresh: 5; url=/nextRefresh Header
Learn how the Refresh header instructs browsers to reload or redirect after a delay. Understand its use cases, limitations, and better alternatives.
- Direction
- Response
- Category
- Redirection
- Status
- Non-standard: Not defined in the HTTP RFCs
On this page
TL;DR:
Refresh: 5; url=/nexttells the browser to go to/nextfive seconds after the page loads, andRefresh: 5reloads the current page. It works like<meta http-equiv="refresh">, not like a real HTTP redirect. If a URL has moved, send a301/308(or another3xx) withLocationinstead.
What is Refresh?
The Refresh response header uses the same declarative refresh machinery as <meta http-equiv="refresh">. The HTML Standard defines how it behaves. RFC 9110 doesn’t list it among HTTP’s redirect mechanisms, because it isn’t one.
How Refresh Works
Here’s a response that sends it (example headers, HTML body left out):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Refresh: 5; url=/next
The countdown is tied to the document loading, not to the moment the headers arrive. MDN says the delay starts after the page has fully loaded. Even Refresh: 0 goes through that document refresh processing, so it isn’t the same as an HTTP redirect that happens before anything renders.
Syntax
Refresh: 30
Refresh: 5; url=/next
Refresh: 0; url=https://example.com/new-page
The first line reloads the page, the second goes to a relative URL, and the third to an absolute one. Give the delay in whole seconds; browsers ignore any fractional part. How long to wait is up to you.
Common Examples
The HTML-only version goes in the document head:
<meta http-equiv="refresh" content="5; url=/next" />
It’s the same timed navigation, just declared in the markup instead of a header. Everything on this page about timing and accessibility applies to both.
Real-World Scenarios
A status page that should keep reloading has to send the directive on every response. Each Refresh only schedules one load. If the next response leaves the header out, the reloading stops.
So a status page can show a generated timestamp and ask for another load. Keep each GET read-only, so reloading only fetches the status and never starts or repeats the job being monitored. If a POST submits the job, keep it on a separate URL from the status page.
Say /status returns Refresh: 30. Each new load can repeat the header while the job runs and drop it once the job is done, which ends the cycle. Base that on the job’s real state: thirty seconds passing doesn’t mean the job finished.
Server Implementation
This nginx route goes inside a server block:
location = /waiting {
default_type text/html;
add_header Refresh '5; url=/result';
return 200 '<!doctype html><html lang="en"><head><title>Waiting</title></head><body><p>Opening the result page in five seconds.</p><a href="/result">Open result now</a></body></html>';
}
add_header puts the field on this 200 response. /result is your application’s job, and nothing here checks whether a background task is finished.
In an existing Express app, this route serves a timestamped status page:
app.get('/status', (req, res) => {
res.set('Refresh', '30')
res.set('Cache-Control', 'no-store')
res.type('html').send(`<!doctype html>
<html lang="en"><head><title>Status</title></head><body>
<p>Generated at ${new Date().toISOString()}</p>
<a href="/status">Refresh now</a></body></html>`)
})
Cache-Control: no-store is what keeps this response out of caches; Refresh has no effect on caching. The example always schedules another load. In a real app, check the job’s state and leave the header off once it’s complete.
A Django view sets the field on the response it returns:
from django.http import HttpResponse
def waiting(request):
response = HttpResponse(
'<p>Opening the result page in five seconds.</p>'
'<a href="/result">Open result now</a>',
content_type='text/html; charset=utf-8',
)
response['Refresh'] = '5; url=/result'
response['Cache-Control'] = 'no-store'
return response
See Django’s docs on setting header fields. You’ll need a /result route of your own. Like the nginx version, this view just schedules a navigation; it doesn’t wait for any background result.
In PHP, send headers before any output:
<?php
header('Refresh: 30');
header('Cache-Control: no-store');
header('Content-Type: text/html; charset=utf-8');
echo '<!doctype html><html lang="en"><head><title>Status</title></head>';
echo '<body><p>Status checked.</p><a href="/status">Refresh now</a></body></html>';
PHP’s header() documentation requires headers to go out before any output. Even stray whitespace before the opening <?php tag can stop these calls from working, unless output buffering happens to mask it.
Getting Refresh right
For a URL that has moved, send the appropriate 301, 302, 303, 307 or 308 with Location. For a long-running operation, a timer is the wrong signal. Show the real status and let people move on once the result exists.
Refresh vs Other Redirect Methods
A redirect with Location happens at the HTTP level. Refresh and meta refresh happen while the document loads. JavaScript navigation is a third mechanism, with a timer your script controls.
A 200 with Refresh is cacheable under the normal HTTP caching rules, like any other 200. The header neither blocks caching nor makes the response fresh, so set Cache-Control for what the response really needs.
| Mechanism | Where navigation is controlled |
|---|---|
303 plus Location | HTTP redirect handling; retrieves another resource |
Refresh: 5; url=/next | Document loading and declarative refresh processing |
| Meta refresh | The document’s declarative refresh processing |
setTimeout(() => location.assign('/next'), 5000) | A JavaScript timer owned by the page |
Only the last row gives your code a timer handle it can pass to clearTimeout(). Once document processing has scheduled a refresh, removing the meta element or clearing some other timer won’t reliably cancel it.
Common Use Cases
Before adding auto-reload to a monitoring page, ask whether you need a full navigation at all. A reload replaces the whole document to update what might be one number. If people need control over the timing, give them a manual refresh.
A manual reload needs nothing more than an ordinary link:
<p>The export is being prepared.</p>
<a href="/status">Check export status</a>
Now the reader decides when the page gets replaced. For a dashboard that has to keep filters, focus or unsaved text, fetch the new values and update just those DOM elements, so the rest of the document stays put.
Accessibility Considerations
Five seconds isn’t necessarily enough time to read a page, and W3C lists timed meta redirects as a WCAG failure. A “go now” link lets people leave early, but it doesn’t let them stay longer. And a JavaScript clearTimeout() call has no power over a refresh the header scheduled.
If you need a delayed navigation that people can cancel, run the timer in JavaScript and drop both the header and the meta tag:
<p id="notice">Opening the result in five seconds.</p>
<button id="stay" type="button">Stay on this page</button>
<a href="/result">Open result now</a>
<script>
const timer = setTimeout(() => location.assign('/result'), 5000)
document.querySelector('#stay').addEventListener('click', () => {
clearTimeout(timer)
document.querySelector('#notice').textContent = 'Automatic navigation stopped.'
})
</script>
clearTimeout() cancels this script’s own timer. The button makes this one behavior controllable; check the rest of the page against accessibility requirements separately.
Testing Refresh Header
curl -sS -D - -o /dev/null https://example.com/waiting
curl shows you the header but never acts on it. curl -L follows HTTP redirects, and this isn’t one. To see the actual behavior, load the route in a browser with Network → Preserve log turned on.
Fetching /waiting from JavaScript just retrieves the response; the current page doesn’t navigate. So finding Refresh in response.headers doesn’t tell you the page will redirect when loaded as a document.
In the Network log, you’re looking for the first 200, then a second document request once the page has loaded and the delay has passed. Check both the response headers and the document’s <head>, since middleware might add the header while a template separately includes a meta refresh. A redirect chain can also land on a page that schedules a refresh, so keep the log from the very first URL instead of looking only at the last request.
Related Headers
Frequently asked questions
What is the Refresh header?
Refresh tells browsers to reload the page or redirect after a delay. Equivalent to meta http-equiv refresh. Example: Refresh: 5; url=https://example.com
Should I use Refresh for redirects?
For a moved URL, use an HTTP redirect status with Location. Refresh is defined through HTML document loading and schedules a navigation after a delay.
What are the problems with Refresh?
Timed navigation can remove content before a user finishes reading. W3C documents timed meta redirects as an accessibility failure. Refresh is HTML loading behavior rather than an HTTP redirect.
When might Refresh be acceptable?
A status document can request a reload, but users may need control over the timing. Use a manual refresh or application-controlled update when a full timed navigation is unnecessary.
Sources
Related
Date Header
Learn how the Date header specifies when the HTTP message was originated by the server. Understand date formats and its role in caching and logging.
Location
Learn how the Location header specifies redirect URLs or the location of newly created resources. Essential for 201, 301, 302, and other redirect responses.
Content-Disposition Header
Learn how the Content-Disposition header controls whether content displays inline or downloads as an attachment. Set custom filenames for file downloads.
Content-Language Header
Learn how the Content-Language header specifies the natural language(s) of response content. Understand language tags and internationalization best practices.