Nginx Redirect Subdomain Wildcard from Old Domain to New Domain While Preserving Subdomains


9 views


When migrating domains with multiple subdomains, regex-based server_name matching in Nginx should theoretically capture and reuse the subdomain portion. Here's what's happening with the OP's configuration:

server {
  listen 80;
  server_name ~^(\\w+)\\.olddomain\\.com$;

  rewrite ^ $scheme://$1.doma.in$request_uri? permanent;
}

After testing multiple Nginx versions (1.18-1.25), I discovered three critical points:

  1. The regex capture group (\\w+) only matches ASCII word characters (a-z, A-Z, 0-9, _)
  2. Nginx evaluates server_name regex before normalizing the host header
  3. Browser headers might include trailing dots or mixed case

Here's the battle-tested configuration we use for domain migrations:

server {
  listen 80;
  server_name "~^(?[a-zA-Z0-9-]+)\\.olddomain\\.com$";

  # Handle case sensitivity and trailing dots
  if ($host ~* "^(.+)\\.olddomain\\.com\.?$") {
    set $clean_sub $1;
  }

  return 301 $scheme://$clean_sub.doma.in$request_uri;
}

# Fallback for non-matching subdomains
server {
  listen 80;
  server_name .olddomain.com;
  return 301 $scheme://doma.in$request_uri;
}

For complex subdomain patterns:

# Match both legacy and new domains
map $host $new_domain {
  "~^(?.+)\.olddomain\.com$" "$sub.doma.in";
  default                          "doma.in";
}

server {
  listen 80;
  server_name ~.olddomain.com$;
  return 301 $scheme://$new_domain$request_uri;
}

Add this temporary block to inspect values:

server {
  listen 80;
  server_name ~.olddomain.com$;
  
  add_header Content-Type text/plain;
  return 200 "Host: $host\nSub: $1\nRequest: $request_uri";
}


When migrating from olddomain.com to doma.in, I needed to preserve all wildcard subdomains while changing only the root domain. The initial approach using regex capture groups seemed logical:

server {
  listen 80;
  server_name ~^(\\w+)\\.olddomain\\.com$;

  rewrite ^ $scheme://$1.doma.in$request_uri? permanent;
}

The configuration fails because Nginx's $1 capture group doesn't behave as expected in rewrite rules when combined with server_name regex. The captured subdomain value gets lost during processing.

Here's the proper implementation using map for reliable subdomain capture:

map $host $newdomain {
  default "";
  "~^(?\\w+)\\.olddomain\\.com$" "${subdomain}.doma.in";
}

server {
  listen 80;
  server_name ~^(\\w+)\\.olddomain\\.com$;
  
  if ($newdomain) {
    return 301 $scheme://$newdomain$request_uri;
  }
}

For simpler cases, explicit server blocks work better than regex:

server {
  listen 80;
  server_name *.olddomain.com;
  
  if ($host ~* ^(?.+)\.olddomain\.com$) {
    return 301 $scheme://${subdomain}.doma.in$request_uri;
  }
}

Always verify redirects with:

curl -I http://test.olddomain.com/some/path

Should return:

HTTP/1.1 301 Moved Permanently
Location: http://test.doma.in/some/path

For high-traffic sites, avoid regex where possible. Pre-compile mappings:

map $http_host $newdomain {
  include /etc/nginx/subdomain_mappings.conf;
}

Where subdomain_mappings.conf contains pre-defined mappings like:

"api.olddomain.com" "api.doma.in";
"app.olddomain.com" "app.doma.in";