OneLinersCommand workbench
Guides
Networking & DNS / Security

Deploy an IPv6 dual-stack Ubuntu server with safe nftables rules

Add a reviewed global IPv6 address alongside existing IPv4, learn the default route through authenticated network ownership rather than assumption, preserve Neighbor Discovery and Path MTU Discovery, and apply one consistent nftables policy to both families with timed recovery.

130 min10 stepsHigh-impact changeRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 10 steps completed
Goal

Make an Ubuntu Server 24.04 service reachable and diagnosable over IPv4 and IPv6 without creating an unfiltered second perimeter, breaking Router Advertisements or Neighbor Discovery, or blocking ICMPv6 errors required for reliable transport.

Supported environments
  • Ubuntu Server 24.04 LTS
  • Netplan 1.0, 1.1
  • nftables 1.0.9
  • Linux kernel 6.8
Prerequisites
  • Console and two-family test hosts Keep independent console access plus one external IPv4 and one external IPv6 test client. Existing SSH sessions do not prove that new connections or return paths work after policy changes.
  • Provider or network IPv6 contract Obtain the assigned prefix, host address, prefix length, router-advertisement policy, link MTU, reverse DNS ownership, and whether a static link-local gateway is required. Do not guess a gateway from the prefix.
  • One firewall owner Inventory nftables, iptables compatibility rules, UFW, firewalld, cloud-init, container runtimes, and provider firewalls. This procedure may replace the host nftables ruleset only when ownership and required services are fully known.sudo nft list ruleset; sudo iptables-save; systemctl is-active ufw firewalld
  • Address and DNS reservation Reserve {{ipv4Address}} and {{ipv6Address}}, confirm no duplicate, define forward and reverse DNS timing, and document the approved management prefixes {{managementV4}} and {{managementV6}}.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A dual-stack Netplan interface with the existing IPv4 contract and one reserved global IPv6 address, while accepting the approved router's RAs for default route and link parameters.
  • A single nftables `inet` input policy that applies equivalent service exposure to IPv4 and IPv6 instead of leaving IPv6 as an unfiltered bypass.
  • Protocol-safe ICMP rules that admit essential IPv6 errors, Neighbor Discovery, Router Advertisements, and Multicast Listener Discovery while validating local ND hop limit and source scope.
  • A timed firewall rollback, two-family external validation, PMTU tests, and post-reboot evidence suitable for a production change record.
Observable outcome
  • The interface holds preferred IPv4 and IPv6 addresses; DAD is complete; an RA default points to the expected link-local router; neighbor state is healthy.
  • New SSH connections are accepted only from approved IPv4 and IPv6 management prefixes, while unrelated inbound traffic hits default-drop policy.
  • IPv6 DNS, application health, tracepath, and 1280-byte-minimum payload tests succeed without hiding ICMPv6 Packet Too Big.
  • A console-recoverable firewall and Netplan configuration reloads after reboot with no drift or unexplained failed units.

Architecture

How the parts fit together

One physical interface carries independent IPv4 and IPv6 Layer-3 state. IPv4 uses the existing explicit gateway, while IPv6 combines a reserved global address with Router Advertisements from an expected link-local router. IPv6 Neighbor Discovery resolves local peers and performs Duplicate Address Detection; ICMPv6 also carries routing errors and Path MTU feedback. A single nftables `inet` table evaluates both families, with family-specific address restrictions and protocol-specific control traffic. Provider filtering remains an outer layer, and a timed local rollback remains independent of both stacks.

{{interfaceName}}Dual-stack link holding link-local, global IPv6, and existing IPv4 state.
Router AdvertisementAdvertises IPv6 default-router lifetime, on-link prefix information, and link MTU from a link-local router.
Neighbor DiscoveryProvides address resolution, reachability detection, router discovery, redirects, and duplicate-address detection for IPv6.
Netplan/systemd-networkdDeclares addresses, IPv4 route, resolver, and RA acceptance and renders them to the backend.
nftables inet tableEnforces one stateful input policy across IPv4 and IPv6 while preserving required control protocols.
Provider firewallLimits upstream reachability but does not replace host policy, ND, or local service binding review.
External test clientsProve independent DNS, connection establishment, return routing, and payload delivery over each family.
  1. The host performs DAD for {{ipv6Address}} and listens for RAs sent from a link-local router with IPv6 hop limit 255.
  2. The RA creates or refreshes the default route and prefix/MTU state; ND resolves the router's link-layer address.
  3. An inbound packet reaches provider controls and then the nftables input chain, where invalid traffic is dropped and established/related state is accepted.
  4. New SSH is admitted only when the packet family and source match the corresponding management prefix; other new service traffic needs explicit rules.
  5. ICMPv6 errors and local ND/RA/MLD pass their narrow protocol rules so routing, neighbor reachability, and PMTU remain functional.
  6. Outbound traffic uses the selected family and source. Packet Too Big messages update PMTU instead of allowing large TCP/UDP flows to stall silently.

Assumptions

  • The server is an IPv6 host, not a router. `net.ipv6.conf.all.forwarding` remains 0; routers require RA, forwarding, filtering, and anti-spoofing decisions beyond this guide.
  • The network intentionally supplies RAs from {{ipv6GatewayLinkLocal}} and the operator has verified its link-layer identity. Untrusted LANs need RA Guard and switch-layer controls.
  • {{ipv6Address}} is a documentation example replaced by a real allocated address; the prefix length and route policy come from the network provider.
  • IPv4 remains operational during first IPv6 activation and provides an independent management/recovery path.
  • One local firewall manager owns the host. Container runtimes or virtualization may add rules and require an integrated policy rather than `flush ruleset`.
  • The application binds safely to IPv6 or wildcard sockets, has identical authentication/TLS behavior on both families, and is included in external health tests.
  • The provider permits essential ICMPv6 and does not publish an AAAA record before host and application acceptance.

Key concepts

