Configuring IPv6 on a VPS: addressing, reverse DNS, and firewall

IPv6 is no longer optional: it drives mail deliverability, access from mobile networks, and search visibility. Here is the full configuration.

Mesh of network nodes around an IPv6 address block

IPv6 stopped being a future topic. A large share of North American mobile traffic is native IPv6, several major mail providers treat properly configured IPv6 senders better, and a service reachable over both families depends less on translation layers. On our VPS plans, every VM gets four IPv6 addresses alongside its IPv4.

This guide covers static configuration on Ubuntu 24.04 with Netplan, DNS publication, reverse DNS, and filtering.

1. Assess what you have

bash
ip -6 addr show
ip -6 route show
ping6 -c3 2620:fe::fe

Three cases. If a global address (starting with 2 or 3) is present and the ping answers, you are already set. If you only see an fe80:: address, only link-local exists. If the address is there but the ping fails, the default route is missing.

2. Configure the address with Netplan

Ubuntu Server uses Netplan. Files live in /etc/netplan/; the YAML is indentation-sensitive and must use spaces.

bash
sudo tee /etc/netplan/60-ipv6.yaml <<'EOF'
network:
  version: 2
  ethernets:
    ens3:
      dhcp4: true
      accept-ra: false
      addresses:
        - "2602:xxxx:xxxx:1::10/64"
      routes:
        - to: "::/0"
          via: "2602:xxxx:xxxx:1::1"
          on-link: true
      nameservers:
        addresses: [2620:fe::fe, 2606:4700:4700::1111, 9.9.9.9]
EOF

sudo chmod 600 /etc/netplan/60-ipv6.yaml
sudo netplan try

netplan try applies the configuration and rolls it back automatically after 120 seconds unless you confirm. On a remote machine that is the safety net that saves you from losing access over a typo. Confirm with Enter, then sudo netplan apply.

Static addressing or autoconfiguration? If your provider announces the prefix through Router Advertisements, leave accept-ra: true and drop the addresses block: the VM configures itself. Mixing both, RA enabled and a static address, produces competing default routes and intermittent losses that are painful to diagnose.

3. Verify connectivity

bash
ip -6 addr show ens3
ip -6 route get 2620:fe::fe
ping6 -c3 2606:4700:4700::1111
traceroute6 -n ipv6.google.com
curl -6 -s https://ifconfig.co

curl -6 must return exactly the address you configured. A different address means a temporary IPv6 generated by privacy extensions, fine on a laptop, a problem on a server whose reverse DNS has to match.

bash
# pin the outgoing source address
sudo tee /etc/sysctl.d/99-ipv6-server.conf <<'EOF'
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0
EOF
sudo sysctl --system

4. Publish the AAAA record

One AAAA record is enough to make the service reachable over IPv6. Keep the same TTL as the A record to avoid asymmetric behaviour during a migration.

DNS zone
example.com.  300  IN  A     198.51.100.42
example.com.  300  IN  AAAA  2602:xxxx:xxxx:1::10

Only publish the AAAA once the service actually answers over IPv6. A dual-stack client prefers IPv6: if the port only listens on IPv4, the user waits for a timeout before falling back. For a web server, check for listen [::]:443 ssl; in Nginx; for any service, sudo ss -tulpen | grep ':::'.

5. Set up reverse DNS (PTR)

The PTR record maps an IP address back to a name. It is not managed at your registrar but by whoever holds the prefix, your hosting provider, through its console. At FFxF, reverse DNS is configurable on every address assigned to the VM.

It is essential for sending mail: most large recipients reject or spam-file a message coming from an IPv6 address with no PTR, or whose PTR does not resolve back.

bash
dig -x 2602:xxxx:xxxx:1::10 +short
# should return: mail.example.com.

dig +short mail.example.com AAAA
# should return: 2602:xxxx:xxxx:1::10

The two lookups must agree with each other. A PTR pointing at a name with no matching AAAA is treated as no PTR at all.

6. Filter IPv6

The classic mistake: a well-tuned IPv4 firewall and nothing on IPv6. Server IPv6 addresses get scanned like any other, prefixes are public and active ranges are inferred from DNS.

bash
grep IPV6 /etc/default/ufw     # should print IPV6=yes
sudo ufw status verbose        # rules should appear as (v6)

If it says IPV6=no, fix the file then run sudo ufw disable && sudo ufw enable. Rules written afterwards cover both families automatically.

Do not block all ICMPv6. Unlike IPv4, the protocol depends on it to function: neighbour discovery, path MTU discovery. Filtering ICMPv6 indiscriminately produces connections that establish and then stall on large transfers. UFW allows what is needed by default; leave it alone.

7. Make services listen

A configured address is useless if the daemons only listen on IPv4.

bash
sudo ss -tulpen | grep -E ':::|\[::\]'
  • Nginx: add listen [::]:80; and listen [::]:443 ssl;.
  • OpenSSH: listens on both families by default, unless ListenAddress narrows it.
  • Postfix: inet_protocols = all.
  • Docker: needs "ipv6": true and a dedicated prefix in daemon.json to give containers IPv6.

Checklist

  • Global address and default route present (ip -6 route).
  • ping6 and curl -6 go out with the expected address.
  • AAAA published only after confirming the service listens.
  • PTR configured and consistent with the AAAA.
  • Firewall active on v6, ICMPv6 preserved.
  • Final check on test-ipv6.com from a client, and from the server.