How to Disable IPv4-Mapped IPv6 Addresses in Linux for Pure IPv6 Services


1 views

When binding to :: (IN6ADDR_ANY), Linux systems by default accept both native IPv6 connections and IPv4 connections through IPv4-mapped IPv6 addresses (e.g., ::ffff:192.0.2.1). This behavior is controlled by the net.ipv6.bindv6only sysctl parameter.

To disable IPv4-mapped addresses globally:

# Temporary setting (until reboot)
sudo sysctl -w net.ipv6.bindv6only=1

# Permanent setting (add to /etc/sysctl.conf or /etc/sysctl.d/)
echo "net.ipv6.bindv6only = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

For applications where you want to control this behavior at runtime:

C/C++ Socket Programming

int enable = 1;
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(enable));

Python Example

import socket

sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
sock.bind(('::', 8080))

After making changes, verify the configuration:

# Check current sysctl value
sysctl net.ipv6.bindv6only

# Test connectivity
nc -6 -l :: 8080  # IPv6-only listener
nc -4 localhost 8080  # Should fail

Nginx Configuration

server {
    listen [::]:80 ipv6only=on;
    # ... other config
}

Apache Configuration


    # ... config

Disabling IPv4-mapped IPv6 can improve security by:

  • Reducing attack surface by eliminating IPv4 processing paths
  • Preventing potential IPv4-related vulnerabilities in IPv6 services
  • Simplifying firewall rules by clearly separating IPv4/IPv6 traffic

However, ensure your infrastructure fully supports IPv6 before implementing this change.


When you bind a service to :: (IPv6 wildcard address) on Linux, the kernel automatically handles IPv4 connections through IPv4-mapped IPv6 addresses (like ::ffff:198.51.100.37). This behavior is defined in RFC 4291 section 2.5.5.2.

There are several valid reasons for wanting pure IPv6 services:

  • Security hardening by reducing attack surface
  • Explicit control over protocol availability
  • Preventing accidental IPv4 traffic when testing IPv6-only environments
  • Compliance with network policies

The most effective way to disable IPv4-mapped IPv6 addresses system-wide is through sysctl:

# Disable IPv4-mapped IPv6 addresses
echo "net.ipv6.bindv6only = 1" >> /etc/sysctl.conf
sysctl -p

This setting makes IPv6 sockets truly IPv6-only. After applying this change, services binding to :: will:

  • Accept only IPv6 connections
  • Return ECONNREFUSED for IPv4 attempts
  • Require explicit binding to 0.0.0.0 for IPv4 service

For individual applications, you can use IPV6_V6ONLY socket option:

C Example

#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    int opt = 1;
    
    // Enable IPv6-only mode
    setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
    
    struct sockaddr_in6 addr = {
        .sin6_family = AF_INET6,
        .sin6_port = htons(8080),
        .sin6_addr = in6addr_any
    };
    
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    listen(sockfd, 5);
    // ... rest of server code
}

Python Example

import socket

sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)

sock.bind(('::', 8080))
sock.listen(5)
# ... rest of server code

After implementation, verify the behavior:

# IPv6 connection should work
nc -6 ::1 8080

# IPv4 connection should fail
nc -4 127.0.0.1 8080

Nginx

server {
    listen [::]:80 ipv6only=on;
    # IPv4 listeners must be explicitly declared
    # listen 0.0.0.0:80;
}

SSH

# In /etc/ssh/sshd_config
AddressFamily inet6  # IPv6 only
# Or for dual stack:
# AddressFamily any
# ListenAddress ::
# ListenAddress 0.0.0.0

Problem: Legacy applications might not handle ECONNREFUSED properly.
Solution: Consider using a firewall rule to drop IPv4 packets instead of refusing them:

iptables -I INPUT -p tcp --dport 8080 -j DROP