Redirects
| 301, 302, 307, 308 | |
| Apache: .htaccess | |
| Two rules for one page | |
| Nginx | |
| Check a redirect | |
| When it does not work |
A redirect answers a request for an old address with a status code and the new address in the Location header; the browser then loads the new one. Where you set it depends on the web server: .htaccess on Apache, nginx.conf on Nginx. The outputs on this page come from test containers with Apache 2.4 and Nginx 1.23, checked on 26 October 2026.
301, 302, 307, 308
| Code | Meaning | Use it for |
|---|---|---|
| 301 | Moved permanently | A page that moved for good. Search engines move its ranking to the new address. |
| 302 | Found, temporary | A short-time detour; the old address stays the main one. |
| 307 | Temporary, keep the method | Like 302, but a POST stays a POST. |
| 308 | Permanent, keep the method | Like 301, but a POST stays a POST. |
All codes: HTTP status codes.
Apache: .htaccess
In the .htaccess of the site root, one line per redirect: the old path from the root of the site, then the new address. Redirect belongs to mod_alias and needs no RewriteEngine on:
Redirect 301 /old_url1.php /new_url.php Redirect 301 /old_dir/url1.php /new_dir/url1.php
A rule for a folder moves everything below it and keeps the rest of the path, so /old_dir/a.php goes to /new_dir/a.php:
Redirect 301 /old_dir /new_dir
If the site runs behind a proxy that ends HTTPS, a relative target such as /new_url.php can come back as an http:// address. Why, and the mod_rewrite form that avoids it: .htaccess.
Two rules for one page
A folder moved, and one page in it also got a new name:
/old_dir/a.php → /new_dir/a.php
/old_dir/page.php → /new_dir/new_page.php
Apache uses the first Redirect that matches. With the folder rule first, page.php first goes to /new_dir/page.php, which does not exist, and needs a second rule from there. The visitor makes two hops:
Redirect 301 /old_dir /new_dir Redirect 301 /new_dir/page.php /new_dir/new_page.php
curl -sIL http://127.0.0.1:18080/old_dir/page.php | grep -E '^(HTTP|Location)'
HTTP/1.1 301 Moved Permanently Location: http://127.0.0.1:18080/new_dir/page.php HTTP/1.1 301 Moved Permanently Location: http://127.0.0.1:18080/new_dir/new_page.php HTTP/1.1 200 OK
Put the rule for the single page first, and it is one hop:
Redirect 301 /old_dir/page.php /new_dir/new_page.php Redirect 301 /old_dir /new_dir
HTTP/1.1 301 Moved Permanently Location: http://127.0.0.1:18080/new_dir/new_page.php HTTP/1.1 200 OK
Exact rules before folder rules, in every server.
Nginx
Nginx has no .htaccess; the rules go into the server block of the site, see nginx.conf. An exact location wins over a prefix one regardless of the order:
location = /old_url1.php { return 301 /new_url.php; } location = /old_dir/page.php { return 301 /new_dir/new_page.php; } location ^~ /old_dir/ { rewrite ^/old_dir/(.*)$ /new_dir/$1 permanent; }
Nginx turns a relative target into a full address with the host and its own port. Behind a port mapping or a proxy that is the wrong port. The test container listened on 80, mapped to 18081 outside:
curl -sI http://127.0.0.1:18081/old_dir/page.php | grep -E '^(HTTP|Location)'
HTTP/1.1 301 Moved Permanently Location: http://127.0.0.1/new_dir/new_page.php
The port is lost. With absolute_redirect off; in the server block Nginx sends the target as written, and the browser keeps the host and port it used:
HTTP/1.1 301 Moved Permanently Location: /new_dir/new_page.php
Check a redirect
curl shows the first answer only with -I, and follows the whole chain with -L, as in the examples above. Every hop is a Location line; more than one means a chain to shorten.
When it does not work
Read the error log of the site. At most hosting providers it is in the control panel or in a logs folder next to the site; on your own Apache server it is usually /var/log/apache2/error.log. The last 100 lines:
tail -n 100 /var/log/apache2/error.log
A request for a page that no longer exists shows up there with its path; that path is the old address to redirect. A browser also remembers 301 answers: test a changed rule with curl or in a private window.
Article authors: Andrei Olegovich, Klavdiya Kondakova