Solving an Nginx problem on AWS EC2 – incorrect domain returned on http

Solving an Nginx problem on AWS EC2 – incorrect domain returned on http

This is a quick story of how moving to IPv6 caused an odd problem where requesting one website on my server returned another website.

I moved my AWS EC2 instance from public IPv4 to IPv6 address only to avoid the IPv4 charges AWS introduced in 2023. I have about six domains hosted on the server using Nginx, with CloudFlare as DNS and CDN. CloudFlare connects to my origin server over IPv6 only, and makes the domains available on the internet over IPv4 and IPv6.

The websites are all working properly over https, but I was finding odd behavior with http requests. When I made an http request to one domain it returned the website for another one of my domains. It took me quite a while to work out why.

For example, in this curl I request example.com and I get back a redirect to example2.com in the “Location” field

curl -v http://example.com
< HTTP/1.1 301 Moved Permanently
< Server: nginx
< Content-Type: text/html
< Connection: keep-alive
< Location: https://example2.com

Here’s the Nginx config – consider it pseudocode, it may not pass nginx tests.

server {
  listen 80;
  server_name example.com;
  return 301 https://example.com$request_uri;  
}

server {
  server_name example.com;
  listen [::]:443 ssl http2;

  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_dhparam /etc/nginx/ssl/dhparams.pem;

  root     /var/www/example/;
}

server {
  listen [::]:80;
  server_name example2.com;
  return 301 https://example2.com$request_uri;  
}

server {
  server_name example2.com;
  listen [::]:443 ssl http2;

  ssl_certificate /etc/letsencrypt/live/example2.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example2.com/privkey.pem;
  ssl_dhparam /etc/nginx/ssl/dhparams.pem;

  root     /var/www/example2/;
}

After reading through solutions others found to similar problems I found a reminder to look at Digital Oceans Nginx Location Matching guide. That guide reminded Nginx matches requests initially by listen port, then by server_name, so I reviewed the configuration and noticed something unexpected… the Nginx server blocks doing the http to https redirects hadn’t been updated to listen for IPv6. It occurred to me that the server block matching is by port and protocol, only if there’s more than one block on that listening port and protocol then it matches the server_name.

When I had moved to IPv6 I had updated all of the https servers to listen on both IPv4 and IPv6, but I’d missed updating most of the http redirect domains to listen on IPv6. Once I had the redirect servers listen on IPv6 as well as IPv4 things started working as expected.

The key steps in solving the problem were:

  • Using another Linux server, with the hosts file modified to point directly at the origin server rather than CloudFlare. I’m not sure this step helped, but it removed CloudFlare caching from the equation.
  • “curl -v http://example.com
  • Reading the digital ocean guide – though it doesn’t mention IPv6 it helped a lot

Here’s the key change I made to solve this problem.

server {
  # Listen on IPv4 and IPv6 on the line below
  listen [::]:80;
  server_name example.com;
  return 301 https://example.com$request_uri;  
}
Facebook Comments