skip to content

In Apache mod_rewrite, what is the difference between `RewriteRule ^old/(.*)$ /new/$1 [L]` and the same rule written `[R=301,L]`?

level: juniorimportance: must knowfreq 72%

answer

  1. one request or two
  2. who learns about the change
  3. Location header or nothing
  4. L means last, not redirect
  5. scheme in the substitution forces it

basics

~20 s

With [L] alone the rewrite is internal: Apache serves the new path itself and the client never learns anything changed. With [R=301,L] Apache sends a redirect response carrying a Location header, so the browser issues a second request and its address bar updates.

solid answer

~50 s

`[L]` performs an *internal* rewrite. The URL is remapped inside the server, one request is served, the client sees the original URL in its address bar and has no idea the mapping happened. `[R]` makes it an *external* redirect: httpd answers with a 3xx status and a `Location` header pointing at the substitution, and the browser then makes a second, separate request for the new URL. `R` with no value defaults to 302; `R=301` asks for a permanent one. So the choice is really about who should know: use `[L]` for internal routing you never want exposed — a front controller, a pretty URL mapped onto a real file — and use `[R=…]` when the canonical address genuinely changed and you want clients, caches and search engines to adopt it. One trap: if the substitution starts with a scheme and host such as `https://example.com/x`, mod_rewrite forces an external redirect whether or not you wrote `R`.

code

apache · 10 lines
apache
RewriteEngine On

# Silent internal routing: address bar keeps showing /products/1234
RewriteRule ^products/([0-9]+)$ /product.php?id=$1 [L]

# Permanent move: client is redirected and updates its address bar
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]

# Implicit redirect: an absolute target redirects even without R
RewriteRule ^shop/(.*)$ https://shop.example.com/$1 [L]

go deeper

for a junior

Be able to say plainly that [L] keeps everything inside the server while [R] sends the client a redirect it must follow, and that the address bar only changes in the second case.

for a middle

Explain that L only stops rule processing and carries no redirect meaning, that bare [R] is 302, and that a substitution starting with a scheme redirects implicitly.

for a senior

Show the diagnostic instinct: check the status line with curl before touching config, and account for the extra round trip an external redirect adds on every affected request.

for a principal

Own the canonicalisation policy — which URL forms are permanent redirects versus silent rewrites — so that redirect chains do not accumulate across teams and years.

## Two different things called "rewriting" mod_rewrite can do two operations that look nearly identical in the config file and are completely different on the wire. An **internal rewrite** changes which resource the server maps this request to. It happens entirely inside httpd. One request arrives, one response goes back, and the URL the client asked for is the URL the client still believes in. An **external redirect** ends the current request with a 3xx status and a `Location` header. The client then decides to make a second request to the new URL. Two requests, two round trips, and the client's address bar now shows the new address. ```apache RewriteEngine On # Internal: /old/report.html is served from /new/report.html, silently. RewriteRule ^old/(.*)$ /new/$1 [L] # External: the client is told to go and ask for /new/report.html itself. RewriteRule ^old/(.*)$ /new/$1 [R=301,L] ``` ## What each flag means `L` stands for "last": stop processing further rules in this ruleset for this pass. It says nothing at all about redirecting. On its own it simply means "this substitution is the answer, don't keep matching". `R` stands for "redirect", and it is what turns the substitution into a response rather than a remapping. Written bare, `[R]`, it uses 302. You may name the status explicitly — `R=301`, `R=302`, `R=303`, `R=307` — and mod_rewrite will also accept other 3xx values. What each of those status codes means to a browser or a cache is HTTP's business, not the rewrite engine's; what mod_rewrite guarantees is only that this is the status it will send with the `Location` header it builds. The two are almost always written together as `[R=301,L]`, because once you have decided to answer with a redirect, continuing to evaluate more rules against the same request is rarely what you want. ## The implicit redirect The substitution's shape can force the decision. If the substitution begins with a scheme and hostname — `http://`, `https://`, or `//host/path` — mod_rewrite cannot serve it internally, because the target is a different origin. It issues an external redirect even without `R`. This surprises people who write a rule pointing at their own site's absolute URL, expecting a silent internal remap, and instead find every request bouncing through a 302. The converse trap is a rule that *should* be a redirect but was written relative, so users keep seeing the old URL and nothing ever migrates. ## Choosing between them Use an internal rewrite when the external URL is the contract and the internal path is an implementation detail: - A front controller: every unmatched URL is served by `/index.php`, and the client never sees `index.php` in the address bar. - Clean URLs: `/products/1234` is really `/product.php?id=1234`, and you do not want the query-string form to leak or to become the address people bookmark. - Serving a different file per locale or per device from one URL. Use an external redirect when the address itself has changed and you want the world to update: - A moved or renamed section, where old links should end up at the new canonical URL. - Canonicalisation — one host, one trailing-slash form, one scheme — where having two live URLs for the same content is the problem you are solving. - Anything a search engine or a bookmark should learn about. ## Cost and visibility An internal rewrite is free in network terms: no extra round trip. An external redirect costs a full extra request, which on a mobile connection is tens to hundreds of milliseconds, and chains of them multiply that. That is a genuine argument for keeping internal routing internal. Visibility cuts the other way when you are debugging. An internal rewrite is invisible from outside: `curl -v` shows a single 200 for the original URL, so you cannot tell from the client whether a rule fired. `curl -sI https://example.com/old/x` immediately shows a redirect, status and `Location` and all. When an internal rule misbehaves you need the server side — raise `LogLevel` to a trace level (httpd 2.4 removed the old `RewriteLog` directive) and read the rewrite trace in the error log. One more consequence worth internalising: because an internal rewrite re-enters the request-mapping machinery, a per-directory rule whose output still matches its own pattern will loop. An external redirect loops too, but the browser stops it and shows a redirect-loop error; the internal version burns server-side recursion until httpd gives up and returns 500.

  • If you write [R] with no status code, what does mod_rewrite send?
    302. Bare `[R]` is equivalent to `[R=302]` — mod_rewrite's default is the temporary form, on the principle that a temporary redirect is the safer thing to get wrong. If you mean the move to be permanent you must say `R=301` (or `R=308`) explicitly. Getting this wrong is common and one-directional in its pain: a wrongly-permanent redirect is cached by clients long after you fix the config.
  • How can you tell from outside the server whether a URL was rewritten internally or redirected?
    Ask for it and look at the status line — `curl -sI https://example.com/old/x`. A redirect shows a 3xx status and a `Location` header, and `curl -L` will visibly follow it. An internal rewrite shows a single 200 for the URL you asked for, with nothing to distinguish it from a file that really lives there. To confirm an internal rule fired you need the server side: raise `LogLevel` to a trace level and read the rewrite trace.
  • Why might a rule you wrote without R still redirect the client?
    Because the substitution begins with a scheme and host — `https://…` or `//host/…`. mod_rewrite cannot map another origin to a local file, so it issues an external redirect regardless of flags. If you intended a silent remap within your own site, write the substitution as an absolute *path* (`/new/$1`) rather than an absolute URL.

saying these in an interview costs you the question

  • Says [L] updates the browser's address bar
  • Thinks bare [R] sends 301
  • Believes [L] means "redirect last"
  • Assumes an internal rewrite costs an extra round trip
  • Writes an absolute URL and expects a silent rewrite

context