Domain redirect (301 vs 302)

301 means permanent and 302 means temporary, but the behaviour that differs is caching. What the RFC requires, what production redirectors send, and why DNS cannot do either.

A domain redirect is an HTTP response that tells the client "the thing you asked for lives at this other URL". The server answers with a 3xx status code and a Location header, and the client requests the new URL instead. 301 means the move is permanent; 302 means it is temporary. Both are HTTP responses, which means something has to be running an HTTP server to send them. DNS cannot do this on its own.

$ curl -sI http://github.com
HTTP/1.1 301 Moved Permanently
Location: https://github.com/

The choice between 301 and 302 is usually presented as a question about search engines. It is really a question about caching, and once you see it that way the rest of the confusion clears up.

The actual difference is cacheability, not permanence

"Permanent" and "temporary" are labels. The behaviour they map to is written in RFC 9110, and it is specific: 301 is heuristically cacheable and 302 is not.

Section 15.4.2 says a 301 response "is heuristically cacheable; i.e., unless otherwise indicated by the method definition or explicit cache controls". The definition of 302 in section 15.4.3 contains no such sentence, and 302 is absent from the spec's list of status codes that are cacheable by default. Only 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501 are on that list.

So a bare 301 with no cache headers gets stored by the browser and reused without asking the server again, for however long that browser's heuristic decides. A bare 302 does not. That is the whole mechanism behind the folklore that "301s are dangerous because you can never take them back".

CodeSemanticsCacheable by defaultMethod preserved on POST
301 Moved PermanentlyPermanent moveYesNo, may become GET
302 FoundTemporary moveNoNo, may become GET
303 See OtherFetch this other thing insteadNoNo, becomes GET by design
307 Temporary RedirectTemporary moveNoYes
308 Permanent RedirectPermanent moveYesYes

Cache-Control overrides all of it, and the big redirectors know

The heuristic only applies "unless otherwise indicated by explicit cache controls". Send a Cache-Control header on the redirect itself and you decide the lifetime, regardless of the status code. Almost nobody writing about 301 vs 302 mentions this, and every large redirector in production relies on it.

Headers read live on 2026-08-21:

RedirectorStatusCache-Control on the redirectEffective lifetime
google.com → www301public, max-age=259200030 days
reddit.com → www301private, max-age=36001 hour
bit.ly link301private, max-age=9090 seconds
tinyurl.com link301no-store, no-cache, must-revalidatenever cached
buff.ly link302public, max-age=0, must-revalidatenever cached
github.com, stripe.com, shopify.com, nytimes.com301(none)browser heuristic, effectively forever

Look at the bit.ly row. A URL shortener, and the whole vanity URL category with it, has to send 301, because that is what passes ranking signals and what analytics tooling expects from a permanent link. But it also has to be able to retarget or kill a link at any moment. It resolves the conflict by sending a permanent status code with a 90-second cache lifetime. TinyURL goes further and forbids caching entirely on a 301.

The practical rule that falls out of this: if you are worried about being stuck with a redirect, do not downgrade it to a 302. Send a 301 with a short max-age. You keep the semantics you want and you keep the ability to change your mind. If you have already shipped a bare 301 that clients have cached, changing it later means serving the old URL again with a short max-age and waiting out the caches you cannot see.

301 and 302 both break POST requests

Buried in both definitions is the same note, in the RFC's own words: "For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request."

That is not a bug in browsers. It is twenty-five years of practice the spec gave up on and documented. curl does it too. The manual for curl 8.7.1 states that when following a redirect, "if the request is a POST, it sends the following request with a GET if the HTTP response was 301, 302, or 303", and preserves the method for any other 3xx.

This is why redirecting an API endpoint with a 301 is a trap. A client POSTs JSON to the old path, gets a 301, and re-issues the request as a GET with no body. The server sees a malformed GET, not a POST. Nothing logs an error that names the cause.

Use 307 when you want a temporary move that keeps the method, and 308 when you want a permanent one that keeps the method. Both were defined precisely because 301 and 302 could not be fixed without breaking the web. For plain page moves on a website, where everything is a GET anyway, 301 remains the right answer.