Dual stack
Running IPv4 and IPv6 simultaneously. Each family has its own addresses, routes, DNS records, firewall matching, failure modes, and possible client preference.
Link-local address
An IPv6 address in `fe80::/10` valid only on one link. Routers commonly use link-local source addresses for RAs and default next hops.
Router Advertisement
ICMPv6 message type 134 sent with hop limit 255 that can communicate router lifetime, prefixes, flags, and MTU to hosts.
Neighbor Discovery
ICMPv6 mechanisms replacing ARP and adding router/prefix discovery, reachability detection, redirects, and Duplicate Address Detection.
Duplicate Address Detection
A host verifies an IPv6 address is unused before marking it preferred. A `tentative` or `dadfailed` address must not serve traffic.
Path MTU Discovery
The source learns the smallest supported packet size along a path. IPv6 relies on ICMPv6 Packet Too Big because routers do not fragment transit packets.
inet nftables family
A table family whose chains see both IPv4 and IPv6 packets, enabling shared state policy plus explicit family-specific matches.
Conntrack related
State assigned to control/error packets associated with an existing flow. Explicit ICMPv6 rules remain useful for protocol clarity and new control traffic.

Before you copy

Values used in this guide

{{interfaceName}}

Existing physical or logical interface receiving the dual-stack configuration.

Example: ens3
{{ipv4Address}}

Existing reserved IPv4 address and prefix retained during rollout.

Example: 198.51.100.20/24
{{ipv4Gateway}}

Existing IPv4 default gateway.

Example: 198.51.100.1
{{ipv6Address}}

Reserved global IPv6 address and prefix assigned to the server.

Example: 2001:db8:120::20/64
{{ipv6HostOnly}}

Address portion of ipv6Address used in source-route tests.

Example: 2001:db8:120::20
{{ipv6GatewayLinkLocal}}

Expected link-local IPv6 router address learned through RA.

Example: fe80::1
{{dnsV4}}

Approved IPv4 recursive resolver.

Example: 192.0.2.53
{{dnsV6}}

Approved IPv6 recursive resolver.

Example: 2001:db8:53::53
{{managementV4}}

Narrow IPv4 administrator source prefix allowed to initiate SSH.

Example: 198.51.100.32/28
{{managementV6}}

Narrow IPv6 administrator source prefix allowed to initiate SSH.

Example: 2001:db8:900:10::/64
{{remoteOperatorV4}}

Current operator IPv4 address used to preserve the return route.

Example: 198.51.100.35
{{ipv6Probe}}

Stable external IPv6 destination approved for route, ping, and PMTU tests.

Example: 2001:4860:4860::8888
{{probeHostname}}

Controlled hostname with reviewed A/AAAA records and health endpoint.

Example: dualstack.example.net
{{tryTimeout}}

Seconds before Netplan automatically restores the previous network state.

Example: 120
{{firewallRollbackMinutes}}

Minutes before the transient unit restores the saved firewall unless deliberately cancelled.

Example: 3

Security and production boundaries

  • IPv6 is not protected by an IPv4-only firewall or NAT assumption. Inventory wildcard listeners and apply equivalent authorization, TLS, logging, rate, and patch policy to both families.
  • Do not block all ICMPv6. Neighbor Discovery and Router Advertisements are local-link control planes, while Packet Too Big, Destination Unreachable, Time Exceeded, and Parameter Problem are required for robust routing and PMTU.
  • Hop limit 255 and link-local source scope help constrain ND/RA arriving at the host but do not replace switch RA Guard, port security, and trusted router ownership.
  • Router Advertisements can change route and MTU behavior. Monitor router MAC, prefix, lifetime, flags, and unexpected RAs on each server network.
  • The default output accept policy preserves application behavior but permits egress. Environments requiring egress filtering must inventory DNS, NTP, package, monitoring, backup, and application destinations first.
  • AAAA publication may immediately shift clients to IPv6. Stage DNS with a controlled hostname and monitor family-specific failures before production publication.

Stop before continuing if

  • Stop if multiple unexpected RAs, prefixes, router MACs, or MTUs appear, or if the expected RA source is not link-local with hop limit 255.
  • Do not confirm Netplan while the global address is tentative/dadfailed, the RA default is absent, or the operator IPv4 route changes.
  • Stop if any other firewall manager or container platform owns rules that `flush ruleset` would remove.
  • Do not cancel the firewall rollback until fresh IPv4 and IPv6 management sessions, DNS, application, ND/RA, and PMTU checks pass.
  • Abort publication of AAAA when TLS identity, authentication, logging, or behavior differs between protocol families.
  • Do not close the change while Packet Too Big is blocked, tracepath shows a PMTU black hole, or post-reboot route/firewall state drifts.
01

verification

Capture both protocol stacks and current firewall ownership

read-only

Record addresses by scope, all route tables and policy rules, IPv6 neighbors, resolver links, forwarding and RA sysctls, listeners, current firewall rules, provider security groups, and the exact operator return route over IPv4.

Why this step matters

IPv6 can already be active through link-local addresses and wildcard listeners even when no global address is configured. A dual-stack change therefore starts by identifying actual exposure and policy ownership, not by assuming IPv6 is absent.

What to understand

Inspect all route tables, rules, neighbors, listeners, and sysctls for both families. A wildcard `[::]` socket may accept IPv4 too depending on application settings.

Forwarding changes RA acceptance semantics in Linux; this server remains a host with forwarding 0 and accept_ra 1.

Capture provider firewall and container-added rules because replacing the local ruleset can remove hidden dependencies.

System changes

  • No persistent change; reads addresses, routes, rules, neighbors, sysctls, sockets, and firewall state.

Syntax explained

ip -6 route show table all
Displays local, main, and policy-table IPv6 routes.
ip -6 neigh
Shows IPv6 neighbor cache including router and reachability state.
ss -lntup
Shows TCP/UDP listeners, wildcard family, and owning process.
nft list ruleset
Shows the complete live nftables policy and counters.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

