In modern Nginx configurations, the handling of dual-stack networking comes down to whether you need protocol-specific socket options. While listen [::]:80 ipv6only=off
implicitly handles IPv4 via IPv6-mapped addresses, there are operational cases where separate directives are preferable:
# Combined approach (works but lacks granularity)
listen [::]:80 ipv6only=off;
# Separate directives (allows protocol-specific tuning)
listen 80 backlog=4096 deferred;
listen [::]:80 ipv6only=on so_keepalive=on;
Consider these production-grade examples where separate directives prove necessary:
# When IPv4 needs TCP optimizations absent in IPv6
listen 80 reuseport fastopen=256;
listen [::]:80 ipv6only=on reuseport;
# When applying different security policies
listen 80 proxy_protocol;
listen [::]:80 ipv6only=on ssl;
The Linux kernel's handling of IPv6 sockets with v4-mapped addresses (when ipv6only=off
) introduces subtle differences:
- Socket buffer allocation may differ between protocols
- TCP stack behaviors (like SYN retries) can vary
- Some kernel versions have different queue handling
# For maximum consistency across protocols
listen 80 rcvbuf=1m sndbuf=1m;
listen [::]:80 ipv6only=on rcvbuf=2m sndbuf=2m;
In containerized environments, explicit separation often works better:
# Docker/Kubernetes optimized config
listen 80 proxy_protocol bind=0.0.0.0;
listen [::]:80 ipv6only=on proxy_protocol bind=[::];
When configuring Nginx for dual-stack (IPv4/IPv6) environments, developers often encounter these two approaches:
# Approach 1: Separate directives
listen 80;
listen [::]:80 ipv6only=on;
# Approach 2: Single directive
listen [::]:80 ipv6only=off;
The fundamental difference lies in how Nginx handles socket binding:
ipv6only=on
creates separate sockets for each protocolipv6only=off
uses IPv6 socket's dual-stack capability (on supported systems)
Consider explicit separation when:
# Example: Different TCP parameters per protocol
listen 80 deferred reuseport fastopen=5;
listen [::]:80 ipv6only=on reuseport fastopen=10;
Or when needing protocol-specific logging:
server {
listen 80;
listen [::]:80 ipv6only=on;
set $log_prefix_ipv4 "";
set $log_prefix_ipv6 "";
if ($server_port = 80) { set $log_prefix_ipv4 "IPv4-"; }
if ($server_port = [::]:80) { set $log_prefix_ipv6 "IPv6-"; }
access_log /var/log/nginx/access.log combined;
}
Single directive (ipv6only=off
) generally offers:
- Reduced kernel socket allocation
- Simpler connection handling
- Lower memory overhead
Important exceptions where separate directives are mandatory:
- Older Linux kernels (< 3.9) without proper IPv6 dual-stack support
- BSD systems with different IPv6 implementation
- When binding to specific interfaces (
listen 192.0.2.1:80
+[2001:db8::1]:80
)
For most modern deployments:
# Preferred configuration for Linux kernels ≥ 3.9
listen [::]:80 ipv6only=off;
# Fallback for older systems (optional)
listen 80;
This provides the cleanest implementation while maintaining compatibility. The fallback line ensures IPv4 connectivity if the system doesn't properly support IPv6 dual-stack sockets.
Verify your configuration with:
ss -tulnp | grep nginx
netstat -tulnp | grep nginx # For older systems
Look for separate IPv4 and IPv6 sockets when using separate directives, or a single IPv6 socket with dual-stack capability when using ipv6only=off
.