How to Route and Configure a /48 IPv6 Subnet in AlmaLinux 8 & 9
DEV Community

How to Route and Configure a /48 IPv6 Subnet in AlmaLinux 8 & 9

An IPv6 /48 allocation provides a large address space that can be divided into 65,536 individual /64 subnets (providing over 1.2 septillion addresses). These subnets can support dedicated public addressing for virtual machines, containers, proxy infrastructure, and other network services.

Native IPv6 routing can provide publicly routable addresses to virtual machines and containers without requiring traditional IPv4-style NAT, provided the upstream network routes the allocated prefix correctly. This guide details the architecture and commands required to safely configure a /48 subnet in AlmaLinux 8 and 9 using NetworkManager (nmcli), handle source address selection, and natively route IPv6 to Docker containers.

(Note: We use the RFC 3849 documentation prefix 2001:db8:abcd::/48 in all examples. Replace this with your actual allocated /48 prefix).

Prerequisites and Provider Routing Requirements

Before modifying server networking, ensure you have:

  • Out-of-Band (OOB) Console Access : (e.g., IPMI, iLO, KVM, or your provider's rescue console). Reapplying network configurations can temporarily drop SSH connections.
  • Confirmation of Upstream Routing : You must know how your provider routes the /48 to your server. Configuring the host depends entirely on whether the provider routes the /48 directly to your server's primary IPv6 address, or if it expects you to respond to Neighbor Discovery on an on-link /64.

Step 1: Identify Your Active Network Interface and Connection Profile

In AlmaLinux 8 and 9, NetworkManager controls networking. You need both the physical interface name and its NetworkManager connection profile name-they are not always identical.

First, find the physical interface name:

ip -br a

Example output shows ens3 as the active interface.

Next, find the associated Connection Profile:

nmcli connection show --active

Example Output:

NAMEUUIDTYPEDEVICE
System ens3 5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03ethernetens3

In this example, the connection profile name is " System ens3 ". You must use this specific profile name when issuing nmcli commands.

Check the Existing NetworkManager IPv6 Configuration:

Before assigning additional IPv6 addresses, check the current IPv6 configuration of your NetworkManager connection:

nmcli -f ipv6.method,ipv6.addresses,ipv6.gateway connection show "System ens3"

The ipv6.method setting determines how NetworkManager handles IPv6 configuration. Common values include auto, manual, disabled, and ignore. Check the existing method before adding static addresses. Do not change it blindly, as doing so may affect your provider's existing IPv6 gateway or Router Advertisement configuration.

Step 2: Understand Routed /48 vs On-Link /64 Configurations

Do not blindly assign a /64 prefix to your main network interface.

Routed /48 (Recommended) : The provider routes the entire /48 to your server's primary IPv6 address. In this setup, assigning a full /64 to your physical interface creates an unwanted connected route, causing Linux to send Neighbor Solicitations for addresses that should be internally routed. Instead, assign individual host IP addresses as /128. Note that assigning a /128 to the host does not automatically route the rest of the /48 ; the provider-routed prefix and your local container/VM routes must work together.

On-Link /64 : The provider connects a /64 directly to your VLAN. In this scenario, assigning a /64 to the interface is appropriate. Check your provider's documentation before proceeding.

Step 3: Assign Secondary IPv6 Addresses Using NetworkManager

To bind specific IPs from your allocation (e.g., 2001:db8:abcd:1::10) to the host system, append them to the active connection profile. Assuming a routed setup, we bind these as /128. (Remember to replace " System ens3 " with your actual profile name).

nmcli connection modify "System ens3" +ipv6.addresses "2001:db8:abcd:1::10/128"
nmcli connection modify "System ens3" +ipv6.addresses "2001:db8:abcd:1::11/128"

Apply the changes (ensure you have OOB access in case of a typo):

nmcli connection up "System ens3"

Step 4: Configure IPv6 Source Address Selection

When multiple IPv6 addresses are bound to an interface, Linux source address selection follows RFC 6724 rules (including scope, destination matching, and address preference). Depending on these rules, Linux might select one of your secondary IPs for outbound traffic. If an application reaches out to an external API, and your primary IP relies on specific firewall whitelists or a strict Reverse DNS (PTR) record for mail, this can cause outbound failures.

You can explicitly mark secondary addresses as deprecated. Deprecated addresses remain fully reachable for inbound connections, but RFC 6724 prefers non-deprecated addresses for automatic outbound source selection:

ip -6 addr change 2001:db8:abcd:1::10/128 dev ens3 preferred_lft 0
ip -6 addr change 2001:db8:abcd:1::11/128 dev ens3 preferred_lft 0

Note: To make this persistent across reboots in NetworkManager, configure manual IP lifetimes or use a dispatcher script in /etc/NetworkManager/dispatcher.d/. Deprecation only influences automatic source selection. Applications can still explicitly bind to any available address. For example:

curl --interface 2001:db8:abcd:1::10 https://api64.ipify.org

Step 5: Enable IPv6 Forwarding and Handle Router Advertisements

To route native IPv6 subnets to Docker or VMs, the server must act as a router. Enabling IPv6 forwarding in Linux modifies how the kernel handles Router Advertisements (RA). If your provider supplies the default IPv6 gateway through Router Advertisements, configure accept_ra=2 on the upstream-facing interface when IPv6 forwarding is enabled. If your provider uses a statically configured gateway, this setting may not be necessary.

Create a persistent sysctl configuration:

cat << EOF > /etc/sysctl.d/99-ipv6-routing.conf
net.ipv6.conf.ens3.accept_ra=2
net.ipv6.conf.all.forwarding=1
EOF

Apply the settings and verify your default route was not lost:

Set accept_ra=2 only if your provider supplies the default IPv6 gateway through Router Advertisements. This setting allows the interface to accept Router Advertisements even when IPv6 forwarding is enabled. If your provider uses a statically configured gateway, this setting is generally unnecessary.

Enabling net.ipv6.conf.all.forwarding=1 enables IPv6 forwarding system-wide. Configure firewall rules to prevent unintended forwarding between network interfaces.

sysctl -p /etc/sysctl.d/99-ipv6-routing.conf
sysctl net.ipv6.conf.all.forwarding
sysctl net.ipv6.conf.ens3.accept_ra
ip -6 route show default

Step 6: Route a /64 Subnet to Docker Containers

With forwarding enabled, you can delegate a full /64 (e.g., 2001:db8:abcd:d0c::/64) to Docker.

Requirement : This requires Docker Engine 27.0 or later. Verify your version before proceeding:

docker version

Create a routed Docker network:

docker network create --ipv6 --subnet 2001:db8:abcd:d0c::/64 \
-o com.docker.network.bridge.gateway_mode_ipv6=routed pub6

Important : Configure Upstream IPv6 Routing Docker automatically creates the local bridge network and its associated routes, but it does not configure your provider's upstream router. Before expecting external IPv6 connectivity, make sure your provider routes the Docker container's /64 subnet through the server's primary IPv6 address. Without the correct upstream route, containers may communicate locally but remain unreachable from external IPv6 networks.

Deploy a container with a static public IPv6 address:

docker run -d --name web-node1 --network pub6 \
--ip6 2001:db8:abcd:d0c::80 -p 80:80 nginx:alpine

Security note: Published Docker ports may be accessible externally over IPv4 or IPv6, depending on the network configuration. Only publish the ports required by your application and configure appropriate firewall restrictions.

Understanding Dual-Stack Port Publishing (-p 80:80): In IPv6 routed mode, Docker exposes the published container port directly on the container's native IPv6 address. The host port specified with -p is not used for IPv6 routed-mode access. However, because the network retains Docker's default IPv4 NAT mode, that same -p 80:80 mapping successfully publishes the port on the host's IPv4 address.

Step 7: Configure Proxy NDP (Only When Required)

Whether you need Proxy NDP depends entirely on your provider's routing architecture:

  • Provider routes /48 through server's primary IPv6 : Not normally required.
  • Provider treats the container addresses as on-link : May be required.
  • Provider supplies a separately routed /64 through the server : Not normally required.

If your provider expects Layer 2 MAC resolution for the delegated IPs, you must enable Proxy NDP on the external interface and proxy the specific container address:

sysctl -w net.ipv6.conf.ens3.proxy_ndp=1
ip -6 neigh add proxy 2001:db8:abcd:d0c::80 dev ens3

To ensure these neighbour entries survive a server reboot, configure a systemd oneshot service to restore them during the boot sequence.

Step 8: Configure IPv6 Firewall Rules

Without NAT, your IPv6 addresses are publicly routable

Rule 1: Do not indiscriminately block ICMPv6 Essential ICMPv6 messages are required for Neighbor Discovery (NDP), Path MTU Discovery, and other core IPv6 network functions.

Rule 2: Host Firewall Configuration Before adding firewall rules, identify the zone assigned to your network interface:

firewall-cmd --get-active-zones

Use the actual active zone assigned to your interface when adding firewall rules. Do not assume that the interface belongs to the public zone. If the active zone is public, use the following command as shown. Otherwise, replace public with the actual zone name reported by firewall-cmd --get-active-zones.

firewall-cmd --zone=public --add-rich-rule= 'rule family="ipv6" destination address="2001:db8:abcd:1::10" port port="80" protocol="tcp" accept' --permanent
firewall-cmd --reload

Rule 3: Container Firewall Configuration Docker manages firewall rules for bridge networks, including published ports and container traffic filtering. Firewalld integrates with Docker through its dedicated docker zone and forwarding policies. For additional access restrictions, configure the appropriate firewall rules based on your active Docker firewall backend:

With Docker's iptables backend, the DOCKER-USER chain can be used to apply custom filtering rules. With the nftables backend (introduced as experimental in Engine 29), use separate nftables filtering chains instead.

not assume that host-level Firewalld rich rules automatically filter all forwarded traffic to Docker containers. Verify the effective IPv6 forwarding and firewall policies before exposing containers publicly.

Step 9: Verify IPv6 Addresses, Routes, and Connectivity

Use these diagnostic commands to separate IP assignment issues from routing failures:

Check assigned addresses, routing table, and default gateway:

ip -6 addr show dev ens3
ip -6 route
ip -6 route show default

Check which route Linux selects for an external destination:

ip -6 route get 2606:4700:4700::1111

Check Docker network routing and container port mappings:

docker network inspect pub6
docker container inspect web-node1 --format '{{json .NetworkSettings.Ports}}'

Test outbound and inbound connectivity:

Test inbound IPv6 connectivity from a separate device or server with working IPv6 connectivity. Testing from the same server does not confirm that the provider's upstream routing and external firewall configuration are working correctly.

# Outbound test from the server
curl -6 https://api64.ipify.org
# Inbound test (Run this from a separate external IPv6-connected machine)
curl -6 http://[2001:db8:abcd:d0c::80]/

Troubleshooting Common IPv6 Routing Problems

Small packets work, but large TCP transfers stall : Check whether ICMPv6 "Packet Too Big" messages are being blocked. IPv6 Path MTU Discovery relies on these messages to determine the appropriate packet size along the network path.

Ping works, but TCP connections time out : Check Firewalld rules, Docker published ports, container service status, and upstream routing before assuming an ICMPv6 problem.

Containers cannot reach the internet : Verify IPv6 forwarding, the upstream default route, and the source address configuration. If the gateway is learned through Router Advertisements, confirm that accept_ra=2 is configured on the correct interface when forwarding is enabled.

External networks cannot reach containers : Confirm that the provider routes the Docker subnet to the server. If the subnet is treated as on-link, verify Proxy NDP and the corresponding neighbour entries.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.