ip -brief address; ip -4 route show table all; ip -6 route show table all; ip -6 rule show; ip -6 neigh show; sysctl net.ipv6.conf.all.disable_ipv6 net.ipv6.conf.all.forwarding net.ipv6.conf.{{interfaceName}}.accept_ra; sudo ss -lntup; sudo nft list ruleset; ip route get {{remoteOperatorV4}}
Example output / evidence
ens3             UP             198.51.100.20/24 fe80::5054:ff:fe11:2233/64
local ::1 dev lo table local proto kernel metric 0 pref medium
fe80::/64 dev ens3 proto kernel metric 256 pref medium
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.all.forwarding = 0
net.ipv6.conf.ens3.accept_ra = 1
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*

Checkpoint: Approve current ownership

Continue whenIPv4 return route, IPv6 sysctls, wildcard listeners, provider policy, and one local firewall owner are documented.

Stop whenAn unowned rule manager, forwarding role, listener, or current route remains.

If this step fails

UFW is inactive but nftables already has container chains.

Likely causeDocker, Kubernetes, libvirt, or another runtime owns packet filtering/NAT directly.

Safe checks
  • sudo nft list ruleset
  • systemctl status docker containerd libvirtd --no-pager
  • sudo iptables-save

ResolutionIntegrate with the runtime's supported firewall model or isolate the host; do not flush rules it requires.

Security notes

  • Treat firewall and listener output as sensitive exposure inventory.

Alternatives

  • Use a centralized asset and policy snapshot corroborated by local kernel state.

Stop conditions

  • No replacement ruleset while ownership is shared.
02

verification

Observe Router Advertisements and link-local Neighbor Discovery

read-only

Before assigning a global address, observe a bounded sample of Router Advertisements and query networkd state. Verify the router's link-local source, advertised prefix, default-router lifetime, flags, and MTU against the network contract.

Why this step matters

Router Advertisements define default-router lifetime, prefixes, flags, and sometimes MTU. Observing them before configuration verifies the network contract and identifies rogue or conflicting routers.

What to understand

RA must arrive from link-local scope with hop limit 255; record source MAC through neighbor or packet metadata.

A bounded capture of type 134 avoids collecting unrelated payloads and protects privacy.

Multiple routers can be valid, but their priorities, lifetimes, prefixes, and failure behavior must be intentional.

System changes

  • No persistent change; captures at most two ICMPv6 Router Advertisements and reads link state.

Syntax explained

timeout 20
Bounds capture duration even if no RA arrives.
icmp6 && ip6[40] == 134
Selects ICMPv6 Router Advertisement packets without broad traffic capture.
-c 2
Stops after two packets.
ip -6 route show proto ra
Displays routes currently learned from Router Advertisements.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo timeout 20 tcpdump -ni {{interfaceName}} -vv 'icmp6 && ip6[40] == 134' -c 2; networkctl status {{interfaceName}} --no-pager; ip -6 route show proto ra
Example output / evidence
10:12:04.442181 IP6 (hlim 255, next-header ICMPv6) fe80::1 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 88
	hop limit 64, Flags [none], pref medium, router lifetime 1800s
	prefix info option: 2001:db8:120::/64, Flags [onlink, auto], valid time 86400s, pref. time 14400s
	mtu option (5), length 8 (1): 1500
Gateway: fe80::1
2001:db8:120::/64 dev ens3 proto ra metric 100 pref medium

Checkpoint: Approve router control plane

Continue whenExpected link-local router/MAC advertises approved prefix, lifetime, flags, and MTU with hop limit 255.

Stop whenNo RA, unexpected router/prefix, zero lifetime, wrong MTU, or inconsistent repeated packets appear.

If this step fails

Two routers advertise conflicting prefixes or MTUs.

Likely causeRogue RA, misconfigured redundant routers, wrong VLAN, or overlapping migration.

Safe checks
  • sudo tcpdump -ni ens3 -e -vv 'icmp6 && ip6[40] == 134' -c 6
  • ip -6 neigh show dev ens3

ResolutionStop host rollout and have network operations identify each switch port/router. Use RA Guard and correct the Layer-2 domain before accepting either.

Security notes

  • Packet capture is metadata-scoped, but router addresses and MACs remain sensitive.

Alternatives

  • Use trusted switch RA-guard telemetry plus host observation.

Stop conditions

  • Unattributed RA ends the change.
03

command

Create protected Netplan and firewall recovery artifacts

caution

Archive Netplan, the live nftables ruleset, `/etc/nftables.conf`, routes, addresses, and sysctls. Create and test a console rollback script before either network or firewall changes.

Why this step matters

A timed firewall recovery is useful only when the saved ruleset parses and Netplan inputs are complete. Protected artifacts turn console access into a deterministic restoration path.

What to understand

Save the live ruleset in nft syntax and validate it immediately; an invalid backup cannot be trusted by a future rollback unit.

Copy the entire Netplan directory with metadata, not just the new file.

Capture both route families and address state because DNS or application symptoms after rollback may be unrelated to firewall policy.

System changes

  • Creates a root-only recovery directory containing Netplan, firewall, route, and address evidence.

Syntax explained

nft list ruleset
Serializes the live nftables configuration.
nft --check --file
Parses the saved ruleset without applying it.
cp --archive
Preserves input file metadata for restoration.
Command
sudo install -d -m 0700 /root/dual-stack-before-wyd302 && sudo cp --archive /etc/netplan /root/dual-stack-before-wyd302/ && sudo nft list ruleset | sudo tee /root/dual-stack-before-wyd302/ruleset.nft >/dev/null && sudo cp --archive /etc/nftables.conf /root/dual-stack-before-wyd302/nftables.conf 2>/dev/null || true; ip address | sudo tee /root/dual-stack-before-wyd302/ip-address.txt >/dev/null; ip route show table all | sudo tee /root/dual-stack-before-wyd302/routes-v4.txt >/dev/null; ip -6 route show table all | sudo tee /root/dual-stack-before-wyd302/routes-v6.txt >/dev/null; sudo nft --check --file /root/dual-stack-before-wyd302/ruleset.nft
Example output / evidence
/etc/netplan -> /root/dual-stack-before-wyd302/netplan
/etc/nftables.conf -> /root/dual-stack-before-wyd302/nftables.conf
ruleset.nft: syntax OK
-rw------- 1 root root 1842 Jul 28 10:18 /root/dual-stack-before-wyd302/ruleset.nft

