You plugged into a new network and IPv6 is misbehaving
Your Fedora machine says connected, but IPv6-only websites time out. Or you are setting up a home server and need a permanent IPv6 address instead of the rotating one your router hands out. NetworkManager is running in the background, listening for router advertisements and DHCPv6 offers, and making decisions you cannot see. You need to take control of that process without breaking your existing IPv4 routing or leaving your firewall wide open.
How Fedora handles IPv6
Fedora enables IPv6 on every interface by default. The kernel listens for Router Advertisement packets from your local network. When it receives one, it automatically builds a link-local address and, if the router permits, a global address using SLAAC. NetworkManager translates those kernel events into connection profiles stored in /etc/NetworkManager/system-connections/. That profile dictates how the system negotiates addresses, routes, and DNS servers. Think of the profile as a set of instructions for a network handshake. Change the instructions, restart the connection, and the handshake happens again.
NetworkManager caches the last successful configuration. If you edit a profile and the network drops, it will fall back to the cached state until you explicitly bring the connection up. Always verify that your changes apply to the active connection, not just the saved profile.
Run nmcli connection show first. Identify the exact connection name before you modify anything.
Check your current IPv6 state
You need to see what the kernel actually assigned before you change anything. The ip command reads directly from the kernel routing tables, bypassing NetworkManager's abstraction layer. This shows you the raw truth.
ip -6 addr show
# Lists every IPv6 address bound to each interface
# Includes link-local, global, and temporary privacy addresses
ip -6 route show
# Shows how the kernel routes IPv6 traffic
# Reveals default gateways and subnet prefixes
If you see addresses starting with fe80:, those are link-local. They only work on your immediate network segment. Addresses starting with 2001: or 2607: are global and routable on the public internet. Temporary addresses starting with 2001:db8: or similar privacy prefixes are generated by the kernel to avoid tracking.
Check the actual routing table before you assume the address is working. An address without a route is useless.
Assign a static IPv6 address
Static assignment makes sense for servers, network appliances, or containers that need a predictable address. NetworkManager does not write static IPv6 addresses directly to the kernel. It tells the kernel to accept the address and then configures the routing table accordingly. You must specify the prefix length, usually /64 for home networks or /48 for enterprise subnets.
nmcli connection modify "Wired connection 1" \
ipv6.method manual \
ipv6.addresses "2001:db8:1234::10/64" \
ipv6.gateway "2001:db8:1234::1" \
ipv6.dns "2001:4860:4860::8888,2001:4860:4860::8844"
# Sets the connection to manual mode instead of auto-negotiation
# Assigns the global IPv6 address with a /64 subnet mask
# Defines the default gateway for off-link IPv6 traffic
# Provides Google Public DNS servers for IPv6 name resolution
nmcli connection up "Wired connection 1"
# Applies the profile to the active interface
# Forces NetworkManager to re-read the kernel routing table
NetworkManager stores this configuration in a keyfile under /etc/NetworkManager/system-connections/. The file uses an INI-like format. Never edit files in /usr/lib/NetworkManager/system-connections/. Those ship with the package and get overwritten on updates. Always edit /etc/.
Restart the connection after every profile change. The kernel does not watch the config file for changes.
Switch to stateful DHCPv6
SLAAC handles address assignment automatically, but it does not support options like domain search paths or custom DNS in all router implementations. Stateful DHCPv6 solves that. It requires a DHCPv6 server on your network and explicit configuration on the client. NetworkManager treats DHCPv6 as a separate method from SLAAC.
nmcli connection modify "Wired connection 1" \
ipv6.method dhcp \
ipv6.dhcp-timeout 30
# Switches the connection to stateful DHCPv6 mode
# Sets a 30-second timeout for DHCPv6 offer responses
# Prevents the interface from hanging during boot if the server is slow
nmcli connection up "Wired connection 1"
# Reapplies the profile and requests a new DHCPv6 lease
# Drops any previously assigned SLAAC addresses for this connection
DHCPv6 and SLAAC can run simultaneously on the same interface. NetworkManager will prefer the method you explicitly set. If you need both, use ipv6.method auto and configure DHCPv6 options separately through ipv6.dhcp-iaid and ipv6.dhcp-duid. Most home networks do not need this complexity.
Check your router configuration before enabling DHCPv6. A missing DHCPv6 server will leave the interface without a global address.
Add custom IPv6 DNS servers
NetworkManager merges DNS servers from all active connections by default. This can cause resolution to fail if one connection provides a broken IPv6 DNS server. You can override this behavior by appending specific servers to a connection profile. The + prefix tells NetworkManager to append rather than replace.
nmcli connection modify "Wired connection 1" \
+ipv6.dns "2606:4700:4700::1111" \
+ipv6.dns "2606:4700:4700::1001"
# Appends Cloudflare IPv6 DNS servers to the existing list
# Preserves any DNS servers learned via SLAAC or DHCPv6
# Prevents accidental overwrites of router-provided DNS
nmcli connection up "Wired connection 1"
# Reloads the DNS configuration for the active connection
# Triggers systemd-resolved to update its cache
NetworkManager writes the merged DNS list to /run/NetworkManager/resolv.conf. The systemd-resolved service reads that file and handles actual queries. If you see resolution failures, check systemd-resolved status before blaming IPv6.
Flush the DNS cache after changing servers. Stale records cause silent routing failures.
Disable IPv6 safely
Some legacy applications or corporate networks break when IPv6 is present. You can disable IPv6 per-connection or system-wide. Per-connection is safer. It leaves the kernel stack intact for other interfaces while isolating the problematic network.
nmcli connection modify "Wired connection 1" ipv6.method disabled
# Tells NetworkManager to ignore IPv6 on this specific connection
# Leaves the kernel IPv6 stack active for other interfaces
nmcli connection up "Wired connection 1"
# Applies the change and drops any existing IPv6 addresses
System-wide disablement requires a kernel boot parameter. This affects every interface, including loopback and virtual networks. Use it only when a specific application crashes on IPv6 socket initialization.
sudo grubby --update-kernel=ALL --args="ipv6.disable=1"
# Appends the kernel parameter to all installed boot entries
# Forces the kernel to skip IPv6 initialization entirely
# Requires a full reboot to take effect
Reboot after changing kernel parameters. The kernel does not reload boot arguments mid-session.
Configure the firewall for IPv6 services
firewalld manages IPv4 and IPv6 rules in the same zone. When you add a service, it applies to both address families automatically. You do not need separate IPv6 rules. The firewall daemon translates high-level service names into nftables or iptables rules for both stacks.
sudo firewall-cmd --permanent --add-service=http
# Adds the HTTP service to the permanent firewall configuration
# Applies to both IPv4 and IPv6 traffic automatically
sudo firewall-cmd --reload
# Reloads the runtime firewall rules without dropping active connections
# Syncs the permanent configuration to the active nftables table
Always run firewall-cmd --reload after rule changes. The runtime configuration and persistent configuration diverge if you skip this step. Check the active rules with firewall-cmd --list-all to confirm both IPv4 and IPv6 ports are open.
Verify the firewall state before exposing services. A misconfigured zone blocks legitimate traffic silently.
Verify it worked
Run journalctl -xeu NetworkManager to see the exact handshake that just occurred. The x flag adds explanatory context to each log line. The e flag jumps to the end. Most sysadmins type this muscle-memory style when debugging network changes.
journalctl -xeu NetworkManager --since "5 minutes ago"
# Filters logs to the last five minutes
# Shows NetworkManager state changes and DHCP/SLAAC events
# Reveals why an address was accepted or rejected
Look for lines containing IPv6 and state change. If you see device state change: ip-config -> activated (reason 'none'), the connection is fully up. If you see DHCPv6 client failed or SLAAC failed, your router or profile configuration is mismatched.
Test actual connectivity with a tool that respects the IPv6 stack.
ping6 ipv6.google.com
# Sends ICMPv6 echo requests to verify end-to-end routing
# Confirms that the kernel accepts and routes IPv6 packets
curl -6 https://ipv6.google.com
# Forces curl to use IPv6 sockets only
# Validates DNS resolution and TLS handshake over IPv6
Run ping6 and curl -6 together. One tests routing, the other tests application-layer connectivity.
Common pitfalls and error patterns
NetworkManager will refuse to apply a profile if the IPv6 address prefix does not match the interface subnet. You will see this in the journal:
Error: Connection activation failed: IPv6 address 2001:db8:1234::10/64 is not in the subnet of the interface.
The kernel rejects addresses that fall outside the configured prefix. Check your router's subnet assignment and adjust the /64 or /48 suffix accordingly.
Another frequent issue is DNS resolution failing while pings succeed. This usually means systemd-resolved is caching stale IPv4 records or ignoring the IPv6 DNS servers. Restart the resolver service to clear the cache.
sudo systemctl restart systemd-resolved
# Restarts the local DNS caching and forwarding service
# Forces a fresh lookup for all pending queries
# Clears any mismatched IPv4/IPv6 resolution state
SELinux denials rarely block IPv6 directly, but they can stop NetworkManager from writing to /etc/NetworkManager/system-connections/. Check journalctl -t setroubleshoot if your profile changes disappear after a reboot. Read the one-line summary before disabling SELinux.
Check the journal before guessing. The exact error string tells you whether the problem is routing, DNS, or permissions.
When to use which method
Use SLAAC when you want zero-configuration addressing on a home network with a standard router. Use static assignment when you are running a server, NAS, or container that requires a predictable address for remote management. Use DHCPv6 when your network administrator controls DNS search domains or requires centralized lease tracking. Use the per-connection disable method when a single application or network segment breaks with IPv6 present. Use the kernel boot parameter only when legacy software crashes on IPv6 socket initialization and cannot be patched.
Trust the profile. Manual interface edits drift, NetworkManager profiles persist across reboots.