No, you cannot do a 301 in DNS

A redirect is an HTTP status code. DNS answers questions about names and returns records: A, CNAME, TXT, MX. There is no record type that produces a 3xx response, because DNS never sees the URL path, the scheme, or the request at all. By the time an HTTP redirect happens, DNS resolution has already finished.

What people mean when they search for a "301 redirect DNS record" is one of two things:

  • A CNAME, ALIAS or flattened apex record, which makes one hostname resolve to another host's address. The URL in the address bar does not change, no status code is involved, and the target host has to be willing to serve your hostname. That is domain aliasing, not redirection.
  • Domain forwarding at a registrar, which is a genuine HTTP redirect. The registrar points your record at a small fleet of web servers it operates, and those servers issue the 301 or 302. It looks like a DNS feature because you configure it in the DNS panel, but the redirect is served by the registrar's HTTP infrastructure.

The second one has a failure mode worth knowing: because forwarding is an HTTP service tied to the registrar's own nameservers, moving your DNS elsewhere silently turns it off. The records still resolve, the forwarding does not. If what you actually want is the apex to serve your app rather than bounce to www, apex domain support is the other half of this problem. See the domain forwarding explainer for what each provider actually implements, and domain masking for the related trick where the URL is meant to stay the same.

What production sites actually send

Measured against twelve major domains on 2026-08-21, starting from plain http://:

DomainFirst hopHopsEnds at
github.com3011https://github.com/
stripe.com3011https://stripe.com/
shopify.com3011https://www.shopify.com/
cloudflare.com3011https://www.cloudflare.com/
apple.com3011https://www.apple.com/
google.com3011http://www.google.com/, still plain HTTP
wikipedia.org3012https://www.wikipedia.org/
reddit.com3012https://www.reddit.com/
nytimes.com3012https://www.nytimes.com/
microsoft.com3072https://www.microsoft.com/

Nine of ten use 301 for the http-to-https and apex-to-www hops. Microsoft is the outlier at 307, which is defensible, since the scheme upgrade is arguably not a permanent property of the resource, but it is not what anyone else does. Google's row is the strange one, and the next section explains it.

Two hops is the normal shape of a healthy site: scheme upgrade, then canonical host, the second hop being the apex-versus-www decision every domain has to make once. If your redirect checker shows four or five, you are almost certainly stacking a platform-level rule on top of an application-level one.

Why your browser shows a chain your checker does not

Run curl -I http://www.google.com/ today and you get HTTP/1.1 200 OK over plain HTTP. No redirect. Google serves that page over an unencrypted connection and sends no Strict-Transport-Security header on it at all.

Your browser will never show you that. Type it in Chrome and you land on HTTPS, with DevTools reporting a "307 Internal Redirect", a status code that no server sent and that never travelled over the network. That entry is Chrome telling you it rewrote the scheme before making a request.

Two mechanisms cause it. The first is HSTS: a site sends Strict-Transport-Security once, and the browser refuses plain HTTP for that host until the max-age expires. The second is the preload list, a file compiled into the browser itself. Chromium's transport_security_state_static.json carries 94,628 entries as of 2026-08-21, and 57 of them are not sites at all but entire top-level domains marked force-https at the public-suffix level:

.app · .bank · .day · .dev · .foo · .gle · .google · .insurance · .meme · .mov · .new · .page · .phd · .prof · .rsvp · .search · .zip, among others.

If your domain ends in .dev or .app, no browser will ever issue a plain-HTTP request to it. Your carefully configured http-to-https redirect is unreachable code in a browser and perfectly visible to curl, to your monitoring, and to a redirect checker. That difference is not a bug in the tool; the tool is showing you what the server does, and the browser is showing you what it decided not to ask.

Incidentally, google.com itself is not on the preload list. Only specific subdomains like mail.google.com and accounts.google.com are, which is exactly why plain HTTP still answers on www.

Redirect chains, loops and the limits that stop them