Checkpoint: Prove console rollback assets

sudo nft --check --file /root/dual-stack-before-wyd302/ruleset.nft; sudo find /root/dual-stack-before-wyd302 -maxdepth 3 -type f -ls

Continue whenSaved ruleset parses and all current Netplan/firewall inputs are root-only and present.

Stop whenThe current ruleset cannot be serialized or any recovery file is missing.

If this step fails

Saved ruleset fails `nft --check`.

Likely causeRuntime output references transient sets, compatibility syntax, or objects not reproducible from the serialized file.

Safe checks
  • sudo nft -nn list ruleset
  • sudo nft --handle list ruleset

ResolutionDo not schedule it as rollback. Use the firewall owner's supported backup/export and test restoration in a console-safe environment.

Security notes

  • The recovery directory reveals firewall policy and internal routes; keep mode 0700.

Alternatives

  • Use a signed configuration-management version plus a tested local restore script.

Stop conditions

  • No firewall apply without a parseable previous policy.
04

config

Add static dual-stack addressing while accepting approved RAs

danger

Retain the existing IPv4 address and default route, add the reserved IPv6 address, and explicitly accept Router Advertisements for the IPv6 default route and link parameters. Do not add a guessed global-address gateway; IPv6 routers normally advertise from link-local addresses.

Why this step matters

Adding IPv6 alongside unchanged IPv4 provides an independent recovery path and lets the server use the provider's legitimate RA for default route/MTU rather than inventing IPv6 gateway conventions.

What to understand

Static global address supports stable DNS and access, while `accept-ra: true` allows default route and prefix/MTU information.

DHCPv6 is disabled because the stated network contract does not use it; enable only when the provider documents address or option service.

The IPv4 route remains explicit and unchanged. DNS includes one resolver per family to test reachability independently.

System changes

  • Creates Netplan policy adding global IPv6 and RA acceptance to the existing IPv4 interface when applied.

Syntax explained

dhcp6: false
Does not request DHCPv6 address/options in this explicit contract.
accept-ra: true
Allows systemd-networkd to process Router Advertisements for this host interface.
addresses
Assigns both reserved IPv4 and IPv6 prefixes to the same link.
nameservers addresses
Provides approved recursive resolvers over both families.
File /etc/netplan/60-oneliners-dual-stack.yaml
Configuration
Fill variables0/6 ready

Values stay on this page and are never sent or saved.

network:
  version: 2
  renderer: networkd
  ethernets:
    {{interfaceName}}:
      dhcp4: false
      dhcp6: false
      accept-ra: true
      addresses: [{{ipv4Address}}, {{ipv6Address}}]
      routes:
        - to: default
          via: {{ipv4Gateway}}
      nameservers:
        addresses: [{{dnsV4}}, {{dnsV6}}]
Example output / evidence
network:
  version: 2
  renderer: networkd
  ethernets:
    ens3:
      dhcp4: false
      dhcp6: false
      accept-ra: true
      addresses: [198.51.100.20/24, 2001:db8:120::20/64]
      routes: [{to: default, via: 198.51.100.1}]
      nameservers:
        addresses: [192.0.2.53, 2001:db8:53::53]

Checkpoint: Review dual-stack declaration

sudo netplan get network.ethernets.{{interfaceName}}

Continue whenExisting IPv4 is unchanged, exact reserved IPv6 is present, RA is true, DHCPv6 is false, and resolvers are approved.

Stop whenIPv4 route changes, an IPv6 gateway is guessed, or address/prefix differs.

If this step fails

Netplan reports a conflicting default route.

Likely causeAnother YAML file already declares an IPv6 route or RA override.

Safe checks
  • sudo netplan get
  • sudo grep -R --line-number 'accept-ra\|2001:' /etc/netplan /lib/netplan /run/netplan 2>/dev/null

ResolutionMerge ownership into one deliberate interface declaration and preserve only the network-approved route source.

Security notes

  • Do not publish the AAAA record during this configuration step.

Alternatives

  • Use a provider-documented static link-local next hop if RAs are intentionally absent.

Stop conditions

  • Any ambiguous gateway or prefix blocks generation.
05

command

Generate and apply the addresses through Netplan try

danger

Validate the merged YAML and generated networkd unit, then use `netplan try` from console with the original IPv4 SSH session open. Do not confirm until Duplicate Address Detection completes, an RA default exists, and fresh IPv4 plus IPv6 sessions return correctly.

Why this step matters

Netplan generation catches syntax safely, and `try` protects the existing IPv4 management path while live IPv6 address, DAD, and route behavior are evaluated.

What to understand

Set mode 0600, inspect debug rendering, and initiate from console or a stable terminal multiplexer.

DAD can take time; do not confirm while the address is tentative. Open fresh sessions instead of trusting existing TCP.

Automatic rollback may not repair an upstream switch/provider mistake, so console remains mandatory.

System changes

  • Temporarily applies the dual-stack address/resolver policy and retains it only after confirmation.

Syntax explained

netplan --debug generate
Renders backend state without applying it and prints decisions.
netplan try
Applies live state under an automatic rollback confirmation guard.
--timeout {{tryTimeout}}
Sets the rollback countdown.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo chmod 0600 /etc/netplan/60-oneliners-dual-stack.yaml && sudo netplan --debug generate && sudo netplan try --timeout {{tryTimeout}}
Example output / evidence
** (generate:5118): DEBUG: ens3: adding address 2001:db8:120::20/64
Do you want to keep these settings?
Press ENTER before the timeout to accept the new configuration
Changes will revert in 120 seconds
Configuration accepted.

Checkpoint: Preserve IPv4 while proving IPv6

