Nginx Redirect: How to Construct $scheme://$host:$port Dynamically for URL Rewrites


10 views


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.