How to Globally Disable IPv6-only Mode in Nginx for Dual Stack Support


3 views

Since Nginx 1.3.4, there's been a significant change in how IPv6 listeners behave. Previously, when you configured:

listen [::]:80 default_server;

Nginx would automatically handle both IPv4 and IPv6 connections. However, post-1.3.4, this directive now binds to IPv6 only by default.

The standard workaround is to add ipv6only=off to each IPv6 listener:

listen [::]:80 default_server ipv6only=off;

While this works, it becomes tedious when managing multiple server blocks across different configuration files.

For system-wide configuration, you can add this to your nginx.conf in the main context (outside any http/server blocks):

resolver ... ipv6=off;

But this doesn't fully solve our specific listener behavior. A more comprehensive approach involves modifying the core Nginx compilation:

./configure --with-http_realip_module --with-ipv6-disable

For those who can't recompile Nginx, consider these patterns:

1. Dual listen directives:

listen 80;
listen [::]:80 ipv6only=off;

2. Configuration generator script (example in Bash):

find /etc/nginx -type f -name "*.conf" -exec sed -i 's/listen $$::$$:$[0-9]\+$;/listen [::]:\1 ipv6only=off;/g' {} +

After making changes, always test with:

nginx -t
service nginx reload

Verify with:

ss -tulnp | grep nginx
curl -4 http://yourdomain.com
curl -6 http://yourdomain.com

While this solution works, be aware that:

  • Each dual-stack listener consumes slightly more memory
  • Connection handling becomes marginally more complex
  • Some older Nginx modules might not fully support this configuration

After upgrading to Nginx 1.3.4+, many administrators noticed their servers became inaccessible via IPv4 when using configurations like:

listen [::]:80 default_server;

This stems from a deliberate change in Nginx's behavior where IPv6 socket binding now defaults to IPV6_V6ONLY flag being enabled, making the socket IPv6-exclusive.

The standard solution is to modify each virtual host configuration:

listen [::]:80 default_server ipv6only=off;

While this works, it becomes tedious in multi-site environments where dozens of configurations need updating.

For system-wide IPv6 dual-stack behavior, add this to your nginx.conf in the main context (outside any http/server blocks):

server {
    listen [::]:80 default_server ipv6only=off;
    # Other global server directives...
}

Then in individual virtual hosts, simply use:

listen 80;
listen [::]:80;

For Linux systems, you can disable IPv6-only binding system-wide by modifying /etc/sysctl.conf:

net.ipv6.bindv6only = 0

Apply with sysctl -p. This makes all IPv6 sockets dual-stack by default.

Verify your setup with:

curl -4 http://yourserver
curl -6 http://yourserver
netstat -tulnp | grep nginx

The output should show both IPv4 and IPv6 listening sockets.

When transitioning configurations:

  • Test changes in staging first
  • Document the IPv6 policy for your team
  • Consider adding monitoring for protocol availability