ip route get {{remoteOperatorV4}}; ip -6 address show dev {{interfaceName}}

Continue whenFresh IPv4 management works through the original route and {{ipv6Address}} becomes preferred without DAD failure.

Stop whenIPv4 route/session fails, IPv6 remains tentative/dadfailed, or networkd reports errors.

If this step fails

IPv4 disconnects although only IPv6 was added.

Likely causeThe new YAML replaced rather than merged existing interface values, or default route/DNS ownership changed.

Safe checks
  • sudo netplan get
  • ip -4 address show dev ens3
  • ip -4 route show

ResolutionDo not confirm; allow rollback, restore the full interface declaration, and regenerate offline.

Security notes

  • Keep provider IPv6 ingress closed until host firewall is accepted.

Alternatives

  • Apply in a clone with console first, but still use try on production.

Stop conditions

  • Any regression in the existing family ends the trial.
06

verification

Prove DAD, RA, ND, routes, DNS, and source selection

read-only

Check that the global address is not tentative or duplicate, the default route is learned from the expected link-local router, the gateway has a reachable neighbor entry, DNS works over both families, and explicit route queries choose the intended sources.

Why this step matters

IPv6 success is a chain of address state, router discovery, neighbor reachability, source selection, DNS, and external routing. Testing each layer prevents a ping-only false positive.

What to understand

The address must be `scope global` and lack tentative/dadfailed flags. The default should reference the expected link-local router with RA protocol.

A reachable/stale router neighbor is normal; FAILED or INCOMPLETE indicates ND trouble.

Route-get with explicit source proves the intended egress. Resolver query should return controlled records, not merely reach a DNS server.

System changes

  • No persistent change; reads IPv6 state and sends limited gateway, DNS, and external probes.

Syntax explained

ip -6 address
Shows scope, prefix, lifetimes, and DAD flags.
ip -6 route/neigh
Shows default router, RA metadata, and neighbor reachability.
resolvectl query
Tests resolver selection and DNS result through systemd-resolved.
ping -6 gateway%interface
Scopes a link-local destination to the correct interface.
Command
Fill variables0/5 ready

Values stay on this page and are never sent or saved.

ip -6 address show dev {{interfaceName}}; ip -6 route show; ip -6 neigh show dev {{interfaceName}}; ip -6 route get {{ipv6Probe}} from {{ipv6HostOnly}}; resolvectl status {{interfaceName}} --no-pager; resolvectl query {{probeHostname}}; ping -6 -c 3 {{ipv6GatewayLinkLocal}}%{{interfaceName}}; ping -6 -c 3 {{ipv6Probe}}
Example output / evidence
inet6 2001:db8:120::20/64 scope global
   valid_lft forever preferred_lft forever
default via fe80::1 dev ens3 proto ra metric 100 expires 1762sec pref medium
fe80::1 dev ens3 lladdr 00:11:22:33:44:55 router REACHABLE
2001:4860:4860::8888 from 2001:db8:120::20 via fe80::1 dev ens3 proto ra src 2001:db8:120::20 metric 100 pref medium
3 packets transmitted, 3 received, 0% packet loss

Checkpoint: Accept IPv6 host state

Continue whenPreferred global address, expected RA default and neighbor, correct source route, both resolvers, gateway, and external probe succeed.

Stop whenAny address, router, source, DNS, or reachability result differs.

If this step fails

The router neighbor oscillates between INCOMPLETE and FAILED.

Likely causeND packets are blocked, wrong VLAN, duplicate router address, or Layer-2 reachability is broken.

Safe checks
  • sudo tcpdump -ni ens3 -vv 'icmp6 && (ip6[40] == 135 or ip6[40] == 136)' -c 20
  • ip -6 neigh show dev ens3

ResolutionCorrect Layer-2/ND before firewall deployment; do not add a static neighbor to conceal broken discovery.

Security notes

  • Probe only approved destinations and avoid broad IPv6 scans.

Alternatives

  • Use provider looking glass and controlled remote probe for external path confirmation.

Stop conditions

  • No firewall rollout over unstable ND or routing.
07

config

Write one inet firewall policy for IPv4 and IPv6

danger

Create an `inet` table with default-drop input, early loopback and established handling, explicit IPv4 control errors, essential IPv6 errors, link-local ND/RA/MLD with hop-limit validation, bounded echo, and source-restricted SSH for both management prefixes.

Why this step matters

An `inet` table expresses a consistent stateful perimeter while preserving protocol traffic that IPv6 requires to resolve neighbors, learn routers, and discover PMTU.

What to understand

Loopback and established/related traffic are accepted before new-connection rules. Invalid conntrack state is dropped.

Critical IPv6 errors are allowed globally; local ND/RA/MLD is restricted to link-local source and hop limit 255.

Echo request is separately rate-limited. Management SSH requires family-specific source prefixes.

`flush ruleset` is safe only after one-owner inventory and a tested saved ruleset; container hosts need an integrated alternative.

System changes

  • Replaces the persistent nftables configuration; live state changes only when the file is loaded.

Syntax explained

table inet
Processes both IPv4 and IPv6 in shared chains.
policy drop
Rejects unmatched inbound packets by default.
ct state established,related
Permits return traffic and related errors for accepted flows.
meta l4proto ipv6-icmp
Matches ICMPv6 even with normal IPv6 extension headers.
ip6 hoplimit 255 / fe80::/10
Constrains local ND/RA to on-link semantics.
limit rate
Bounds echo and drop logging volume without rate-limiting essential errors.
File /etc/nftables.conf
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

#!/usr/sbin/nft -f
flush ruleset

