Setup NAT64 on Debian using unbound and TAYGA

Some of my networks have been configured for IPv6 only. This results in a major problem: Compatibility. Many services out there are still IPv4 only. With an IPv6 only network you can’t reach any of these natively. On the other hand an IPv6 only network decreases the overall complexity when it comes to administration.

All my hosts at least support IPv6. Most of them don’t require an IPv4 address. Only a few like IoT devices or public servers like mail or the reverse proxy are (also) reachable over IPv4.

The standard Debian package sources are available over IPv6. So we can easily install and update packages from the official sources. Unfortunately some hosts require third party package sources or need IPv4 outbound connections. A good example for this is WordPress. The VM hosting my blog is reachable over IPv6 only. A reverse proxy ensures the reachability over IPv4. WordPress itself runs perfectly in this setup. But is unreachable over IPv6. This means that the update checks and the plugin directory are unavailable.

To solve this problem we can use NAT64. This allows to encode an IPv4 address into an IPv6 address. A router handles the translation process from IPv6 to IPv4 and the IPv4 NAT.

How does it work?

We need a special prefix for the translation. The well-known prefix for NAT64 is 64:ff9b::/96. We may use any other prefix but in general you should stick with this one. Let’s assume we want to reach over NAT64. Using the IPv4 as suffix will do the job: 64:ff9b:: Of course an IPv6 is normally hex encoded but this syntax is allowed. As soon as the gateway sees an address containing the configured NAT64 prefix it will start the translation to IPv4. In this article I’m going to use TAYGA for the translation.

IPv6 only hosts won’t perform this translation on their own. Instead the DNS server has to respond to an AAAA request with the corresponding NAT64 address (if no IPv6 address is available for the requested domain). DNS servers won’t do this out of the box as the NAT64 prefix may vary and preferring NAT64 over native IPv4 may cause performance issues. I’m going to setup a dedicated DNS64 using unbound.

There are public DNS64 servers out there. Google offers a DNS64 service which you may use. As of 2022 is still IPv4 only. Querying AAAA records will return an empty result. Using one of the Google DNS64 servers will give us 64:ff9b::8c52:7903.


When using a public DNS64 service you will be unable to reach hosts in private networks. The well-known prefix is disallowed per RFC 6052 to reach private networks. Thus you will need to specify a custom prefix.

Some libraries or tools may not support IPv6 at all. NAT64 only works if your host is fully IPv6 compatible. As NAT64 uses an IPv6 address, your application must understand IPv6. Example: If curl would not be IPv6 ready, NAT64 wouldn’t fix my WordPress issues.

NAT64 is NAT. NAT is complicated and often causes issues with protocols like SIP. NAT64 increases the complexity and may cause additional issues. Using NAT64 combines the disadvantages of IPv4 and IPv6: The lack of globally routable addresses while causing incompatibilities.

Setup DNS

This step is optional and only required in case you want to setup your own DNS64 server. First of all install unbound:

apt install unbound

Make sure to configure an upstream DNS provider in /etc/unbound/unbound.conf.d/forward-zones.conf:

   name: "."
   forward-addr: 2001:4860:4860::8888
   forward-addr: 2001:4860:4860::8844

After that modify /etc/unbound/unbound.conf.

Ensure the line module-config contains dns64:

module-config: "dns64 validator iterator"

You may also want to set the dns64-prefix:

dns64-prefix: 64:ff9b::/96

The whole file may look like this:

include: "/etc/unbound/unbound.conf.d/*.conf"
   module-config: "dns64 validator iterator"
   interface: 2001:67c:2924:141:d64::20
   access-control: ::0/0 refuse
   access-control: 2001:67c:2924::/48 allow
   dns64-prefix: 64:ff9b::/96

Don’t forget to adjust the interface and access-control.

After restarting unbound systemctl restart unbound you should be able to query your DNS64 server:

$ dig AAAA @2001:67c:2924:141:d64::20
 ; <<>> DiG 9.11.5-P4-5.1+deb10u3-Debian <<>> AAAA @2001:67c:2924:141:d64::20
 ;; global options: +cmd
 ;; Got answer:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9012
 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
 ; EDNS: version: 0, flags:; udp: 4096
 ;            IN  AAAA
 ;; ANSWER SECTION:        59  IN  AAAA    64:ff9b::8c52:7903
 ;; Query time: 61 msec
 ;; SERVER: 2001:67c:2924:141:d64::20#53(2001:67c:2924:141:d64::20)
 ;; WHEN: Sat Apr 24 23:23:14 CEST 2021
 ;; MSG SIZE  rcvd: 67


TAYGA will translate the IPv6 address returned by the DNS64 server to IPv4. As the host will act as a router you have to enable forwarding:

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

Continue by installing TAYGA:

apt install tayga

Configure /etc/tayga.conf like this:

tun-device nat64
# TAYGA's IPv4 address
# TAYGA's IPv6 address
ipv6-addr fd67:c166:c737:ea24::1
# The NAT64 prefix.
prefix 64:ff9b::/96
# Dynamic pool prefix.
# Persistent data storage directory
data-dir /var/spool/tayga

Restart TAYGA and start on boot:

systemctl restart tayga
systemctl enable tayga

Afterwards make sure to point a route for 64:ff9b::/96 to your TAYGA router.

Finally configure iptables for NAT4:

iptables -t nat -A POSTROUTING -o nat64 -j MASQUERADE
iptables -t nat -A POSTROUTING -s -j MASQUERADE

Done. You should be now able to reach IPv4 only hosts over IPv6. You can test your setup like this:

ping -6


Don’t forget to implement some firewalling. Ensure your TAYGA host is only reachable from authorized hosts and deny IPv4 access to unwanted hosts or services.

Notify of
Inline Feedbacks
View all comments