--- title: "IPv4 vs IPv6: Differences and When to Use Each on Servers" description: "Understand how IPv4 and IPv6 differ in addressing, NAT, headers and security, and learn how to run a dual-stack Linux server with Nginx, databases and UFW." category: "Comparison Guide" language: en source: https://cubepath.com/docs/comparison-guide/ipv4-vs-ipv6-when-to-use-each updated: 2026-09-25 --- # IPv4 vs IPv6: Differences and When to Use Each on Servers IPv4 and IPv6 are the two versions of the Internet Protocol in use today. IPv4 still carries most traffic, but its address pool ran out years ago, and a large and growing share of users now reach the Internet over IPv6, especially on mobile networks. This guide explains how the two protocols differ, what that means for a server, and how to configure and test a dual-stack Ubuntu 24.04 server so it answers on both. ## At a glance | | IPv4 | IPv6 | |---|---|---| | Address size | 32 bits (about 4.3 billion addresses) | 128 bits (about 3.4 x 10^38 addresses) | | Notation | Dotted decimal: `203.0.113.10` | Hexadecimal groups: `2001:db8::10` | | Typical allocation to a server | One address, sometimes a few | A /64 subnet or larger | | Address configuration | Static or DHCP | Static, SLAAC or DHCPv6 | | Neighbor discovery | ARP (broadcast) | NDP over ICMPv6 (multicast) | | NAT | Common, often required | Not needed, every host can have a global address | | Header | 20 to 60 bytes, variable, with checksum | Fixed 40 bytes, no checksum, extension headers | | Fragmentation | Routers and senders | Sender only, relies on Path MTU Discovery | | Minimum MTU | 68 bytes (576 must be reassembled) | 1280 bytes | | DNS record | `A` | `AAAA` | | Reverse DNS zone | `in-addr.arpa` | `ip6.arpa` | The addresses used in this guide come from the ranges reserved for documentation (`203.0.113.0/24` and `2001:db8::/32`). Replace them with your own. ## Key differences that matter on a server ### Address space and NAT With IPv4, addresses are scarce and cost money, so providers hand out one public address per server and private networks sit behind NAT. NAT breaks end-to-end connectivity: inbound connections need port forwarding, and protocols that embed addresses (SIP, FTP active mode, some games) need helpers. Carrier-grade NAT on ISP networks also means many clients share a single public IPv4, which makes rate limiting and abuse blocking by IP less precise. With IPv6, a server usually gets a whole /64 or more. Every service or container can have its own global address, and no translation happens on the path. That simplifies logging and firewall rules, but it also means a firewall is now the only thing standing between the Internet and your hosts, not NAT. ### Header and performance The IPv6 header has a fixed size and drops the checksum, which makes forwarding simpler for routers. In practice the performance difference between the two protocols is small and depends far more on the route each one takes: IPv4 and IPv6 traffic can follow different paths through different providers. Measure both from where your users are instead of assuming one is faster. ### Security Neither protocol is secure by itself. IPsec was mandatory in the original IPv6 specification but is now optional in both, so it is not a practical difference. What does change: - **ICMPv6 is required.** IPv6 uses ICMPv6 for neighbor discovery, router advertisements and Path MTU Discovery. Blocking all ICMPv6 breaks connectivity in subtle ways, such as connections that hang when packets get larger. - **Scanning is harder but not impossible.** A /64 is too large to sweep, but addresses like `::1` or ones derived from the MAC address are easy to guess. - **Firewalls must cover both.** A common mistake is to write strict IPv4 rules and leave IPv6 wide open. On Ubuntu, UFW applies each rule to both protocols as long as `IPV6=yes` is set in `/etc/default/ufw`, which is the default. ## Which one should you use? **Run dual-stack (recommended for public services).** Serve both IPv4 and IPv6. IPv4 keeps you reachable from every network, and IPv6 gives a direct path to the many clients that prefer it, avoids carrier-grade NAT on mobile networks and prepares you for IPv6-only networks. Modern clients use Happy Eyeballs (RFC 8305) to try both and pick the one that connects first, so a working dual-stack server has no downside for users. **IPv4 only** is acceptable for internal services on a private network, legacy software that cannot bind to IPv6, or when your upstream provider does not offer IPv6. Even then, keep IPv6 enabled in the kernel: many tools expect the loopback `::1` to exist. **IPv6 only** fits internal infrastructure you fully control (Kubernetes clusters, backend networks, IoT fleets) where the address space makes planning easier. For anything that must be reachable by the public, you still need IPv4 or a translation service (NAT64/DNS64) in front of it, because a part of the Internet still has no IPv6. The rest of this guide sets up and verifies a dual-stack server. ## Prerequisites - A server running Ubuntu 24.04 LTS with both a public IPv4 and a public IPv6 address, for example a CubePath VPS. - A non-root user with `sudo` privileges. - A domain name if you want to publish `A` and `AAAA` records. ## Step 1 - Checking your IPv4 and IPv6 configuration List the addresses on the server: ```bash ip -brief address ``` ```output lo UNKNOWN 127.0.0.1/8 ::1/128 eth0 UP 203.0.113.10/24 2001:db8:1234::10/64 fe80::5054:ff:fe12:3456/64 ``` The address starting with `fe80::` is link-local: every IPv6 interface has one, and it only works on the local link. You need a global address (here `2001:db8:1234::10`) for Internet traffic. Check that both protocols have a default route: ```bash ip -4 route show default ip -6 route show default ``` ```output default via 203.0.113.1 dev eth0 proto static default via 2001:db8:1234::1 dev eth0 proto static metric 1024 pref medium ``` On Ubuntu 24.04 addresses are managed by Netplan. If the IPv6 address or route is missing, check the files in `/etc/netplan/`: a static IPv6 configuration lists the address under `addresses:` and the gateway in a `routes:` entry with `to: "::/0"`. Apply changes with `sudo netplan try`, which rolls back automatically if you lose connectivity. ## Step 2 - Testing connectivity over each protocol Test outbound connectivity on each protocol separately: ```bash ping -4 -c 3 one.one.one.one ping -6 -c 3 one.one.one.one ``` Then check which public address the Internet sees for each protocol: ```bash curl -4 https://icanhazip.com curl -6 https://icanhazip.com ``` ```output 203.0.113.10 2001:db8:1234::10 ``` If the IPv6 command fails with `Network is unreachable`, there is no IPv6 default route. If it times out, something upstream or a firewall is dropping IPv6 traffic. To see the path each protocol takes, install `mtr`, which works for both: ```bash sudo apt install mtr-tiny mtr -4 -rwc 10 one.one.one.one mtr -6 -rwc 10 one.one.one.one ``` ## Step 3 - Making services listen on both protocols Services must be told to listen on IPv6. The details depend on the software. ### Nginx In the `server` block, add an IPv6 `listen` directive next to the IPv4 one: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx server { listen 80; listen [::]:80; server_name your_domain; root /var/www/your_domain; index index.html; } ``` The default site on Ubuntu already includes `listen [::]:80 default_server;`. Test and reload: ```bash sudo nginx -t sudo systemctl reload nginx ``` ### MySQL 8 MySQL 8 accepts a comma-separated list of addresses in `bind-address`, or `*` for all IPv4 and IPv6 addresses: ```bash sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf ``` ```ini [mysqld] bind-address = 127.0.0.1,::1 ``` Listening only on loopback addresses is the safe default. Add a specific private or public address only if other servers must connect, and restrict access with the firewall. ### PostgreSQL In `postgresql.conf`, `listen_addresses` takes a comma-separated list, and `'*'` means all IPv4 and IPv6 addresses: ```ini listen_addresses = 'localhost' ``` `localhost` resolves to both `127.0.0.1` and `::1`. Clients connecting over IPv6 must also be allowed in `pg_hba.conf` with an IPv6 CIDR, for example `2001:db8:1234::/64`. ### Verifying the listening sockets ```bash sudo ss -tlnp ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=1234,fd=7)) ``` A line with `0.0.0.0` accepts IPv4, a line with `[::]` accepts IPv6. ## Step 4 - Applying firewall rules to both protocols Confirm that UFW manages IPv6: ```bash grep IPV6 /etc/default/ufw ``` ```output IPV6=yes ``` Rules without an address apply to both protocols automatically: ```bash sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status ``` ```output Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) 80/tcp (v6) ALLOW Anywhere (v6) 443/tcp (v6) ALLOW Anywhere (v6) ``` Rules that name a source address apply only to that protocol, so an IPv4 allowlist for a database port needs a matching IPv6 rule if clients connect over IPv6: ```bash sudo ufw allow from 2001:db8:5678::/64 to any port 5432 proto tcp ``` UFW's default rules already allow the ICMPv6 messages IPv6 needs. Do not add rules that drop all ICMPv6. ## Step 5 - Publishing DNS records and testing from outside Point both records at the server in your DNS provider: | Type | Name | Value | |---|---|---| | A | your_domain | 203.0.113.10 | | AAAA | your_domain | 2001:db8:1234::10 | Check them: ```bash dig +short A your_domain dig +short AAAA your_domain ``` Then test the site over each protocol from another machine that has IPv6: ```bash curl -4 -I http://your_domain curl -6 -I http://your_domain ``` Both should return `HTTP/1.1 200 OK`. To test an IPv6 address directly, put it in brackets: `curl -I http://[2001:db8:1234::10]/`. > Important: only publish an `AAAA` record once the service really works over IPv6. Clients with IPv6 try it first, and a broken IPv6 path makes the site slow or unreachable for them even though IPv4 works. ## Troubleshooting **The server has an IPv6 address but no outbound IPv6 connectivity.** Check `ip -6 route show default`. If the route is missing and your provider uses router advertisements, make sure the interface accepts them (`sysctl net.ipv6.conf.eth0.accept_ra` should not be `0`); otherwise add the gateway to Netplan as described in Step 1. **IPv6 is disabled in the kernel.** Check: ```bash sysctl net.ipv6.conf.all.disable_ipv6 ``` A value of `1` means it was disabled, usually by a file in `/etc/sysctl.d/` or by `ipv6.disable=1` on the kernel command line. Remove that setting and reboot. **Connections work for small requests but hang on large responses.** This is typically a Path MTU problem caused by a firewall dropping ICMPv6 "Packet Too Big" messages. Allow ICMPv6 along the path. **A client prefers IPv6 but you need it to use IPv4.** On Linux clients, uncomment this line in `/etc/gai.conf` to make the system resolver prefer IPv4 addresses: ```text precedence ::ffff:0:0/96 100 ``` This is a client-side workaround. The real fix is making the IPv6 path work. ## Conclusion IPv6 is not a replacement you switch to on a fixed date: for the foreseeable future public services need both. Running dual-stack costs little on a modern Linux server, as long as every service listens on both protocols, the firewall covers both, and you only publish `AAAA` records after testing. As next steps, add HTTPS with Let's Encrypt for both records, set up reverse DNS (`PTR`) for your IPv6 address if the server sends email, and monitor your site over IPv4 and IPv6 separately so a failure on one does not go unnoticed.