table inet oneliners_host {
  chain input {
    type filter hook input priority filter; policy drop;
    iifname "lo" accept
    ct state invalid drop
    ct state established,related accept

    ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
    meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
    ip6 saddr fe80::/10 ip6 hoplimit 255 meta l4proto ipv6-icmp icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, mld-listener-query, mld-listener-report, mld-listener-reduction, mld2-listener-report } accept

    ip protocol icmp icmp type echo-request limit rate 10/second accept
    meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 10/second accept

    ip saddr {{managementV4}} tcp dport 22 accept
    ip6 saddr {{managementV6}} tcp dport 22 accept
    limit rate 5/second log prefix "nft-input-drop " level info
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}
Example output / evidence
table inet oneliners_host {
  chain input {
    type filter hook input priority filter; policy drop;
    iifname "lo" accept
    ct state invalid drop
    ct state established,related accept
    ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
    meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
    ip6 saddr fe80::/10 ip6 hoplimit 255 meta l4proto ipv6-icmp icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, mld-listener-query, mld-listener-report, mld-listener-reduction, mld2-listener-report } accept
    ip protocol icmp icmp type echo-request limit rate 10/second accept
    meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 10/second accept
    ip saddr 198.51.100.32/28 tcp dport 22 accept
    ip6 saddr 2001:db8:900:10::/64 tcp dport 22 accept
    limit rate 5/second log prefix "nft-input-drop " level info
  }
  chain output { type filter hook output priority filter; policy accept; }
}

Checkpoint: Peer-review both perimeters

sudo nft --check --file /etc/nftables.conf

Continue whenPolicy parses, required services exist for both families, ICMPv6 types are explicit, and no existing firewall owner is lost.

Stop whenAny syntax, owner, service, management source, or ICMPv6 requirement is missing.

If this step fails

nft rejects an ICMPv6 type name.

Likely causeInstalled nftables version uses a different symbolic name or set syntax.

Safe checks
  • nft --version
  • nft describe icmpv6 type

ResolutionUse the installed official symbolic values shown by `nft describe`, retest with `--check`, and preserve the intended numeric protocol semantics.

Security notes

  • Do not accept all link-local IPv6 traffic; match ICMPv6 type, source scope, and hop limit.

Alternatives

  • Use Ubuntu's maintained nftables framework or one configuration manager that preserves equivalent protocol semantics.

Stop conditions

  • No load of an unvalidated full ruleset.
08

command

Validate and apply nftables behind a timed rollback

danger

Parse the full file without changing state, schedule a root transient unit to restore the saved ruleset after {{firewallRollbackMinutes}}, then load the new policy. Cancel the rollback only after fresh SSH, RA/ND, DNS, PMTU, and external service tests pass over both families.

Why this step matters

A parse check plus independent timed restoration limits lockout when a syntactically valid ruleset has incorrect semantics. Fresh two-family sessions must prove success before rollback is cancelled.

What to understand

`systemd-run --on-active` schedules restoration through PID 1, independent of the SSH shell.

Confirm the timer exists before loading. Keep console open and know the immediate manual restore command.

After load, inspect counters and test new sessions from allowed and denied sources; only then stop the timer.

System changes

  • Schedules a transient rollback and replaces the live nftables ruleset with the reviewed policy.

Syntax explained

nft --check --file
Parses the full policy without changing the kernel ruleset.
systemd-run --on-active
Creates a transient timer/service that restores the saved ruleset later.
nft --file
Atomically interprets the policy file into the live ruleset as one batch.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo nft --check --file /etc/nftables.conf && sudo systemd-run --unit=nft-rollback-wyd302 --on-active={{firewallRollbackMinutes}}m /usr/sbin/nft --file /root/dual-stack-before-wyd302/ruleset.nft && sudo nft --file /etc/nftables.conf && sudo nft list ruleset
Example output / evidence
/etc/nftables.conf: syntax OK
Running timer as unit: nft-rollback-wyd302.timer
Will run service as unit: nft-rollback-wyd302.service
table inet oneliners_host {
	chain input {
		type filter hook input priority filter; policy drop;
		ct state established,related accept
		meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
	}
}

Checkpoint: Prove policy before cancelling rollback

systemctl status nft-rollback-wyd302.timer --no-pager; sudo nft list ruleset

Continue whenRollback timer is pending, new table is loaded, fresh approved IPv4/IPv6 SSH works, denied tests fail, and ND/RA remains healthy.

Stop whenTimer is absent, console is lost, or any required two-family test fails.

If this step fails

The ruleset loads and immediately drops IPv6 SSH.

Likely causeThe external client source is outside {{managementV6}}, service uses another port, or rule ordering/source syntax is wrong.

Safe checks
  • sudo nft list chain inet oneliners_host input -a
  • sudo tcpdump -ni ens3 -vv 'ip6 and tcp port 22' -c 10
  • ip -6 route get 2001:db8:900:10::35

ResolutionLet rollback run, verify the real client prefix and listener, correct offline, and repeat. Do not widen SSH to all IPv6.

Security notes

  • Never cancel the timer from habit; cancellation is a formal acceptance action.

Alternatives

  • Use a firewall manager with transactional confirm/rollback semantics.

Stop conditions

  • One failed required test leaves rollback armed.
09

verification

Verify ICMPv6 and Path MTU Discovery end to end

read-only

Run tracepath and controlled no-fragment-size probes from both sides while observing Packet Too Big and error counters. A successful TCP handshake is insufficient because an ICMPv6 black hole often appears only after larger payloads.

Why this step matters

Path MTU failures often pass ping and TCP handshake tests but stall real payloads. Tracepath and controlled IPv6 sizes verify that essential ICMPv6 feedback survives the host and upstream policy.

What to understand

IPv6 guarantees a minimum link MTU of 1280; a 1232-byte echo payload plus IPv6/ICMPv6 headers exercises that floor without fragmentation assumptions.

Tracepath reports observed PMTU and hop progression without requiring broad packet capture.

Test from both directions and through real tunnels/load balancers because PMTU is path-specific.

System changes

  • No persistent change; sends bounded ICMPv6 and UDP-style trace probes and reads firewall counters.

Syntax explained

tracepath -6 -n
Discovers IPv6 path and PMTU without reverse DNS.
ping -6 -s 1232
Sends a payload that produces a 1280-byte IPv6 packet.
ip -6 route get
Shows selected source, next hop, interface, and cached route metrics.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

