When examining HTTPD server behavior on Linux systems, you might encounter an interesting phenomenon where netstat
or ss
shows listening sockets bound only to IPv6 (tcp6
), yet the service remains fully accessible via IPv4. This occurs due to Linux's implementation of dual-stack sockets and has important implications for server administration.
// Example of checking listening ports
$ netstat -ltn | grep :443
tcp6 0 0 :::443 :::* LISTEN
Modern Linux kernels implement a special behavior for IPv6 sockets when the IPV6_V6ONLY
socket option is not set (which is the default in most distributions). In this case:
- An IPv6 socket listening on
::
(equivalent to0.0.0.0
in IPv4) will automatically accept both IPv4 and IPv6 connections - The kernel handles IPv4 connections by mapping them to IPv6-mapped IPv4 addresses (::ffff:x.y.z.w)
- This behavior is controlled by the
/proc/sys/net/ipv6/bindv6only
parameter
To confirm whether your HTTPD is truly IPv6-only or actually dual-stack:
# Check the global dual-stack setting
$ cat /proc/sys/net/ipv6/bindv6only
0 # 0 means dual-stack is enabled
# Check Apache's listen directives
$ apachectl -S | grep listening
*:443 # This wildcard listen will create dual-stack behavior
In some cases, you might explicitly want separate listeners. Here's how to configure Apache for this:
# In httpd.conf
Listen 0.0.0.0:443
Listen [::]:443
# And set IPV6_V6ONLY for the IPv6 socket
SetEnvIf Listener "[::]:443" ipv6only=1
The dual-stack approach actually has some advantages:
- Single socket handling reduces kernel resource usage
- Simplified connection management in the web server
- Consistent logging format (all connections appear as IPv6)
However, if you need to distinguish between IPv4 and IPv6 connections for logging or access control, you'll need to configure separate listeners as shown above.
To see the actual connection types being handled:
$ ss -tnp sport = :443
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.1.100:443 192.168.1.50:54321
ESTAB 0 0 [2001:db8::1]:443 [2001:db8::2]:12345
When examining HTTPD server behavior with netstat
, you might notice an interesting scenario where the service appears to be listening only on IPv6 (tcp6
), yet remains fully accessible via IPv4 connections. This occurs due to Linux's dual-stack socket implementation.
Modern Linux kernels implement a special behavior for IPv6 sockets: when an application binds to ::
(IPv6 wildcard address), the kernel automatically makes the service available on both IPv4 and IPv6 addresses through a single socket. This is controlled by the net.ipv6.bindv6only
sysctl parameter (defaulting to 0).
# Check current dual-stack setting
sysctl net.ipv6.bindv6only
# Temporary change (for testing)
sysctl -w net.ipv6.bindv6only=1
# Permanent change (add to /etc/sysctl.conf)
echo "net.ipv6.bindv6only = 1" >> /etc/sysctl.conf
This behavior explains why you see:
netstat
shows only IPv6 listening socket- Both IPv4 and IPv6 clients can connect
- Apache logs will show IPv4 addresses for IPv4 clients
You can confirm this behavior with these commands:
# Check listening ports with ss (modern alternative to netstat)
ss -tuln | grep 443
# Test IPv4 connectivity
curl -4 https://your-server
# Test IPv6 connectivity
curl -6 https://your-server
While the default behavior works well, you can explicitly configure Apache for specific IP versions:
# Listen on IPv4 only
Listen 0.0.0.0:443
# Listen on IPv6 only (requires bindv6only=1)
Listen [::]:443
# Listen on both explicitly (creates two sockets)
Listen 0.0.0.0:443
Listen [::]:443
The dual-stack approach actually provides benefits:
- Single socket management reduces kernel resource usage
- Simplified configuration
- Better performance for mixed environments
If you encounter issues:
- Verify
net.ipv6.bindv6only
setting - Check firewall rules for both IP versions
- Ensure proper DNS records (A and AAAA)
- Test with both
curl -4
andcurl -6