=====================
== avisiblenetwork ==
=====================
seeing the invisible

Nginx Ipv6 Problem

Problem

I regularly use Nginx as a static web server behind Azure networking products like Application Gateway, or as a test machine for VM connectivity. I am fairly comfortable writing simple server blocks that return quick html pages displaying “Connected to the backend!!” for testing pruposes.

Recently, however, I encountered the same issue twice: despite writing specific server blocks with hostnames like “www.alpha.com”, I kept being served the default Nginx welcome page. I repeatedly reviewed my configurations and tested with curl, yet the default page persisted.

After checking the access logs, the problem became clear: the default configuration had the web server listening on IPv6, and my client machine was sending requests via IPv6. Eventhough I had written my server blocks to listen on port 80 of IPv4, the requests were arriving as IPv6 traffic and thus hitting the default IPv6 server block instead.

Steps

Copying Default Configs and Modifying for My Use Case

My typical workflow involves copying the default Nginx config, removing comments and what I consider unnecessary (which usually includes the IPv6 listener), and specifying the hostname so the server responds to the corresponding hostname in the HTTP header.

The default Http block:

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

        server {
            listen       80;
            listen       [::]:80;
            server_name  _;
            root         /usr/share/nginx/html;
    
            # Load configuration files for the default server block.
            include /etc/nginx/default.d/*.conf;
        }
}

My custom server block:

server {
	listen       80;
	server_name  www.alpha.com;
	root         /usr/share/nginx/alpha;
	index index.html;

	location / {
		try_files $uri $uri/ =404;
	}
}

I reloaded Nginx and tested with the following Curl command: curl -H "host: www.alpha.com" localhost

To my frustration, I received the generic Nginx default webpage:

  <body>
    <h1>Fedora Webserver <strong>Test Page</strong></h1>

    <div class='row'>
    
      <div class='col-sm-12 col-md-6 col-md-6 '></div>
          <p class="summary">If you can
            read this page, it means that the web server installed at
            this site is working properly, but has not yet been
            configured.</p>
      </div>

I reviewed the configuration repeatedly, checking that the default config did not have “default_server” on its listening port, verifying server_name setting, etc. After the request hit the IP address and port, the server block should have been selected based on the hostname.

I even attempted to make the alpha server block the “default_server”, but that did not work either.

After troubleshooting the configs themselves, I decided to look at the access logs…

What I Learned

When I examined the access logs, I immediately spotted the issue. All the Get requests were showing “::1” as the client address - the IPv6 loopback address.

::1 - - [09/Oct/2025:02:14:23 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:02:18:30 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:02:18:39 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:02:21:33 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
127.0.0.1 - - [09/Oct/2025:02:28:32 -0400] "GET / HTTP/1.1" 200 17 "-" "curl/8.6.0" "-"
127.0.0.1 - - [09/Oct/2025:02:28:51 -0400] "GET / HTTP/1.1" 200 17 "-" "curl/8.6.0" "-"
127.0.0.1 - - [09/Oct/2025:12:12:47 -0400] "GET / HTTP/1.1" 200 17 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:12:13:38 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:12:13:54 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"
::1 - - [09/Oct/2025:12:18:22 -0400] "GET / HTTP/1.1" 200 8474 "-" "curl/8.6.0" "-"

I quickly commented out the listen [::]:80; line in the default config, and my alpha page was served thereafter.

This was not strictly an Nginx configuration issue. The default config was listening on both IPv6 and IPv4. My client machine was actively prioritizing IPv6. I could have used a different curl command for the same result: curl -4 -H "host: www.alpha.com" localhost.

To verify my IP version preference in curl, I tested with curl -v www.microsoft.com, and confirmed that IPv6 was being preferred. My client machine has a curl preference, and most likely an OS-level preference configured somewhere (possibly via Happy Eyeballs?).

Shortened verbose curl process of getting www.microsoft.com:

 Host www.microsoft.com:80 was resolved.
* IPv6: 2600:1406:5e00:482::356e, 2600:1406:5e00:48a::356e
* IPv4: 23.45.42.76
*   Trying [2600:1406:5e00:482::356e]:80...
* Connected to www.microsoft.com (2600:1406:5e00:482::356e) port 80
> GET / HTTP/1.1
> Host: www.microsoft.com
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK

Takeaways

Read the logs first. It is easy to develop tunnel vision when troubleshooting simple setups. You think there are only a few places where mistakes could occur, so you focus on your current server block, verify the directory it is pointing to, and gloss over the default config.

The access log immediately revealed that the server was receiving IPv6 requests, which made everything click into place.

This exercise reinforced an important lesson: when something seemingly simple is not working, step back and examine the actual traffic rather than assumptions about the traffic. The logs do not lie.