tracepath -6 -n {{ipv6Probe}}; ping -6 -c 3 -s 1232 {{ipv6Probe}}; sudo nft list chain inet oneliners_host input; ip -6 route get {{ipv6Probe}}
Example output / evidence
 1?: [LOCALHOST]                        0.018ms pmtu 1500
 1:  2001:db8:120::1                     0.481ms
 2:  2001:db8:ffff::1                    1.842ms
 3:  2001:4860:4860::8888               12.091ms reached
     Resume: pmtu 1500 hops 3 back 3
3 packets transmitted, 3 received, 0% packet loss
2001:4860:4860::8888 from 2001:db8:120::20 via fe80::1 dev ens3 proto ra src 2001:db8:120::20 metric 100

Checkpoint: Accept payload path

Continue whenTrace reaches target with explained PMTU and repeated minimum-MTU probes return without loss from both directions.

Stop whenHandshake-only success, PMTU collapse, Packet Too Big drop, or unexplained loss appears.

If this step fails

Tracepath reports `pmtu 1280` unexpectedly on a 1500 network.

Likely causeA tunnel or intermediate link is smaller, ICMP feedback reports that value, or upstream policy is compensating for a broken path.

Safe checks
  • ip -details link show ens3
  • sudo tcpdump -ni any -vv 'icmp6 && ip6[40] == 2' -c 10
  • ip -6 route get 2001:4860:4860::8888

ResolutionIdentify the actual constrained segment and set/tunnel MTU consistently; retain Packet Too Big rather than assuming 1500.

Security notes

  • Avoid high-rate or oversized probes; use controlled destinations.

Alternatives

  • Use application-layer PLPMTUD-capable tests for protocols that do not rely solely on ICMP feedback.

Stop conditions

  • Do not publish IPv6 service with a known PMTU black hole.
10

verification

Prove external reachability and reboot persistence

read-only

From independent IPv4 and IPv6 clients, establish fresh SSH and application connections, verify forward and reverse DNS as planned, then reboot with console open. After boot, recheck RA default, neighbors, nftables, services, and both external paths.

Why this step matters

External clients prove upstream routing and provider policy, while reboot proves persistent Netplan, nftables enablement, RA timing, and listener behavior rather than transient runtime success.

What to understand

Establish new connections over both families and verify the application response, TLS identity, and logs.

After reboot, RA may take seconds to arrive; measure rather than hard-code a global gateway workaround.

Confirm nftables is enabled, rule counters/logs are sane, and Netplan reports no drift before publishing DNS broadly.

System changes

  • Reboots the host and reloads persistent network plus firewall policy.

Syntax explained

systemctl is-enabled nftables
Verifies persistent ruleset loading is configured for boot.
netplan status --diff
Detects post-boot declaration/live divergence.
ss -lntup
Confirms service bindings over both families after boot.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

systemctl is-enabled nftables; systemctl --failed --no-pager; ip -6 route show default; ip -6 neigh show dev {{interfaceName}}; sudo nft list ruleset; sudo netplan status --diff; ss -lntup
Example output / evidence
enabled
  UNIT LOAD ACTIVE SUB DESCRIPTION
0 loaded units listed.
default via fe80::1 dev ens3 proto ra metric 100 expires 1788sec pref medium
fe80::1 dev ens3 lladdr 00:11:22:33:44:55 router REACHABLE
table inet oneliners_host { ... }
Difference: none
tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=912,fd=4))

Checkpoint: Close dual-stack acceptance

Continue whenFresh external IPv4/IPv6 service and SSH tests pass, RA/ND/PMTU are healthy, nftables persists, no failed units or drift remain.

Stop whenEither family, firewall, listener, route, DNS, or service differs after boot.

If this step fails

IPv6 is unavailable for the first minute after reboot.

Likely causeRA interval, DAD, networkd ordering, switch control plane, or nftables boot ordering delays readiness.

Safe checks
  • journalctl -b -u systemd-networkd -u nftables --no-pager
  • ip -6 address show dev ens3
  • ip -6 route show default

ResolutionMeasure each readiness transition, correct ordering/RA availability, and keep the service unadvertised until its health gate includes IPv6 readiness.

Security notes

  • Keep AAAA DNS staged until external negative and positive policy tests pass.

Alternatives

  • Use a load balancer health gate that advertises IPv6 only after host readiness.

Stop conditions

  • No production publication while boot readiness is nondeterministic.

Finish line

Verification checklist

Verify global IPv6 and RA routeip -6 address show dev {{interfaceName}} scope global; ip -6 route show default; ip -6 neigh show dev {{interfaceName}}{{ipv6Address}} is preferred and non-tentative, default route points to {{ipv6GatewayLinkLocal}} via RA, and the router neighbor is reachable.
Verify firewall semanticssudo nft --check --file /etc/nftables.conf && sudo nft list chain inet oneliners_host inputThe file parses; input policy is drop; established traffic, critical ICMPv6, hop-limit-255 link-local ND/RA, bounded echo, and both management SSH prefixes are present.
Verify both external familiesgetent ahosts {{probeHostname}}; curl -4 --fail --silent --show-error --max-time 5 https://{{probeHostname}}/health; curl -6 --fail --silent --show-error --max-time 5 https://{{probeHostname}}/healthDNS returns approved A and AAAA records and both health requests return the same healthy application response.

Recovery guidance

Common problems and safe checks

The global address remains `tentative` and then becomes `dadfailed`.

Likely causeAnother node uses the address, a cloned VM duplicated configuration, or the link reflects Neighbor Solicitations unexpectedly.

Safe checks
  • ip -6 address show dev ens3
  • journalctl -u systemd-networkd --since '-10 minutes' --no-pager
  • sudo tcpdump -ni ens3 -vv 'icmp6 && (ip6[40] == 135 or ip6[40] == 136)' -c 20

ResolutionRemove the address from service, locate the duplicate through switch/neighbor ownership, allocate a unique address, and repeat DAD. Never disable DAD to force use.

