When handling redirects in Nginx, we often need to reconstruct the original request's protocol, domain, and port while modifying only the path portion. The standard Nginx variables like $scheme, $host, and $port work well individually, but concatenating them requires special handling.
The most straightforward approach is to combine the variables manually in your location block:
location = /login.htm {
return 301 $scheme://$host:$server_port/login;
}
Note we use $server_port instead of $port since $port shows the client's port in some cases.
To avoid showing redundant port numbers (like :443 for HTTPS), we can use a more sophisticated approach:
map $scheme $port_suffix {
http ":80";
https ":443";
default "";
}
server {
...
set $show_port $server_port;
if ($server_port = $port_suffix) {
set $show_port "";
}
if ($show_port != "") {
set $show_port ":$show_port";
}
location = /login.htm {
return 301 $scheme://$host$show_port/login;
}
}
If you're okay with the browser potentially following redirects to different host variations (www vs non-www), you could simplify to:
location ~* \.htm$ {
return 301 $scheme://$host$uri;
}
Remember these important details:
- $host includes the port if it's non-standard
- $http_host preserves the exact Host header
- For proxy scenarios, you might need $proxy_host
Here's a full example handling various cases:
server {
listen 80;
listen 443 ssl;
server_name example.com;
# Port normalization
set $port_display "";
if ($server_port !~ "^(80|443)$") {
set $port_display ":$server_port";
}
# Redirect .htm to clean URLs
location ~* ^(.+)\.htm$ {
return 301 $scheme://$host$port_display$1;
}
# Other configurations...
}
When implementing redirects in Nginx, we often need to maintain the original request's protocol, domain, and port while only modifying the path. The common approach using $scheme://$host:$port
works but feels verbose. Let's explore more elegant solutions.
The most reliable method is concatenating the individual variables:
return 301 $scheme://$host:$server_port$request_uri;
Key points about this approach:
$server_port
is more accurate than$port
as it reflects the actual server port- Works consistently for both HTTP (80) and HTTPS (443) default ports
- Preserves the original request's scheme (http/https)
For more complex deployments, consider this pattern:
server {
listen 80;
listen 443 ssl;
server_name example.com;
location /old-path {
return 301 https://$host$request_uri;
}
}
Notice we omit the scheme when using HTTPS to prevent double redirects.
When dealing with non-standard ports or load balancers:
map $http_x_forwarded_proto $real_scheme {
default $scheme;
"https" "https";
}
server {
# ...
return 301 $real_scheme://$host:$server_port/new-path;
}
This accommodates:
- Proxy headers (X-Forwarded-Proto)
- Custom port configurations
- Cloud deployments
Always prefer return
over rewrite
for simple redirects:
# Good
return 301 $scheme://$host/new-path;
# Avoid
rewrite ^/old-path /new-path permanent;
The return
directive is more efficient as it doesn't trigger regex processing.
Here's a complete implementation for removing file extensions:
server {
listen 80;
server_name www.example.com;
location ~ ^/(.*)\.htm$ {
return 301 $scheme://$host/$1;
}
}
This captures the path before .htm and redirects to the clean URL.