Apache and nginx solve redirects with different tools but the same HTTP underneath: Apache uses Redirect and RewriteRule in an .htaccess or vhost, while nginx uses return and rewrite inside a server block, and both ultimately send a 301 or 302 status with a Location header. Once you see that the server directives are just two syntaxes for producing the same response, choosing between them — and porting rules from one to the other — becomes straightforward. This article compares them directive by directive, explains the status codes and regex backreferences, decodes the flags, and shows how to test a rule before it reaches production.
What is the core difference between the two servers?
The difference is where and how the rule is evaluated, not what it produces. Apache can read .htaccess files per directory at request time, which is convenient on shared hosting but costs a filesystem lookup on every request. nginx has no per-directory config; redirects live in the server or location block and are loaded once when the config is parsed. Here is the same permanent redirect in both.
| Task | Apache | nginx |
|---|---|---|
| Simple permanent redirect | Redirect 301 /old /new |
return 301 /new; |
| Regex redirect with capture | RewriteRule ^blog/(.*)$ /news/$1 [R=301,L] |
rewrite ^/blog/(.*)$ /news/$1 permanent; |
| Temporary redirect | Redirect 302 /sale /promo |
return 302 /promo; |
| Force HTTPS | RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] |
return 301 https://$host$request_uri; |
| Backreference syntax | $1, $2 … |
$1, $2 … |
| Config granularity | Per-directory (.htaccess) or vhost | server / location blocks only |
Read across any row and the HTTP result is identical. What changes is verbosity and evaluation cost.
When should I use Redirect versus RewriteRule in Apache?
Use Redirect for fixed paths and RewriteRule when you need pattern matching or conditions. Redirect comes from mod_alias and does prefix matching: Redirect 301 /docs /help also moves /docs/intro to /help/intro, because it matches the prefix and appends the remainder. That behaviour is a feature when you are relocating a whole tree and a surprise when you only meant the exact path — for a single URL, prefer RedirectMatch 301 ^/docs$ /help so the anchor pins it.
RewriteRule comes from mod_rewrite and is a small pattern-matching engine. It takes a regular expression, a substitution, and a set of flags in square brackets:
RewriteEngine On
RewriteRule ^products/([0-9]+)/?$ /catalog/item?id=$1 [R=301,L]
The ([0-9]+) group captures the numeric id, and $1 replaces it into the target. You reach for RewriteRule when the decision depends on more than the path — a query string, a host, a cookie, an existing file check — usually paired with RewriteCond lines that guard the rule. If your redirect is a plain “this path now lives at that path,” Redirect is easier to read and less prone to accidental over-matching.
How do nginx rewrite and return differ?
Use return for a known destination and rewrite when you must transform the URL with a regex. return is the fast path: it emits the status and Location immediately with no pattern evaluation, which is why the idiomatic HTTPS redirect is a one-liner return 301 https://$host$request_uri; rather than a rewrite. Because it does no matching, return cannot capture parts of the URL, so it suits fixed or wholesale redirects.
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
rewrite is nginx’s regex engine and is the analogue of Apache’s RewriteRule. It matches a pattern, substitutes with backreferences, and ends with a flag:
rewrite ^/blog/(.*)$ /news/$1 permanent;
The trailing word is the flag. permanent issues a 301, redirect issues a 302 — both send the browser onward with a Location header. The other two flags, last and break, do not redirect at all; they rewrite the URI internally and control whether nginx re-evaluates location matching. Confusing an internal-rewrite flag with an external-redirect flag is a frequent nginx mistake: last keeps the request on your server, while permanent sends the client to a new URL.
301 or 302 — which status code should I send?
Send 301 when the move is permanent and 302 when it is temporary, because the codes mean different things to browsers and search engines. A 301 Moved Permanently tells clients the resource has a new canonical home; search engines transfer ranking signals to the target, and browsers cache the redirect — often for a long time. A 302 Found signals a temporary detour; the original URL keeps its authority and clients should not assume the change is durable.
The practical consequence is caching. Because browsers cache 301s aggressively, a 301 you regret is hard to take back: users who already received it keep following the old rule until their cache expires, even after you delete the directive. So use 302 for anything provisional — a maintenance page, a seasonal promo, an experiment — and reserve 301 for changes you are confident are final. If you are unsure, a 302 is the safer default because it is reversible in practice.
How do regex backreferences work across both servers?
Both servers capture with parentheses and reuse with $n, numbered left to right. A backreference is just the text a capturing group matched, dropped into the destination. In ^blog/(.*)$ /news/$1, the (.*) captures everything after blog/, and $1 pastes it into the new path, so /blog/hello-world becomes /news/hello-world. A second group is $2, a third is $3, and so on.
Two portability notes matter when moving rules between servers. First, anchor your patterns. In nginx the rewrite pattern is matched against the URI including the leading slash (^/blog/), whereas Apache’s RewriteRule in an .htaccess context matches the path with the leading slash stripped (^blog/). Forgetting this is why a rule that worked on one server matches nothing on the other. Second, remember that neither engine sees the query string in the path pattern by default — Apache needs %{QUERY_STRING} in a RewriteCond, and nginx exposes it as $args — so a rule that “does nothing” to a URL with parameters is usually matching the path correctly and simply ignoring the part you cared about.
What do the flags mean, side by side?
Flags decide the status code and whether processing stops. On the Apache side, [R=301,L] is the common pair: R=301 makes it an external redirect with that status (bare R defaults to 302), and L means “last,” halting further rules in this pass so a later rule cannot rewrite the URL again. On the nginx side the single trailing word carries the meaning: permanent = 301, redirect = 302, while last and break perform internal rewrites without redirecting the client. The mapping is clean: Apache [R=301,L] corresponds to nginx permanent, and Apache [R=302,L] corresponds to nginx redirect.
How do I test a redirect before deploying?
Test the status code and Location header directly, before you trust the rule in production. The lowest-level check is a header request that does not follow the hop, so you can see exactly what the server returns:
curl -sI https://example.com/old
# look for:
# HTTP/2 301
# location: https://example.com/new
Reading the raw response separates the two questions that matter — is the status the one you intended (301 vs 302), and is the Location exactly right, including the scheme, host, trailing slash, and any preserved path segment. Because browsers cache 301s, verify in a fresh profile or with curl rather than a tab that may already hold the old redirect. It also helps to trace a redirect chain: a rule that sends /a to /b which then sends to /c works but wastes a round trip and dilutes the benefit, so collapse chains to a single hop where you can.
You can dry-run the logic before touching a server at all. Our redirect tester runs in your browser: paste a rule and a sample URL, and it shows the resulting destination and status so you can confirm the pattern and backreferences match what you expect. Because it is client-side, no URLs or configuration leave your machine — useful when the paths you are testing reveal internal structure you would rather not paste into a remote service. Get the rule right in a tester, confirm the live headers with curl, and only then ship it.