<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>A Geek with Guns - WireGuard</title>
    <link rel="self" type="application/atom+xml" href="https://www.christopherburg.com/tags/wireguard/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://www.christopherburg.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-12-30T15:30:00-06:00</updated>
    <id>https://www.christopherburg.com/tags/wireguard/atom.xml</id>
    <entry xml:lang="en">
        <title>Setup IPv6 in WireGuard</title>
        <published>2025-12-30T15:30:00-06:00</published>
        <updated>2025-12-30T15:30:00-06:00</updated>
        
        <author>
          <name>
            Christopher Burg
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/"/>
        <id>https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/</id>
        
        <content type="html" xml:base="https://www.christopherburg.com/blog/setup-ipv6-in-wireguard/">&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;Over the holiday break I finished upgrading all of my self-hosted services to make them available via IPv6. The upgrade was straightforward for all of my services except one: my WireGuard &lt;abbr title=&quot;Virtual Private Network&quot;&gt;VPN&lt;&#x2F;abbr&gt; server. I wanted to provide my VPN clients with IPv6 connectivity without using &lt;abbr title=&quot;Network Address Translation&quot;&gt;NAT&lt;&#x2F;abbr&gt;. Unfortunately most of the guides online use NAT for IPv4 and IPv6. NAT is necessary for IPv4, but goes against the very design of IPv6. I wanted to provide each client with a globally routable IPv6 address. This ended up being simple once I figured it out.&lt;&#x2F;p&gt;