The address is preferred, but no IPv6 default route appears.

Likely causeRAs are absent/blocked, accept_ra is disabled by forwarding or sysctl policy, router lifetime is zero, or the interface listens on the wrong VLAN.

Safe checks
  • sysctl net.ipv6.conf.all.forwarding net.ipv6.conf.ens3.accept_ra
  • sudo tcpdump -ni ens3 -vv 'icmp6 && ip6[40] == 134' -c 3
  • networkctl status ens3 --no-pager

ResolutionRestore the expected RA path and host acceptance for a non-forwarding server, or configure the provider's documented static link-local route. Do not guess a gateway.

Small pings and SSH connect, but HTTPS transfers stall.

Likely causeICMPv6 Packet Too Big is blocked upstream or locally, producing a Path MTU black hole after the handshake.

Safe checks
  • tracepath -6 -n 2001:4860:4860::8888
  • sudo tcpdump -ni any -vv 'icmp6 && ip6[40] == 2' -c 10
  • sudo nft list chain inet oneliners_host input

ResolutionPermit valid Packet Too Big end to end, correct the lower link MTU if required, and retest representative payloads. Do not permanently clamp every path to hide broken ICMPv6.

The server loses its IPv6 router shortly after nftables activation.

Likely causeRA or neighbor advertisements fail the rule due to source, hop-limit, interface, or ordering mismatch.

Safe checks
  • ip -6 neigh show dev ens3
  • ip -6 route show default
  • sudo nft monitor trace
  • sudo tcpdump -ni ens3 -vv 'icmp6 && (ip6[40] == 134 or ip6[40] == 136)' -c 10

ResolutionLet timed rollback restore service, compare the observed valid RA/NA fields with the narrow rule, and correct protocol handling without accepting arbitrary ICMPv6.

IPv6 works locally, but external clients cannot connect.

Likely causeProvider ingress, upstream routing, reverse path, service bind, DNS, or host source-prefix policy differs from local tests.

Safe checks
  • ss -lntp
  • ip -6 route get 2001:db8:900:10::35 from 2001:db8:120::20
  • sudo tcpdump -ni ens3 -vv 'ip6 and tcp port 22' -c 20
  • sudo nft list chain inet oneliners_host input

ResolutionUse a controlled external client to locate the last observed SYN and verify return routing, provider ACL, listener, and management prefix one layer at a time.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use a static IPv6 default route via a reviewed link-local gateway when the provider explicitly does not send RAs; set the interface scope and `on-link` semantics according to official network guidance.
  • Use DHCPv6 for address or additional configuration only when the network contract defines it. DHCPv6 does not replace RA default-router discovery in normal IPv6 host behavior.
  • Keep automatic reboot-independent IPv4 management while deploying IPv6 only on a service interface.
  • Use UFW when its generated dual-family policy fully expresses requirements and remains the sole firewall owner; inspect resulting nftables rather than mixing direct rules.
  • Use a centralized firewall manager when a fleet needs transactionality, policy review, and staged rollback, while retaining host-level validation.

Operate it safely

  • Publish staged AAAA and reverse DNS with family-specific monitoring, then compare latency, TLS, HTTP status, and error rates before expanding traffic.
  • Add alerts for missing or changed RAs, duplicate-address failure, neighbor failure, IPv6 default-route loss, nftables reload failure, and PMTU symptoms.
  • Apply equivalent IPv6 policy to provider firewalls, load balancers, monitoring, backups, vulnerability scans, and incident runbooks.
  • Test one real service over IPv6 from multiple networks, including a path with a smaller MTU or tunnel, before claiming general reachability.
  • Review ICMPv6 and management prefixes whenever the network, provider, tunnel, firewall, or application listener changes.

Reference

Frequently asked questions

Can I block ICMPv6 except ping?

No. IPv6 depends on ICMPv6 for Neighbor Discovery, Router Advertisements, error reporting, and Path MTU Discovery. Permit required types narrowly and rate-limit echo separately.

Why is the IPv6 gateway link-local?

IPv6 routers normally advertise and act as next hops using link-local addresses scoped to the interface. The global prefix does not imply a `::1` gateway.

Does a global IPv6 address make every service public?

It makes direct routing possible if upstream and host policy allow it. Wildcard listeners, provider controls, host nftables, authentication, and DNS determine actual exposure.

Should the server use SLAAC or a static address?

This design uses a stable assigned address while accepting RA for default route and link parameters. Networks may use SLAAC or DHCPv6 differently; follow the explicit provider contract.

Why keep IPv4 during rollout?

Dual stack allows independent recovery and comparison. Removing IPv4 is a separate IPv6-only readiness project involving every dependency and management path.

Recovery

Rollback

Rollback restores the saved nftables ruleset through the pre-scheduled timer or console, then restores Netplan through `netplan try` timeout or the protected backup. DNS withdrawal and provider firewall reversal are separate actions. Already published AAAA records can continue sending IPv6 clients until caches expire.

  1. If fresh access, ND/RA, PMTU, or service tests fail after nftables load, do not cancel `nft-rollback-wyd302.timer`; use console to verify the saved ruleset is restored.
  2. If immediate firewall recovery is required, run `sudo nft --file /root/dual-stack-before-wyd302/ruleset.nft` from console and verify the management route before further changes.
  3. For address rollback, let `netplan try` expire or restore `/root/dual-stack-before-wyd302/netplan/.` to `/etc/netplan/`, generate, and apply from console.
  4. Remove or defer AAAA/reverse DNS and provider IPv6 ingress rules until the server has returned to the previous state; preserve packet counters, journals, RA capture metadata, and failed policy for analysis.

Evidence

Sources and review

Verified 2026-07-24Review due 2027-01-20
Ubuntu nftables command manualofficialUbuntu Netplan configuration manualofficialLinux kernel IPv6 sysctl documentationofficialRFC 7381 Enterprise IPv6 Deployment GuidelinesofficialRFC 8201 IPv6 Path MTU Discoveryofficial