How to Configure IPv6 Dual-Stack Networking on RHEL 7

IPv6 adoption has accelerated steadily, yet most production servers still need to serve IPv4 clients simultaneously. A dual-stack configuration allows a single network interface to carry both protocol versions concurrently, eliminating the need for tunneling or translation layers. On RHEL 7, NetworkManager provides the authoritative way to configure dual-stack interfaces, and both Nginx and Apache can be made to listen on both stacks with minimal configuration changes. This guide covers static dual-stack assignment using nmcli, the differences between SLAAC, DHCPv6, and static IPv6 addressing, web server dual-stack binding, firewall rules for both stacks, and practical testing with curl.

Prerequisites

  • RHEL 7 server with root or sudo access
  • NetworkManager installed and running (systemctl status NetworkManager)
  • An IPv6 address or prefix assigned by your hosting provider or router (for static/DHCPv6), or router advertisement enabled on the network segment (for SLAAC)
  • Basic familiarity with nmcli and network interface naming
  • Nginx or Apache installed if you plan to configure dual-stack web serving

Step 1: Identify the Network Interface and Existing Connection

Before making changes, identify the active NetworkManager connection profile and the interface name.

# List all interfaces
ip link show

# List all NetworkManager connection profiles
nmcli connection show

# Show details for a specific connection (substitute "eth0" with your interface)
nmcli connection show eth0

Note the connection name (the first column in nmcli connection show). It may be eth0, ens3, System eth0, or similar. Use this exact name in subsequent commands.

Step 2: Understand IPv6 Configuration Methods

RHEL 7 NetworkManager supports three IPv6 configuration methods:

  • SLAAC (Stateless Address Autoconfiguration) — The interface generates its own IPv6 address using the network prefix advertised by the router (Router Advertisement). Set ipv6.method auto. No server-side configuration required beyond enabling RA on the router.
  • DHCPv6 — A DHCPv6 server assigns an IPv6 address. Set ipv6.method dhcp. Requires a DHCPv6 server on the network segment.
  • Static — You manually specify the IPv6 address, prefix length, and gateway. Set ipv6.method manual with ipv6.addresses and ipv6.gateway.

For servers, static assignment is strongly preferred because it guarantees a predictable address for DNS records and firewall rules.

Step 3: Configure a Static Dual-Stack Interface with nmcli

The following commands assign a static IPv4 address, a static IPv6 address, DNS servers for both stacks, and bring the connection up. Replace the example addresses with values appropriate for your network.

# Set static IPv4
sudo nmcli con modify eth0 
  ipv4.method manual 
  ipv4.addresses "203.0.113.10/24" 
  ipv4.gateway "203.0.113.1" 
  ipv4.dns "8.8.8.8 8.8.4.4"

# Set static IPv6
sudo nmcli con modify eth0 
  ipv6.method manual 
  ipv6.addresses "2001:db8:1:1::10/64" 
  ipv6.gateway "2001:db8:1:1::1" 
  ipv6.dns "2606:4700:4700::1111 2606:4700:4700::1001"

# Disable IPv6 privacy extensions for server stability
sudo nmcli con modify eth0 ipv6.ip6-privacy 0

# Apply changes
sudo nmcli con up eth0

Verify that both addresses are assigned:

ip addr show eth0

You should see both an inet line (IPv4) and an inet6 line (IPv6) in the output.

Step 4: Configure SLAAC or DHCPv6 (Optional Alternative)

If your network provides SLAAC or DHCPv6, the configuration is simpler. You can still combine automatic IPv6 with static IPv4.

# SLAAC for IPv6, static for IPv4
sudo nmcli con modify eth0 ipv6.method auto

# DHCPv6 for IPv6, static for IPv4
sudo nmcli con modify eth0 ipv6.method dhcp

# Apply
sudo nmcli con up eth0

# Confirm IPv6 address was assigned
ip -6 addr show eth0

Step 5: Configure Nginx for Dual-Stack

By default on Linux, binding to an IPv6 socket with ipv6only=off causes the socket to accept both IPv4 and IPv6 connections (dual-stack socket). On RHEL 7 with Nginx, this is the recommended approach.

# /etc/nginx/conf.d/example.conf
server {
    # IPv6 dual-stack socket — accepts both IPv4 and IPv6
    listen [::]:80 ipv6only=off;

    server_name example.com www.example.com;
    root /var/www/html;
    index index.html;
}

server {
    listen [::]:443 ssl ipv6only=off;

    server_name example.com www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    root /var/www/html;
}
sudo nginx -t
sudo systemctl reload nginx

If you need separate IPv4 and IPv6 server blocks (for example, to log them separately), use explicit IPv4 and IPv6 listen directives:

listen 0.0.0.0:80;
listen [::]:80 ipv6only=on;

Step 6: Configure Apache for Dual-Stack

Apache on RHEL 7 uses a separate Listen directive for IPv4 and IPv6. Edit /etc/httpd/conf/httpd.conf or the appropriate include file.

# Replace or add Listen directives in /etc/httpd/conf/httpd.conf
Listen 80
Listen [::]:80

# For TLS in /etc/httpd/conf.d/ssl.conf:
Listen 443 https
Listen [::]:443 https

If Apache was compiled with IPV6_V6ONLY support (the default on RHEL 7), IPv4 and IPv6 must each have their own Listen line. Verify the configuration and reload:

sudo apachectl configtest
sudo systemctl reload httpd

Step 7: Configure firewalld for Dual-Stack

On RHEL 7, firewalld applies rules to both IPv4 (iptables) and IPv6 (ip6tables) simultaneously when you use firewall-cmd. A single rule covers both stacks.

# Allow HTTP and HTTPS for both IPv4 and IPv6
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Reload firewall to apply
sudo firewall-cmd --reload

# Verify
sudo firewall-cmd --list-services

To allow a custom port on both stacks explicitly:

sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload

Step 8: Configure DNS A and AAAA Records

For dual-stack to be reachable by hostname, your DNS zone must have both an A record (IPv4) and an AAAA record (IPv6). In a BIND zone file:

; IPv4 address record
example.com.     IN  A     203.0.113.10

; IPv6 address record
example.com.     IN  AAAA  2001:db8:1:1::10

; www subdomain
www.example.com. IN  A     203.0.113.10
www.example.com. IN  AAAA  2001:db8:1:1::10

After updating DNS, propagation typically takes between a few minutes and 24 hours depending on TTL values. Verify with:

dig example.com A
dig example.com AAAA

Step 9: Test Connectivity with curl

The -4 and -6 flags to curl force connections over IPv4 or IPv6 respectively, making it straightforward to confirm that both stacks are functional.

# Force IPv4
curl -4 -I http://example.com

# Force IPv6
curl -6 -I http://example.com

# Test HTTPS over IPv6 with verbose output
curl -6 -v https://example.com

# Test from external (replace with your server's IPv6 address)
curl -6 -I http://[2001:db8:1:1::10]/

Use ping6 and traceroute6 to check Layer 3 reachability when HTTP tests fail:

ping6 -c 4 2001:db8:1:1::10
traceroute6 2001:db8:1:1::10

Conclusion

Configuring a dual-stack RHEL 7 server involves setting both IPv4 and IPv6 parameters via nmcli, updating web server listen directives to handle both protocol families, ensuring firewalld permits traffic on both stacks, and publishing DNS A and AAAA records. Once in place, a dual-stack configuration is largely transparent to application code — your web server and most services handle both families automatically. Regular testing with curl -4 and curl -6 after any configuration change helps catch regressions before they affect end users, and monitoring both IPv4 and IPv6 health checks provides full visibility into the availability of your server across all network paths.