IPv6 transition is less about if and more about when, and most organizations are already living in a hybrid IPv4/IPv6 world.

Let’s watch a dual-stack web server in action. Imagine we have a server webserver.example.com that needs to be reachable by both IPv4 and IPv6 clients.

# /etc/nginx/sites-available/default
server {
    listen 80;
    listen [::]:80;
    server_name webserver.example.com;
    root /var/www/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Notice the listen directives. listen 80; handles IPv4 connections on port 80. listen [::]:80; is the key for IPv6 – it tells Nginx to listen on the IPv6 wildcard address (::) for port 80. If you only had the IPv4 listen directive, IPv6 clients wouldn’t be able to reach this server. If you only had the IPv6 listen directive, IPv4 clients wouldn’t connect. Dual-stack means both are active.

The operating system’s network stack is responsible for binding to both address families. When a client attempts to connect to webserver.example.com, the DNS resolution will return both an A record (for IPv4) and an AAAA record (for IPv6). The client’s OS will then try to connect to the IPv6 address first if it prefers IPv6. If that fails or is unavailable, it will fall back to the IPv4 address.

This dual-stack approach is the most common and often the simplest way to transition, as it allows both IPv4 and IPv6 traffic to flow over the same network infrastructure and applications. Devices are configured with both an IPv4 and an IPv6 address, and they can communicate with both IPv4 and IPv6 destinations.

However, what if your internal network is all IPv6, but you need to reach an IPv4-only service, or vice-versa? This is where tunneling comes in. Tunneling encapsulates IPv6 packets within IPv4 packets (or vice-versa) to traverse a network that only supports the other protocol.

Consider a scenario where your internal network is IPv6-only, but you need to access an IPv4-only web server ipv4.example.com. You’d typically use a tunneling mechanism like 6to4 or Teredo, or more commonly, manual tunneling. Let’s look at manual tunneling using ip tunnel.

On a router or gateway machine (let’s call it tunnel-gw) with both IPv4 and IPv6 connectivity, you might set up a tunnel interface:

# On tunnel-gw, for an IPv6-in-IPv4 tunnel to a remote endpoint 203.0.113.10
ip tunnel add ipv6_to_ipv4_tunnel mode ip6ip4 remote 203.0.113.10 local 192.0.2.5 ttl 255
ip link set ipv6_to_ipv6_tunnel up
ip addr add 2001:db8:abcd:1::1/64 dev ipv6_to_ipv4_tunnel

Here, ip tunnel add ... mode ip6ip4 creates an interface that will encapsulate IPv6 packets into IPv4 packets. remote 203.0.113.10 is the IPv4 address of the other end of the tunnel, and local 192.0.2.5 is the IPv4 address of this machine. We then assign an IPv6 address to this new tunnel interface, 2001:db8:abcd:1::1/64. Any IPv6 traffic destined for an IPv4-only network, routed through this ipv6_to_ipv4_tunnel interface, will be wrapped in an IPv4 header and sent to the remote endpoint. The remote endpoint will decapsulate it.

This allows your internal IPv6-only hosts to reach IPv4 resources. The reverse is also true: you can create IPv4-in-IPv6 tunnels using mode ipv6ip to send IPv4 traffic over an IPv6-only backbone.

The biggest challenge with tunneling is often managing the tunnel endpoints and ensuring they have stable IPv4 (or IPv6) addresses. Automated tunneling protocols like 6to4 and Teredo attempt to solve this, but they can introduce their own complexities and reliability issues, often relying on public relay servers.

The one place where tunneling gets surprisingly tricky is when you have a dual-stack network and try to tunnel IPv6 over it. If your underlying network supports IPv6 natively, the packets should just go through. But if you’re tunneling IPv6 because you think the path is IPv4-only, and it turns out parts of it do support IPv6 natively, you can end up with packets being encapsulated, sent, and then re-encapsulated if the remote end also tunnels IPv6. This is called "double encapsulation" and is a common, hard-to-debug issue. The inner IPv6 packet, which should have been routed natively, gets wrapped in an outer IPv4 header, sent to a tunnel endpoint, decapsulated, and then if the next hop prefers IPv6 and the destination is reachable via IPv6, it’s sent natively. If, however, the intermediate network also tries to tunnel it, you get a packet within a packet within a packet.

The next hurdle is often understanding how stateless and stateful address autoconfiguration (SLAAC and DHCPv6) work together.

Want structured learning?

Take the full Computer Networking course →