&lt;p&gt;I previous wrote a guide &lt;a href=&quot;&#x2F;archive&#x2F;getting-a-static-ip-address-with-a-cheap-vps-and-wireguard&#x2F;&quot;&gt;for setting up WireGuard to share a public static IPv4 address&lt;&#x2F;a&gt;. The formatting was ruined in the transition from my old WordPress blog to this statically generated one, but it explains how to setup a WireGuard VPN server for IPv4. This post will explain how to setup a WireGuard VPN server for IPv6.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;preamble&quot;&gt;Preamble&lt;&#x2F;h1&gt;
&lt;p&gt;First a major caveat. As of this writing, I&#x27;ve had this iteration of my VPN server running for two days. I&#x27;ve tested it on my home network, on my phone&#x27;s cellular network, and on an IPv4 only network through my travel router. IPv6 appears to work in all cases. I haven&#x27;t finished thorough testing though. There may be a number of corner cases that don&#x27;t work. I will update this guide as I find and fix them. Therefore, if you find something broken, check back and I might have discovered it and posted the solution already.&lt;&#x2F;p&gt;
&lt;p&gt;I also pieced my setup together by taking bits and pieces of several online guides. I pillaged from &lt;a href=&quot;https:&#x2F;&#x2F;mkaczanowski.com&#x2F;ndppd-ipv6-ndp-proxy&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;blog.frehi.be&#x2F;2022&#x2F;06&#x2F;11&#x2F;setting-up-wireguard-vpn-with-ipv6&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;blog.miyuru.lk&#x2F;setup-wireguard-with-global-ipv6&#x2F;&quot;&gt;this guide&lt;&#x2F;a&gt;, and &lt;a href=&quot;https:&#x2F;&#x2F;utcc.utoronto.ca&#x2F;~cks&#x2F;space&#x2F;blog&#x2F;linux&#x2F;ModernProxyIPv6AndARP&quot;&gt;this guide&lt;&#x2F;a&gt;. If there are unnecessary configuration steps in this guide, I likely took it from one of these guides and didn&#x27;t test my final setup without the configuration. Such mistakes are entirely my fault.&lt;&#x2F;p&gt;
&lt;p&gt;In order to follow this guide, you need a source of IPv6 addresses and specifically a separate subnet from your hosting network. My &lt;abbr title=&quot;Internet Service Provider&quot;&gt;ISP&lt;&#x2F;abbr&gt; provides me with a &#x2F;48 prefix. This gives me roughly a bajillion addresses and subnets.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conventions-used-in-this-guide&quot;&gt;Conventions Used in This Guide&lt;&#x2F;h1&gt;
&lt;p&gt;For the purposes of this guide, I&#x27;m going to use the following IP addresses (&lt;code&gt;2001:db8&lt;&#x2F;code&gt; is the &lt;a href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc3849&quot;&gt;prefix reserved for examples and documentation&lt;&#x2F;a&gt; for those not aware):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234::&#x2F;48&lt;&#x2F;code&gt;: The prefix provided by our ISP.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:9999::&#x2F;64&lt;&#x2F;code&gt;: The subnet of our hosting network. The subnet &lt;code&gt;9999&lt;&#x2F;code&gt; was chosen for readability purposes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::&#x2F;64&lt;&#x2F;code&gt;: The subnet provided to our WireGuard VPN clients. Throughout this guide remember that the subnet with numbers is for our hosting network and the subnet with letters is for our VPN clients.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:9999::1&lt;&#x2F;code&gt;: The IPv6 address used by clients to connect to our WireGuard VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::1&lt;&#x2F;code&gt;: The IPv6 address of the WireGuard interface. This differs from the above address in that a client only uses it after it has connected to the WireGuard VPN server through the above address. Once connected, clients will use this address as their gateway.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt;: The IPv6 address provided to the first of our WireGuard VPN clients. This address will be incremented subsequently so our second client will have an IPv6 address of &lt;code&gt;2001:db8:1234:ffff::1:2&lt;&#x2F;code&gt;, our third &lt;code&gt;2001:db8:1234:ffff::1:3&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;I will also use &lt;code&gt;en0&lt;&#x2F;code&gt; as the name of the network interface of our WireGuard VPN server, &lt;code&gt;51820&lt;&#x2F;code&gt; as the port used to connect to WireGuard on our server, and &lt;code&gt;wg-vpn.conf&lt;&#x2F;code&gt; as the name of our WireGuard configuration file.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-actual-guide&quot;&gt;The Actual Guide&lt;&#x2F;h1&gt;
&lt;p&gt;The first thing you will need to do is ensure that the router between your hosting network and ISP is setup to route the subnet for our VPN clients to our VPN server. How you do this will depend on both your ISP and your router. For my Ubiquiti Cloud Gateway Ultra, I created a static route that routes the entire &lt;code&gt;2001:db8:1234:ffff::&#x2F;64&lt;&#x2F;code&gt; subnet to &lt;code&gt;2001:db8:1234:9999::1&lt;&#x2F;code&gt;. You will also need to configure your router&#x27;s firewall to allow incoming WireGuard traffic to your VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m hosting my VPN server on Fedora Server. By default, IPv6 forwarding isn&#x27;t enabled on Fedora Server. I enabled it by adding &lt;code&gt;net.ipv6.conf.all.forwarding=1&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;etc&#x2F;sysctl.conf&lt;&#x2F;code&gt; and issued the &lt;code&gt;sysctl -p&lt;&#x2F;code&gt; command to load the changes. I also added several other lines. &lt;code&gt;&#x2F;etc&#x2F;sysctl.conf&lt;&#x2F;code&gt; on my VPN server contains the following contents:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv4.ip_forward=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.en0.proxy_ndp=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.all.forwarding=1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;net.ipv6.conf.en0.accept_ra=2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first line enables IPv4 forwarding. It&#x27;s enabled by default on Fedora Server, but I added the line just to ensure it&#x27;s always enabled. The second line enables proxying &lt;abbr title=&quot;Neighbor Discovery Protocol&quot;&gt;NDP&lt;&#x2F;abbr&gt; packets, which is used by IPv6 to discover neighboring devices. The final line enables router advertisements while IPv6 forwarding is enabled. I added it to the file when I was trying to dole out IPv6 addresses through another method. I&#x27;m not sure if it&#x27;s needed for my final setup. This is one of those potentially unnecessary configuration steps I mentioned in the preamble for this guide.&lt;&#x2F;p&gt;
&lt;p&gt;The only port I opened on my VPN server&#x27;s firewall is &lt;code&gt;51820&lt;&#x2F;code&gt; for &lt;abbr title=&quot;User Datagram Protocol&quot;&gt;UDP&lt;&#x2F;abbr&gt; packets. WireGuard only uses UDP so you don&#x27;t need to open the port for &lt;abbr title=&quot;Transmission Control Protocol&quot;&gt;TCP&lt;&#x2F;abbr&gt;. I also enabled masquerading capabilities. Masquerading capabilities are only needed for NAT, which means it&#x27;s only necessary for IPv4. You don&#x27;t need to enable it if you&#x27;re only using IPv6 on your VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;Everything else happens in the WireGuard configuration file located at &lt;code&gt;&#x2F;etc&#x2F;wireguard&#x2F;wg-vpn.conf&lt;&#x2F;code&gt;. The first section of the configuration file sets up the interface:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Interface]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PrivateKey = &amp;lt;Your Server&amp;#39;s Private Key&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Address = 2001:db8:1234:ffff::1&#x2F;64
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;SaveConfig = false
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ListenPort = 51820
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is all pretty basic. &lt;code&gt;PrivateKey&lt;&#x2F;code&gt; obvious contains your server&#x27;s private key that was generated with &lt;code&gt;wg genkey&lt;&#x2F;code&gt;. &lt;code&gt;Address&lt;&#x2F;code&gt; is the IPv6 address of the WireGuard interface. Clients will use this address as their gateway once they&#x27;ve connected to our VPN server. &lt;code&gt;SaveConfig&lt;&#x2F;code&gt; determines if the current configuration is saved when the WireGuard interface is shutdown. I generate my configuration files with Ansible so I don&#x27;t want any state maintained and disable this feature. &lt;code&gt;ListenPort&lt;&#x2F;code&gt; is the port through which clients will connect to the VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;The next section is where the heavy lifting is done. &lt;code&gt;PostUp&lt;&#x2F;code&gt; commands run when the WireGuard server is being started. &lt;code&gt;PostDown&lt;&#x2F;code&gt; commands run when the WireGuard server is being stopped. In this case, the &lt;code&gt;PostDown&lt;&#x2F;code&gt; commands simply undo the &lt;code&gt;PostUp&lt;&#x2F;code&gt; commands. Also note that &lt;code&gt;%i&lt;&#x2F;code&gt; stands for the WireGuard interface name, which is &lt;code&gt;wg-vpn&lt;&#x2F;code&gt; since the configuration file is &lt;code&gt;wg-vpn.conf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip6tables -A FORWARD -i en0 -o %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip6tables -A FORWARD -i %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:1 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:2 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:3 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip6tables -D FORWARD -i en0 -o %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip6tables -D FORWARD -i %i -j ACCEPT;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:1 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:2 dev en0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PostDown = ip -6 neighbor del proxy 2001:db8:1234:ffff::1:3 dev en0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;PostUp = ip6tables -A FORWARD -i en0 -o %i -j ACCEPT;&lt;&#x2F;code&gt; and &lt;code&gt;PostUp = ip6tables -A FORWARD -i %i -j ACCEPT;&lt;&#x2F;code&gt; enable IPv6 forwarding between the server&#x27;s network interface (&lt;code&gt;en0&lt;&#x2F;code&gt; in this example) and the WireGuard interface (&lt;code&gt;wg-vpn&lt;&#x2F;code&gt; in this example). This allowed IPv6 traffic originating from our clients to be forwarded out of the VPN server&#x27;s network interface and return traffic routed from the VPN server&#x27;s network interface to the appropriate client.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::1:1 dev en0&lt;&#x2F;code&gt; and the following two lines setup NDP proxies for each client. You could probably replace all of these lines with &lt;code&gt;PostUp = ip -6 neighbor add proxy 2001:db8:1234:ffff::&#x2F;64 dev en0&lt;&#x2F;code&gt;. Again I generate my configuration file with an Ansible playbook so it&#x27;s just as easy for me to loop through my list of clients and add a line per client. As mention above, the &lt;code&gt;PostDown&lt;&#x2F;code&gt; lines simply undo the &lt;code&gt;PostUp&lt;&#x2F;code&gt; lines when the WireGuard interface is stopped.&lt;&#x2F;p&gt;
&lt;p&gt;The final part of the file contains configuration information for each peer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:1&#x2F;128
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:2&#x2F;128
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;The Client&amp;#39;s Public Key&amp;gt; 
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = 2001:db8:1234:ffff::1:3&#x2F;128
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is straightforward. &lt;code&gt;PublicKey&lt;&#x2F;code&gt; contains the client&#x27;s public key and &lt;code&gt;AllowedIPs&lt;&#x2F;code&gt; contains the IPv6 address we&#x27;re assigning to the client. Running &lt;code&gt;systemctl start wg-quick@wg-vpn.service&lt;&#x2F;code&gt; will bring the WireGuard interface up so clients can start connecting.&lt;&#x2F;p&gt;
&lt;p&gt;The configuration file for each client is simple:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Interface]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Address = 2001:db8:1234:ffff::1:1&#x2F;64
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PrivateKey = &amp;lt;The Client&amp;#39;s Private Key&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[Peer]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;AllowedIPs = ::&#x2F;0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Endpoint = [2001:db8:1234:9999::1]:51820
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PublicKey = &amp;lt;Your Server&amp;#39;s Public Key&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Address&lt;&#x2F;code&gt; is the globally routable IPv6 address our VPN server is assigning to the client. &lt;code&gt;PrivateKey&lt;&#x2F;code&gt; is the client&#x27;s private key. There&#x27;s similarly little to say about the &lt;code&gt;[Peer]&lt;&#x2F;code&gt; section, which contains connection information for the VPN server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;AllowedIPs = ::&#x2F;0&lt;&#x2F;code&gt; tells the client to route all IPv6 traffic through the WireGuard interface. &lt;code&gt;Endpoint&lt;&#x2F;code&gt; contains the IPv6 address clients use to connect to the VPN server. Note the square brackets. Because IPv6 addresses use &#x27;:&#x27; as a separator and &#x27;:&#x27; is also used by convention to separate an IP address from a port number, the square brackets are used to disambiguate the &#x27;:&#x27; in the IPv6 address from the &#x27;:&#x27; differentiating the port number.&lt;&#x2F;p&gt;
&lt;p&gt;When the client connects to the VPN server, it will have the globally routable IPv6 address of &lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt;. If you connect to a website that tests IPv6 connectivity such as &lt;a href=&quot;https:&#x2F;&#x2F;test-ipv6.run&#x2F;&quot;&gt;this IPv4 and IPv6 connectivity test&lt;&#x2F;a&gt;, it should show &lt;code&gt;2001:db8:1234:ffff::1:1&lt;&#x2F;code&gt; as your IPv6 address.&lt;&#x2F;p&gt;
&lt;p&gt;There you have it, a WireGuard VPN server that provides clients with globally routable IPv6 addresses.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