Every hop is a full round trip: DNS if the host changed, a TCP connection, a TLS handshake, then the response. On mobile, three unnecessary hops is a visible delay, and none of it is cached at the DNS TTL level, which only saves you the lookup, not the round trip.

Clients cap the chain rather than following it forever:

ClientLimit
Chromium (kMaxRedirects)20
curl 8.7.1 with -L50
Domainee's redirect checker12, with loop detection

Chromium's constant is annotated in net/url_request/url_request.h with the WHATWG Fetch rule it implements: "if request's redirect count is twenty, return a network error". When a client hits its cap you get ERR_TOO_MANY_REDIRECTS, which is almost always a genuine loop rather than a long chain. The classic cause is an origin that redirects http to https while a proxy in front of it terminates TLS and forwards the request as http. Chrome surfaces it as ERR_TOO_MANY_REDIRECTS; the HTTP header checker will show you the offending Location on a single hop.

Two more things a chain can hide. A hop that returns 405 to a HEAD request but 301 to a GET will look like a dead end to any checker that probes with HEAD, and netflix.com behaves exactly this way today. And meta refresh tags and JavaScript location assignments are not redirects at the HTTP layer at all; they happen after a 200 response, so no HTTP-level tool will ever see them.

For platforms serving customer domains

If you run a product with multi-tenant custom domains, redirects are a per-tenant configuration surface, not a one-time setting. Every customer arrives with their own opinion about apex versus www, some already have a redirect in place at their registrar, and each one needs the destination to hold a valid certificate before the redirect is worth issuing at all, because a redirect to a host with no cert produces a browser warning instead of a page.

The ordering that works: resolve DNS, provision the certificate, verify the destination answers 200, and only then start redirecting to it. The custom domain debugging runbook covers the failure modes in the order they actually occur.

Domainee is a custom domains API for SaaS with a native MCP server — 50 domains and 100 GB free. It handles the certificate and routing side of custom domains so redirects land somewhere that already works, and the domain forwarding API covers the case where the domain should redirect rather than serve.

FAQ

What is the difference between a 301 and a 302 redirect? 301 is a permanent move and 302 is a temporary one, but the behavioural difference is caching: per RFC 9110, a 301 is heuristically cacheable by default and a 302 is not. A bare 301 gets stored by browsers and reused without re-checking the server, which is why it feels irreversible. Both may downgrade a POST to a GET.

Should I use a 301 or a 302 redirect for SEO? Use 301 for any move you intend to keep: a domain change, an https upgrade, a permanent URL restructure. Use 302 for genuinely temporary situations such as a maintenance page, a geo-based landing test, or a seasonal URL. Search engines handle both, but 301 is the unambiguous signal that the destination is the canonical URL.

Can I undo a 301 redirect? Not immediately, and not for clients that already cached it. You can bound the problem in advance by sending Cache-Control: max-age=... on the 301 itself. bit.ly ships 301s with a 90-second max-age for exactly this reason. If you have already shipped a bare 301, serve the old URL again with a short max-age and wait out the caches you cannot reach.

Is there such a thing as a 301 redirect DNS record? No. DNS returns records, not HTTP status codes, and resolution finishes before any HTTP request is made. Registrar "URL forwarding" is a real 301 or 302, but it is served by web servers the registrar runs, which is why it stops working when you move your nameservers elsewhere.

When should I use 307 or 308 instead? Whenever the request might not be a GET. 301 and 302 both permit clients to rewrite POST to GET, the RFC says so explicitly and curl does it, so redirecting an API endpoint with either will silently drop request bodies. 307 preserves the method for a temporary move, 308 for a permanent one.

Why does my redirect checker show a different chain than my browser? Because the browser is not asking the same questions. HSTS and the preloaded-TLD list make browsers rewrite http to https internally before any request leaves the machine, which appears in DevTools as a "307 Internal Redirect" that no server sent. Any .dev or .app domain is affected on every browser. Checkers also see only HTTP-level redirects, not meta refresh or JavaScript navigation.

Want this handled for you? Start free with Domainee — 50 custom domains + 100 GB bandwidth, no card.