wireguard-docs Documentation

Unofficial WireGuard Documentation: Setup, Usage, Configuration, and full example setups for VPNs supporting both servers & roaming clients.

RAW Doc

Technical Documentation: pirate/wireguard-docs

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)


WireGuard is an open-source VPN solution written in C by Jason Donenfeld and others, aiming to fix many of the problems that have plagued other modern server-to-server VPN offerings like IPSec/IKEv2, OpenVPN, or L2TP. It shares some similarities with other modern VPN offerings like Tinc and MeshBird, namely good cipher suites and minimal config. As of 2020-01 it's been merged into the 5.6 version of the Linux kernel, meaning it will ship with most Linux systems out-of-the-box.

Official Links

WireGuard Goals

  • strong, modern security by default
  • minimal config and key management
  • fast, both low-latency and high-bandwidth
  • simple internals and small protocol surface area
  • simple CLI and seamless integration with system networking

Table of Contents

See wireguard-docs for example code and documentation source.

  • [Table of Contents](#Table-of-Contents)
  • [Intro](#Intro)
  • [My Personal Requirements for a VPN Solution](#My-Personal-Requirements-for-a-VPN-Solution)
  • [List of Possible VPN Solutions](#List-of-Possible-VPN-Solutions)
  • [WireGuard Documentation](#WireGuard-Documentation)
  • [Glossary](#Glossary)
  • [Peer/Node/Device](#PeerNodeDevice)
  • [Bounce Server](#Bounce-Server)
  • [Subnet](#Subnet)
  • [CIDR Notation](#CIDR-Notation)
  • [NAT](#NAT)
  • [Public Endpoint](#Public-Endpoint)
  • [Private key](#Private-key)
  • [Public key](#Public-key)
  • [DNS](#DNS)
  • [Example Strings](#Example-Strings)
  • [How WireGuard Works](#How-WireGuard-Works)
  • [How Public Relay Servers Work](#How-Public-Relay-Servers-Work)
  • [How WireGuard Routes Packets](#How-WireGuard-Routes-Packets)
  • [What WireGuard Traffic Looks Like](#What-WireGuard-Traffic-Looks-Like)
  • [WireGuard Performance](#WireGuard-Performance)
  • [WireGuard Security Model](#WireGuard-Security-Model)
  • [How WireGuard Manages Keys](#How-WireGuard-Manages-Keys)
  • [Usage](#Usage)
  • [QuickStart](#QuickStart)
  • [Setup](#Setup)
  • [Config Creation](#Config-Creation)
  • [Key Generation](#Key-Generation)
  • [Start / Stop](#Start--Stop)
  • [Inspect](#Inspect)
  • [Testing](#Testing)
  • [Config Reference](#Config-Reference)
  • [[Interface]](#Interface)
  • [[Peer]](#Peer)
  • [Advanced Topics](#Advanced-Topics)
  • [IPv6](#IPv6)
  • [Forwarding All Traffic](#Forwarding-All-Traffic)
  • [NAT-to-NAT Connections](#NAT-to-NAT-Connections)
  • [Dynamic IP Allocation](#Dynamic-IP-Allocation)
  • [Platform Capture Technologies](#Platform-Capture-Technologies)
  • [Other WireGuard Implementations](#Other-WireGuard-Implementations)
  • [WireGuard Setup Tools](#WireGuard-Setup-tools)
  • [Config Shortcuts](#Config-Shortcuts)
  • [Containerization](#Containerization)
  • [Further Reading](#Further-Reading)
  • [Reference Docs](#Reference-Docs)
  • [Tutorials](#Tutorials)
  • [Papers, Articles, and Talks](#Papers-Articles-and-Talks)
  • [Related Projects](#Related-Projects)
  • [Docker](#Docker)
  • [Other](#Other)
  • [Discussions](#Discussions)
  • Intro

    Whether living behind the Great Wall of China or just trying to form a network between your servers, WireGuard is a great option and serves as a "lego block" for building networks (much in the same way that ZFS is a lego block for building filesystems).

    WireGuard Overview

    • minimal config, low tunable surface area and sane defaults
    • minimal key management work needed, just 1 public & 1 private key per host
    • behaves like a normal network interface, behaves well with standard kernel packet routing rules
    • ability to easily create a LAN like 192.0.2.0/24 between all servers, or more complex networks using custom routes
    • ability to route some traffic or all traffic to/through arbitrary hosts on the VPN LAN
    • robust automatic reconnects after reboots / network downtime / NAT connection table drops
    • fast (low latency and line-rate bandwidth)
    • modern encryption, secure by default with forward secrecy & resilience to downgrade attacks
    • supports normal IP traffic, including ICMP, not just TCP/HTTP
    • ability to join the VPN from Ubuntu, FreeBSD, iOS, macOS, Windows, Android, and some Chromebooks (via apps or native clients)
    • supports both running on the host routing traffic for docker or running in a docker container routing for the host

    Things WireGuard does not do:

    • form a self-healing mesh network where nodes automatically gossip with neighbors
    • include its own signalling/discovery layer for double-NAT traversal (WebRTC-style)
    • handle automatically distributing & revoking keys through a central authority
    • allow sending raw layer-2 ethernet frames (it's at the IP layer)

    But you can write your own solutions for these problems using WireGuard under the hood (like Tailscale or AltheaNet).

    List of Other VPN Solutions

    • WireGuard
    • IPSec (IKEv2)/strongSwan: in my experience, there was lots of brittle config that was different for each OS, the NAT busting setup is very manual and involves updating the central server and starting all the others in the correct order, it wasn't great at becoming stable again after network downtime, had to be manually restarted often. your mileage may vary.
    • OpenVPN: can work over UDP or be disguised as HTTPS traffic over TCP
    • StealthVPN: haven't tried it, should I?
    • DsVPN: I think it does TCP-over-TCP which usually doesn't end well...
    • SoftEther (SSTP): haven't tried it yet, should I? (also does TCP-over-TCP?)
    • L2TP: somewhat outdated
    • PPTP: ancient, inflexible, insecure, doesn't solve all the requirements
    • SOCKS/SSH: good for proxying single-port traffic, not a full networking tunnel or VPN

    Mesh VPN Solutions

    • TINC: haven't tried it yet, but it doesn't work on iOS, worst case scenario I could live
    • VPNCloud: similar properties to WireGuard, with more auto-mesh features
    • cjdns: haven't tried it yet, should I?
    • ZeroTier: haven't tried it yet, should I?
    • MeshBird: "Cloud native" VPN/networking layer
    • Yggdrasil Network: Yggdrasil is a self-arranging IPv4/IPv6 mesh VPN (haven't tried it yet)

    VPN Setup Tools


    WireGuard Documentation


    Glossary

    Example Strings

    These are demo hostnames, domain names, IP addresses, and ranges used in the documentation and example configs.
    Replace them with your preferred values when doing your own setup.

    • Example domain: example-vpn.dev can be replaced with any publicly accessible domain you control
    • Example hostnames: public-server1, public-server2, home-server, laptop, phone can be changed to your device hostnames
    • IP addresses & ranges: 192.0.2.1/24, 192.0.2.3, 192.0.2.3/32, 2001:DB8::/64 can be replaced with your preferred subnets and addresses (e.g. 192.168.5.1/24)

    Wherever you see these strings below, they're just being used as placeholder values to illustrate an example and have no special meaning.

    Make sure to change the IP addresses in your configs! The blocks used in these docs
    are reserved for example purposes by the IETF and should never be used in real network setups.

    • 192.0.2.0/24 (TEST-NET-1) IPv4 example range RFC5737
    • 2001:DB8::/32 IPv6 example range RFC3849

    You can use any private range you want for your own setups, e.g. 10.0.44.0/24, just make sure
    they don't conflict with any of the LAN subnet ranges your peers are on.

    Peer/Node/Device

    A host that connects to the VPN and registers a VPN subnet address such as 192.0.2.3 for itself. It can also optionally route traffic for more than its own address(es) by specifying subnet ranges in comma-separated CIDR notation.

    Bounce Server

    A publicly reachable peer/node that serves as a fallback to relay traffic for other VPN peers behind NATs. A bounce server is not a special type of server, it's a normal peer just like all the others, the only difference is that it has a public IP and has kernel-level IP forwarding turned on which allows it to bounce traffic back down the VPN to other clients.

    See more: How NAT traversal works (Tailscale uses WireGuard under the hood)

    Subnet

    A group of IPs separate from the public internet, e.g. 192.0.2.1-255 or 192.168.1.1/24. Generally behind a NAT provided by a router, e.g. in office internet LAN or a home Wi-Fi network.

    CIDR Notation

    A way of defining a subnet and its size with a "mask", a smaller mask = more address bits usable by the subnet & more IPs in the range. Most common ones:

    • 192.0.2.1/32 (a single IP address, 192.0.2.1) netmask = 255.255.255.255
    • 192.0.2.1/24 (256 addresses from 192.0.2.0-192.0.2.255) netmask = 255.255.255.0
    • 192.0.2.1/16 (65,536 IPs from 192.0.0.0 - 192.0.255.255) netmask = 255.255.0.0
    • 192.0.2.1/8 (16,777,216 IPs from 192.0.0.0 - 192.255.255.255) netmask = 255.0.0.0
    • 0.0.0.0/0 (4,294,967,296 addresses from 0.0.0.0 - 255.255.255.255) netmask = 0.0.0.0
    • IPv6 CIDR notation is also supported e.g. 2001:DB8::/64

    Classless Inter-Domain Routing (CIDR)

    To people just getting started 192.0.2.1/32 may seem like a weird and confusing way to refer to a single IP. This design is nice though because it allows peers to expose multiple IPs if needed without needing multiple notations. Just know that anywhere you see something like 192.0.2.3/32, it really just means 192.0.2.3.

    NAT

    A subnet with private IPs provided by a router standing in front of them doing Network Address Translation, individual nodes are not publicly accessible from the internet, instead the router keeps track of outgoing connections and forwards responses to the correct internal IP (e.g. standard office networks, home Wi-Fi networks, free public Wi-Fi networks, etc)

    Public Endpoint

    The publicly accessible address:port for a node, e.g. 123.124.125.126:1234 or some.domain.tld:1234 (must be accessible via the public internet, generally can't be a private IP like 10.0.0.1 or 192.168.1.1 unless it's directly accessible using that address by other peers on the same subnet).

    Private key

    A WireGuard private key for a single node, generated with:
    wg genkey > example.key
    (never leaves the node it's generated on)

    Public key

    A WireGuard public key for a single node, generated with:
    wg pubkey < example.key > example.key.pub
    (shared with other peers)

    DNS

    Domain Name Server, used to resolve hostnames to IPs for VPN clients, instead of allowing DNS requests to leak outside the VPN and reveal traffic. Leaks are testable with dnsleak.com.


    How WireGuard Works

    How Public Relay Servers Work

    Public relays are just normal VPN peers that are able to act as an intermediate relay server between any VPN clients behind NATs, they can forward any VPN subnet traffic they receive to the correct peer at the system level (WireGuard doesn't care how this happens, it's handled by kernel IP forwarding plus whatever firewall/NAT rules your host needs).

    If all peers are publicly accessible, you don't have to worry about special treatment to make one of them a relay server, it's only needed if you have any peers connecting from behind a NAT.

    Each client only needs to define the publicly accessible servers/peers in its config, any traffic bound to other peers behind NATs will go to the catchall VPN subnet (e.g. 192.0.2.1/24) in the public relays AllowedIPs route and will be forwarded accordingly once it hits the relay server.

    In summary: only direct connections between clients should be configured, any connections that need to be bounced should not be defined as peers, as they should head to the bounce server first and be routed from there back down the VPN to the correct client.

    Important: traffic bounced through a relay server is not end-to-end encrypted between the two NAT-ed peers. The relay server decrypts incoming traffic from one peer and re-encrypts it with the destination peer's key before forwarding. This means the relay server can see the plaintext VPN traffic passing through it. Each hop (Aโ†”Relay, Relayโ†”B) is independently encrypted, but the relay acts as a trusted intermediary that performs Cryptokey Routing to match the decrypted packet's destination IP to the correct peer. For more details, see the WireGuard whitepaper and Pro Custodibus's explanation of hub-and-spoke E2EE limitations. If end-to-end encryption between NAT-ed peers is required, consider using an application-level encryption layer (e.g. TLS) on top of WireGuard, or a solution like Tailscale's DERP relays which forward opaque encrypted packets without decrypting them.

    How WireGuard Routes Packets

    More complex topologies are definitely achievable, but these are the basic routing methods used in typical WireGuard setups:

    • Direct node-to-node
      In the simplest case, the nodes will either be on the same LAN or both be publicly accessible. Define directly accessible nodes with hardcoded Endpoint addresses and ports so that WireGuard can connect straight to the open port and route UDP packets without intermediate hops.
    • Node behind local NAT to public node
      When 1 of the 2 parties is behind remote NAT (e.g. when a laptop behind NAT connects to public-server2), define the publicly accessible node with a hardcoded Endpoint and the NAT-ed node without. The connection will be opened from NAT client -> public client, then traffic will route directly between them in both directions as long as the connection is kept alive by outgoing PersistentKeepalive packets from the NAT-ed client.
    • Node behind local NAT to node behind remote NAT (via relay)
      Most of the time when both parties are behind NATs, the NATs do source port randomization making direct connections infeasible, so they will both have to open a connection to public-server1, and traffic will forward through the intermediary bounce server as long as the connections are kept alive.
    • Node behind local NAT to node behind remote NAT (via UDP NAT hole-punching)
      While sometimes possible, it's generally infeasible to do direct NAT-to-NAT connections on modern networks, because many NAT routers randomize source ports or otherwise restrict unsolicited return traffic. A more advanced solution needs some external signaling/discovery component to tell each side what public IP:port tuple the other side currently appears to have, and even then success still depends on the NAT behavior of both networks. This is roughly the role played by STUN/ICE-style tooling in systems like WebRTC, but plain WireGuard does not include that machinery by itself. See the full section below on NAT to NAT Connections for more information.

    More specific (also usually more direct) routes provided by other peers will take precedence when available, otherwise traffic will fall back to the least specific route and use the 192.0.2.1/24 catchall to forward traffic to the bounce server, where it will in turn be routed by the relay server's system routing table (net.ipv4.ip_forward = 1) back down the VPN to the specific peer that's accepting routes for that traffic. WireGuard does not automatically find the fastest route or attempt to form direct connections between peers if not already defined, it just goes from the most specific route in [Peers] to least specific.

    You can figure out which routing method WireGuard is using for a given address by measuring the ping times to figure out the unique length of each hop, and by inspecting the output of:

    bash
    wg show wg0

    What WireGuard Traffic Looks Like

    WireGuard uses encrypted UDP packets for all traffic, it does not provide guarantees around packet delivery or ordering, as that is handled by TCP connections within the encrypted tunnel.

    Further reading:

    WireGuard Performance

    WireGuard claims faster performance than most other competing VPN solutions, though the exact numbers are sometimes debated and may depend on whether hardware-level acceleration is available for certain cryptographic ciphers.

    WireGuard's performance gains are achieved by handling routing at the kernel level, and by using modern cipher suites running on all cores to encrypt traffic. WireGuard also gains a significant advantage by using UDP with no delivery/ordering guarantees (compared to VPNs that run over TCP or implement their own guaranteed delivery mechanisms).

    Further reading:

    WireGuard Security Model

    WireGuard uses the following protocols and primitives to secure traffic:

    • ChaCha20 for symmetric encryption, authenticated with Poly1305, using RFC7539โ€™s AEAD construction
    • Curve25519 for ECDH
    • BLAKE2s for hashing and keyed hashing, described in RFC7693
    • SipHash24 for hashtable keys
    • HKDF for key derivation, as described in RFC5869

    WireGuard's cryptography is essentially an instantiation of Trevor Perrin's Noise framework. It's modern and, again, simple. Every other VPN option is a mess of negotiation and handshaking and complicated state machines. WireGuard is like the Signal/Axolotl of VPNs, except it's much simpler and easier to reason about (cryptographically, in this case) than double ratchet messaging protocols.
    It is basically the qmail of VPN software.
    And it's ~4000 lines of code. It is plural orders of magnitude smaller than its competitors.

    https://news.ycombinator.com/item?id=14599834

    Further reading:

    How WireGuard Manages Keys

    Authentication in both directions is achieved with a simple public/private key pair for each peer. Each peer generates these keys during the setup phase, and shares only the public key with other peers.

    No other certificates or pre-shared keys are needed beyond the public/private keys for each node.

    Key generation, distribution, and revocation can be handled in larger deployments using a separate service like Ansible or Kubernetes Secrets.

    Some services that help with key distribution and deployment:

    You can also read in keys from a file or via command if you don't want to hardcode them in wg0.conf, this makes managing keys via 3rd party service much easier:

    ini
    [Interface]
    ...
    PreUp = wg set %i private-key <(cat /some/path/%i/privkey)

    Multiple servers should not share the same private key. WireGuard's protocol assumes distinct peers use distinct private keys, and reusing one can cause replay-related problems and involuntary endpoint roaming between those machines. See the WireGuard whitepaper discussion of timestamp/replay behavior.
    Most of the time, every peer should have its own public/private keypair so that peers can be individually identified and revoked.


    Usage

    QuickStart

    Overview of the general process:

    1. Install apt install wireguard or pkg/brew install wireguard-tools on each node
    2. Generate public and private keys locally on each node wg genkey+wg pubkey
    3. Create a wg0.conf WireGuard config file on the main relay server
      • [Interface] Make sure to specify the local tunnel address(es) the server will use, e.g. Address = 192.0.2.1/24,2001:DB8::1/64 for a dual-stack VPN subnet
      • [Peer] Create a peer section for every client joining the VPN, using their corresponding remote public keys
    4. Create a wg0.conf on each client node
      • [Interface] Make sure to specify only the client peer's own tunnel IPs when it doesn't relay traffic, e.g. Address = 192.0.2.3/32,2001:DB8::3/128.
      • [Peer] Create a peer section for each public peer not behind a NAT, and make sure to use IPv4 and/or IPv6 CIDRs as appropriate when defining what should route through that peer, e.g. relay server AllowedIPs = 192.0.2.0/24,2001:DB8::/64 or simple client AllowedIPs = 192.0.2.3/32,2001:DB8::3/128.
    5. Start WireGuard on the main relay server with wg-quick up /full/path/to/wg0.conf
    6. Start WireGuard on all the client peers with wg-quick up /full/path/to/wg0.conf
    7. Traffic is routed from peer to peer using most specific route first over the WireGuard interface, e.g. ping 192.0.2.3 checks for a direct route to a peer with AllowedIPs = 192.0.2.3/32 first, then falls back to a relay server that's accepting IPs in the whole subnet

    Setup

    bash
    # install on Ubuntu
    sudo apt install wireguard
    
    # install on macOS
    brew install wireguard-tools
    
    # install on FreeBSD
    pkg install wireguard
    
    # install on iOS/Android using Apple App Store/Google Play Store
    # install on other systems using https://www.wireguard.com/install/#installation

    ChromeOS note: some Chromebooks have basic built-in WireGuard support in the VPN settings UI, where you can add a connection and select Provider type: WireGuard. ChromeOS also documents VPN.Type = WireGuard in its ONC format for managed or imported network configs. Under the hood, ChromiumOS implements this as a built-in WireGuard client backed by the kernel module and documents a kernel 5.4+ requirement. See Google's Chromebook VPN setup guide, the Chromium ONC spec, and the ChromiumOS VPN implementation notes.

    bash
    # to enable kernel forwarding on bounce servers
    echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
    echo "net.ipv6.conf.all.forwarding = 1" | sudo tee -a /etc/sysctl.conf
    # optional IPv4-specific proxy_arp example for proxy-ARP based topologies
    echo "net.ipv4.conf.all.proxy_arp = 1" | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p /etc/sysctl.conf
    
    # to add IPv4 forwarding/NAT rules on bounce servers
    sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    sudo iptables -A FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT
    sudo iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o eth0 -j MASQUERADE
    
    # to add IPv6 forwarding rules on bounce servers
    sudo ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    sudo ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    sudo ip6tables -A FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT
    # optional NAT66 example if you intentionally want IPv6 masquerading
    # sudo ip6tables -t nat -A POSTROUTING -s 2001:DB8::/64 -o eth0 -j MASQUERADE

    Config Creation

    bash
    nano wg0.conf  # can be placed anywhere as long as the filename matches the interface name (often /etc/wireguard/wg0.conf)

    Key Generation

    bash
    # generate private key
    wg genkey > example.key
    
    # generate public key
    wg pubkey < example.key > example.key.pub

    Start / Stop

    bash
    wg-quick up /full/path/to/wg0.conf
    wg-quick down /full/path/to/wg0.conf
    # Note: CONFIG_FILE can be in any directory, but its filename must still match the interface name, e.g. ./wg0.conf or /full/path/to/wg0.conf
    # If wg0.conf is in /etc/wireguard you can use the simpler:
    wg-quick up wg0
    bash
    # start/stop VPN network interface
    ip link set wg0 up
    ip link set wg0 down
    
    # register/unregister VPN network interface
    ip link add dev wg0 type wireguard
    ip link delete dev wg0
    
    # register/unregister local VPN address
    ip address add dev wg0 192.0.2.3/32
    ip address delete dev wg0 192.0.2.3/32
    
    # register/unregister VPN route
    ip route add 192.0.2.3/32 dev wg0
    ip route delete 192.0.2.3/32 dev wg0

    Inspect

    Interfaces

    bash
    # show system LAN and WAN network interfaces
    ip address show
    # or if ip is not available:
    ifconfig
    
    # show system VPN network interfaces
    ip link show wg0
    # or
    ifconfig wg0
    
    # show WireGuard VPN interfaces
    wg show all
    wg show wg0

    Addresses

    bash
    # show public IP address
    ip address show eth0
    # or
    ifconfig eth0
    # or
    dig -4 +short myip.opendns.com @resolver1.opendns.com
    
    # show VPN IP address
    ip address show wg0

    Routes

    bash
    # show WireGuard routing table and peer connections
    wg show
    wg show wg0 allowed-ips
    
    # show system routing table
    ip route show table main
    ip route show table local
    
    # show system route to specific address
    ip route get 192.0.2.3

    Logs

    To enable additional logging run:

    bash
    modprobe wireguard
    echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control

    To follow logs:

    bash
    dmesg -wH

    Testing

    Ping Speed

    bash
    # check that the main relay server is accessible directly via public internet
    ping public-server1.example-vpn.dev
    
    # check that the main relay server is available via VPN
    ping 192.0.2.1
    
    # check that public peers are available via VPN
    ping 192.0.2.2
    
    # check that remote NAT-ed peers are available via VPN
    ping 192.0.2.3
    
    # check that NAT-ed peers in your local LAN are available via VPN
    ping 192.0.2.4

    Bandwidth

    bash
    # install iperf using your preferred package manager
    apt/brew/pkg/opkg install iperf
    
    # check bandwidth over public internet to relay server
    iperf -s # on public relay server
    iperf -c public-server1.example-vpn.dev # on local client
    
    # check bandwidth over VPN to relay server
    iperf -s # on public relay server
    iperf -c 192.0.2.1 # on local client
    
    # check bandwidth over VPN to remote public peer
    iperf -s # on remote public peer
    iperf -c 192.0.2.2 # on local client
    
    # check bandwidth over VPN to remote NAT-ed peer
    iperf -s # on remote NAT-ed peer
    iperf -c 192.0.2.3 # on local client
    
    # check bandwidth over VPN to local NAT-ed peer (on same LAN)
    iperf -s # on local NAT-ed peer
    iperf -c 192.0.2.4 # on local client

    DNS

    Check for DNS leaks using dnsleak.com, or by checking the resolver on a lookup:

    bash
    dig example.com A

    Config Reference

    Overview

    WireGuard config is in INI syntax, defined in a file usually called wg0.conf. It can be placed anywhere on the system, but is often placed in /etc/wireguard/wg0.conf.

    The config path is specified as an argument when running any wg-quick command, e.g:
    wg-quick up /etc/wireguard/wg0.conf

    The config file name must be in the format ${name of the new WireGuard interface}.conf. WireGuard interface names are typically prefixed with wg and numbered starting at 0, but you can use any name that matches the regex ^[a-zA-Z0-9_=+.-]{1,15}$.

    Config files can opt to use the limited set of wg config options, or the more extended wg-quick options, depending on what command is preferred to start WireGuard. These docs recommend sticking to wg-quick as it provides a more powerful and user-friendly config experience.

    The core wg(8) config format covers peer/session state such as PrivateKey, ListenPort, FwMark, PublicKey, PresharedKey, AllowedIPs, Endpoint, and PersistentKeepalive. The convenience keys Address, DNS, Table, MTU, PreUp, PostUp, PreDown, and PostDown below are wg-quick(8) extensions. See wg(8) and wg-quick(8).

    Jump to definition:

    ยถ [Interface]
    ยถ # Name = node1.example.tld
    ยถ Address = 192.0.2.3/32 (wg-quick only)
    ยถ ListenPort = 51820
    ยถ PrivateKey = localPrivateKeyAbcAbcAbc=
    ยถ DNS = 1.1.1.1,8.8.8.8 (wg-quick only)
    ยถ Table = 12345 (wg-quick only)
    ยถ MTU = 1420 (wg-quick only)
    ยถ PreUp = /bin/example arg1 arg2 %i (wg-quick only)
    ยถ PostUp = /bin/example arg1 arg2 %i (wg-quick only)
    ยถ PreDown = /bin/example arg1 arg2 %i (wg-quick only)
    ยถ PostDown = /bin/example arg1 arg2 %i (wg-quick only)

    ยถ [Peer]
    ยถ # Name = node2-node.example.tld
    ยถ AllowedIPs = 192.0.2.1/24
    ยถ Endpoint = node1.example.tld:51820
    ยถ PublicKey = remotePublicKeyAbcAbcAbc=
    ยถ PersistentKeepalive = 25

    `[Interface]`

    Defines the VPN settings for the local node.

    Examples

    • Node is a client that only routes traffic for itself and only exposes one IP
    ini
    [Interface]
    # Name = phone.example-vpn.dev
    Address = 192.0.2.5/32
    PrivateKey = <private key for phone.example-vpn.dev>
    • Node is a public bounce server that can relay traffic to other peers and exposes route for entire VPN subnet
    ini
    [Interface]
    # Name = public-server1.example-vpn.tld
    Address = 192.0.2.1/24
    ListenPort = 51820
    PrivateKey = <private key for public-server1.example-vpn.tld>
    DNS = 1.1.1.1

    `# Name`

    This is just a standard comment in INI syntax used to help keep track of which config section belongs to which node, it's completely ignored by WireGuard and has no effect on VPN behavior.

    NOTE: All comments, including # Name, are removed from the .conf files by certain operations and applications.
    If you need to identify peers, consider using a wireguard vanity key generator, such as
    wireguard-vanity-keygen or
    wireguard-vanity-address,
    which will allow you to include the host name in the public key of the host.
    The key generation can take minutes (4 characters), hours (5 characters) or longer,
    so consider using an abbreviation for hosts with longer names.

    `Address`

    Defines what address(es) the local WireGuard interface should use. Depending on whether the node is a simple client joining the VPN subnet, or a bounce server that's relaying traffic between multiple clients, this can be set to a single IP of the node itself (specified with CIDR notation), e.g. 192.0.2.3/32 or 2001:DB8::3/128, or to multiple IPv4/IPv6 addresses on the same interface.

    Examples

    • Node is a client that only routes traffic for itself
      Address = 192.0.2.3/32

    • IPv6-only client address: Address = 2001:DB8::3/128

    • Node is a public bounce server that can relay traffic to other peers
      When the node is acting as the public bounce server, it should use an address inside the VPN subnet together with the subnet prefix length it routes, not just a /32 for itself.

    Address = 192.0.2.1/24

    • Dual-stack example with both IPv4 and IPv6: Address = 192.0.2.1/24,2001:DB8::1/64

    `ListenPort`

    When the node is acting as a public bounce server, it should hardcode a port to listen for incoming VPN connections from the public internet. Clients that are not acting as relays usually do not need to set this value, but they still can if they want a fixed local UDP port.

    Examples

    • Using default WireGuard port
      ListenPort = 51820
    • Using custom WireGuard port
      ListenPort = 7000

    `PrivateKey`

    This is the private key for the local node, never shared with other servers.
    All nodes must have a private key set, regardless of whether they are public bounce servers relaying traffic, or simple clients joining the VPN.

    This key can be generated with wg genkey > example.key

    Examples

    PrivateKey = somePrivateKeyAbcdAbcdAbcdAbcd=

    `DNS`

    This is a wg-quick convenience option for the local machine, not a WireGuard setting that is announced or pushed to peers. In wg-quick, IP entries are applied as DNS servers for the local interface via resolvconf, and non-IP entries are treated as DNS search domains. See wg-quick(8) and the WireGuard for Windows parser.

    Examples

    • The value can be left unconfigured to use the system's default DNS handling
    • A single DNS server can be provided: DNS = 1.1.1.1
    • Multiple DNS servers can be provided: DNS = 1.1.1.1,8.8.8.8
    • A DNS server and search domain can be provided: DNS = 10.0.0.2, internal.example.com

    `Table`

    Optionally defines which routing table to use for the WireGuard routes, not necessary to configure for most setups.

    There are two special values: โ€˜offโ€™ disables the creation of routes altogether, and โ€˜autoโ€™ (the default) adds routes to the default table and enables special handling of default routes.

    See wg-quick(8).

    Examples

    ini
    Table = 1234

    `MTU`

    Optionally defines the maximum transmission unit (MTU, aka packet/frame size) to use when connecting to the peer, not necessary to configure for most setups.

    The MTU is automatically determined from the endpoint addresses or the system default route, which is usually a sane choice.

    You should avoid setting this if at all possible. You almost certainly do not want 1500 here due to the encapsulation overhead.

    See wg-quick(8).

    Examples

    ini
    MTU = 1420

    `PreUp`

    Optionally run a command before the interface is brought up.
    This option can be specified multiple times, with commands executed in the order they appear in the file.

    Examples

    • Add an IP route
      PreUp = ip rule add ipproto tcp dport 22 table 1234

    `PostUp`

    Optionally run a command after the interface is brought up.
    This option can appear multiple times, as with PreUp

    Examples

    • Read in a config value from a file or some command's output
      PostUp = wg set %i private-key <(some command here)

    • Log a line to a file
      PostUp = echo "$(date +%s) WireGuard Started" >> /var/log/wireguard.log

    • Hit a webhook on another server
      PostUp = curl https://events.example.dev/wireguard/started/?key=abcdefg

    • Add a route to the system routing table
      PostUp = ip rule add ipproto tcp dport 22 table 1234

    • Add an iptables rule to enable packet forwarding on the WireGuard interface
      PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

    • Configure systemd-resolved DNS settings for this interface
      PostUp = resolvectl domain %i "~."; resolvectl dns %i 192.0.2.1; resolvectl dnssec %i yes

    `PreDown`

    Optionally run a command before the interface is brought down.
    This option can appear multiple times, as with PreUp

    Examples

    • Log a line to a file
      PreDown = echo "$(date +%s) WireGuard Going Down" >> /var/log/wireguard.log

    • Hit a webhook on another server
      PreDown = curl https://events.example.dev/wireguard/stopping/?key=abcdefg

    `PostDown`

    Optionally run a command after the interface is brought down.
    This option can appear multiple times, as with PreUp

    Examples

    • Log a line to a file
      PostDown = echo "$(date +%s) WireGuard Stopped" >> /var/log/wireguard.log

    • Hit a webhook on another server
      PostDown = curl https://events.example.dev/wireguard/stopped/?key=abcdefg

    • Remove the iptables rule that forwards packets on the WireGuard interface
      PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

    `[Peer]`

    Defines the VPN settings for a remote peer capable of routing traffic for one or more addresses (itself and/or other peers). Peers can be either a public bounce server that relays traffic to other peers, or a directly accessible client via LAN/internet that is not behind a NAT and only routes traffic for itself.

    In the simple relay topology used throughout this README, all clients are defined as peers on the public bounce server. Simple clients that only route traffic for themselves only need to define peers for the public relay and any other nodes that are directly reachable. Nodes behind separate NATs are usually not defined as peers of each other in this topology, because traffic between them is expected to go through the public relay instead. In that setup, NAT-ed nodes point their relay peer at a broader subnet such as AllowedIPs = 192.0.2.1/24 on the public server. Traffic destined for a NAT-ed peer that is not directly defined will match the relay server's broad AllowedIPs range, be encrypted to the relay server, and then be decrypted and re-encrypted by the relay for delivery to the final destination peer (see the security note above about relay traffic not being end-to-end encrypted).

    In summary, all nodes must be defined on the main bounce server. On client nodes, only peers that are directly accessible from a node should be defined as peers of that node, and any peers that must be relayed by a bounce server should be left out and handled by the relay server's catchall route.

    In the configuration outlined in the docs below, a single server public-server1 acts as the relay bounce server for a mix of publicly accessible and NAT-ed clients, and peers are configured on each node accordingly:

    • in public-server1 wg0.conf (bounce server)
      [peer] list: public-server2, home-server, laptop, phone

    • in public-server2 wg0.conf (simple public client)
      [peer] list: public-server1

    • in home-server wg0.conf (simple client behind NAT)
      [peer] list: public-server1, public-server2

    • in laptop wg0.conf (simple client behind NAT)
      [peer] list: public-server1, public-server2

    • in phone wg0.conf (simple client behind NAT)
      [peer] list: public-server1, public-server2

    Examples

    • Peer is a simple public client that only routes traffic for itself
    ini
    [Peer]
    # Name = public-server2.example-vpn.dev
    Endpoint = public-server2.example-vpn.dev:51820
    PublicKey = <public key for public-server2.example-vpn.dev>
    AllowedIPs = 192.0.2.2/32
    • Peer is a simple client behind a NAT that only routes traffic for itself
    ini
    [Peer]
    # Name = home-server.example-vpn.dev
    PublicKey = <public key for home-server.example-vpn.dev>
    AllowedIPs = 192.0.2.3/32
    • Peer is a public bounce server that can relay traffic to other peers
    ini
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25

    `# Name`

    This is just a standard comment in INI syntax used to help keep track of which config section belongs to which node, it's completely ignored by WireGuard and has no effect on VPN behavior.

    `Endpoint`

    Defines the publicly accessible address for a remote peer. This should be left out for peers behind a NAT or peers that don't have a stable publicly accessible IP:PORT pair. Typically, this only needs to be defined on the main bounce server, but it can also be defined on other public nodes with stable IPs like public-server2 in the example config below. IPv6 literal endpoints are supported, but they must be wrapped in square brackets. After a packet is received from a peer, WireGuard also updates the stored endpoint to that peer's most recent authenticated source IP:PORT.

    Examples

    • Endpoint is an IP address
      Endpoint = 123.124.125.126:51820 (IPv6 is also supported)
    • Endpoint is an IPv6 address: Endpoint = [2001:DB8::1]:51820
    • Endpoint is a hostname/FQDN
      Endpoint = public-server1.example-vpn.tld:51820

    `AllowedIPs`

    This defines which IP prefixes the local node associates with a peer in WireGuard's cryptokey routing table. When sending, the local node uses the destination IP to choose which peer to encrypt to. When receiving, it only accepts a decrypted packet from that peer if the packet's source IP matches one of that peer's AllowedIPs. This mapping is local to the machine that owns the config; it is not dynamically advertised to other peers. See WireGuard's cryptokey routing overview and the WireGuard whitepaper.

    On simple clients, this is usually a single address (the VPN address of the peer itself) or a small routed prefix. On bounce servers, this may be a broader range of IPs or subnets that should be routed back to that peer. Multiple IPs and subnets may be specified using comma-separated IPv4 or IPv6 CIDR notation (from a single /32 or /128 address, all the way up to 0.0.0.0/0 and ::/0 to indicate a default route to send all internet and VPN traffic through that peer). If you need "internet passthrough but no peer-to-peer access", treat AllowedIPs as address mapping and source validation, and enforce any additional forwarding policy separately on the machine that would route the traffic.

    When deciding how to route a packet, the system chooses the most specific route first, and falls back to broader routes. So for a packet destined to 192.0.2.3, the system would first look for a peer configured with 192.0.2.3/32 specifically, and would fall back to a peer configured with 192.0.2.1/24 or a larger range like 0.0.0.0/0 as a last resort.

    Examples

    • peer is a simple client that only represents its own tunnel IP: AllowedIPs = 192.0.2.3/32

    • peer is a simple client with an IPv6 tunnel address: AllowedIPs = 2001:DB8::3/128

    • peer is a relay server that can bounce VPN traffic to all other peers AllowedIPs = 192.0.2.1/24

    • peer is a relay server for a dual-stack VPN subnet: AllowedIPs = 192.0.2.0/24,2001:DB8::/64

    • peer is a relay server that bounces all internet & VPN traffic (like a proxy), including IPv6
      AllowedIPs = 0.0.0.0/0,::/0

    • peer is a relay server that routes to itself and only one other peer
      AllowedIPs = 192.0.2.3/32,192.0.2.4/32

    • peer is a relay server that routes to itself and all nodes on its local LAN
      AllowedIPs = 192.0.2.3/32,192.168.1.1/24

    `PublicKey`

    This is the public key for the remote node, shareable with all peers.
    All nodes must have a public key set, regardless of whether they are public bounce servers relaying traffic, or simple clients joining the VPN.

    This key can be generated with wg pubkey < example.key > example.key.pub.
    (see above for how to generate the private key example.key)

    Examples

    PublicKey = somePublicKeyAbcdAbcdAbcdAbcd=

    `PersistentKeepalive`

    If the connection is going from a NAT-ed peer to a public peer, the node behind the NAT must regularly send an outgoing keepalive packet in order to keep the bidirectional connection alive in the NAT router's connection table.

    Examples

    • local public node to remote public node
      This value should be left undefined as persistent pings are not needed.

    • local public node to remote NAT-ed node
      This value should be left undefined as it's the client's responsibility to keep the connection alive because the server cannot reopen a dead connection to the client if it times out.

    • local NAT-ed node to remote public node
      PersistentKeepalive = 25 this will send an authenticated empty keepalive packet every 25 seconds, keeping the connection open in the local NAT router's connection table.


    Advanced Topics

    IPv6

    The examples in these docs primarily use IPv4, but WireGuard is layer-3 only and natively supports both IPv4 and IPv6, including v4-in-v6 and v6-in-v4. In practice, the important patterns are:

    • Address accepts IPv6 interface addresses such as 2001:DB8::3/128
    • AllowedIPs accepts IPv6 CIDRs such as 2001:DB8::/64 and the IPv6 default route ::/0
    • Endpoint accepts IPv6 literals, but they must be wrapped in square brackets, e.g. [2001:DB8::1]:51820
    • the proxy_arp line shown in the Setup section is IPv4-specific; IPv6 forwarding uses net.ipv6.conf.all.forwarding = 1

    See the WireGuard whitepaper, wg(8), and wg-quick(8).

    Example

    ini
    [Interface]
    Address = 192.0.2.3/32, 2001:DB8::3/128
    
    [Peer]
    ...
    AllowedIPs = 0.0.0.0/0, ::/0

    Forwarding All Traffic

    If you want to forward all internet traffic through the VPN, and not just use it as a server-to-server subnet, you can add 0.0.0.0/0, ::/0 to the AllowedIPs definition of the peer you want to pipe your traffic through.

    If the client also has IPv6 connectivity and you want all traffic to go through the VPN, include the IPv6 catchall ::/0 too. Otherwise, IPv6 traffic can still bypass the tunnel even if IPv4 is routed through it.

    Example

    ini
    [Interface]
    # Name = phone.example-vpn.dev
    Address = 192.0.2.3/32
    PrivateKey = <private key for phone.example-vpn.dev>
    
    [Peer]
    # Name = public-server1.example-vpn.dev
    PublicKey = <public key for public-server1.example-vpn.dev>
    Endpoint = public-server1.example-vpn.dev:51820
    AllowedIPs = 0.0.0.0/0, ::/0

    NAT To NAT Connections

    Plain WireGuard configs can sometimes make direct connections between two clients behind NATs without the need for a public relay server, but there is no built-in signaling or discovery layer to find the right public IP:PORT pair automatically. In practice, manual NAT-to-NAT setups only work when the peers already know usable public IP:PORT information ahead of time, or when an external signaling/discovery tool provides it.

    A known port and address need to be configured ahead of time for the static/manual approach described here. WebRTC is an example of a protocol that can dynamically configure a connection between two NATs, but it does this by using an out-of-band signaling server to detect the IP:PORT combo of each host. WireGuard itself doesn't do that, so the plain wg/wg-quick setup below only works with a hardcoded Endpoint + ListenPort (and PersistentKeepalive so it doesn't drop after inactivity).

    See the official wireguard-tools nat-hole-punching example for a manual setup.

    Requirements for NAT-to-NAT setups

    • For the static/manual approach, at least one peer has to have a usable Endpoint that can be configured ahead of time. If neither side has one, you'll need an external discovery/signaling solution or a relay server
    • At least one peer has to have a hardcoded UDP ListenPort defined, and its NAT router must not do UDP source port randomization, otherwise return packets will be sent to the hardcoded ListenPort and dropped by the router instead of using the random port assigned by the NAT on the outgoing packet
    • All NAT'ed peers must have PersistentKeepalive enabled on all other peers, so that they continually send outgoing keepalive packets to keep connections persisted in their NAT's routing table

    The hole-punching connection process

    1. Peer1 sends a UDP packet to Peer2, it's rejected by Peer2's NAT router immediately, but that's ok, the only purpose was to get Peer1's NAT to start forwarding any expected UDP responses back to Peer1 behind its NAT
    2. Peer2 sends a UDP packet to Peer1, it's accepted and forwarded to Peer1 as Peer1's NAT server is already expecting responses from Peer2 because of the initial outgoing packet
    3. Peer1 sends a UDP response to Peer2's packet, it's accepted and forwarded by Peer2's NAT server as it's also expecting responses because of the initial outgoing packet

    This process of sending an initial packet that gets rejected, then using the fact that the router has now created a forwarding rule to accept responses is called "UDP hole-punching".

    When you send a UDP packet out, the router (usually) creates a temporary rule mapping your source address and port to the destination address and port, and vice versa. UDP packets returning from the destination address and port (and no other) are passed through to the original source address and port (and no other). This is how most UDP applications function behind NATs (e.g. BitTorrent, Skype, etc). This rule will timeout after some minutes of inactivity, so the client behind the NAT must send regular outgoing packets to keep it open (see PersistentKeepalive).

    Getting this to work when both end-points are behind NATs or firewalls requires that both end-points send packets to each-other at about the same time. This means that both sides need to know each-other's public IP addresses and port numbers ahead of time, in WireGuard's case this is achieved by hard-coding pre-defined ports for both sides in wg0.conf.

    Drawbacks and limitations

    Many of the older hole-punching methods described in old blog posts are no longer effective on modern networks. One example was a novel method pioneered by pwnat that faked an ICMP Time Exceeded response from outside the NAT to get a packet back through to a NAT'ed peer, thereby leaking its own source port. Hardcoding UDP ports and public IPs for both sides of a NAT-to-NAT connection (as described above) still works on a small percentage of networks. Generally the more "enterprisey" a network is, the less likely you'll be able to hole punch public UDP ports (commercial public Wi-Fi and cell data NATs often don't work for example).

    Source port randomization

    The static/manual approach described here is generally not possible if all endpoints are behind NATs with strict UDP source port randomization (for example, many cellular data networks). Since neither side is able to hardcode a ListenPort and guarantee that its NAT will accept traffic on that port after the outgoing packet, you cannot coordinate a port for the initial hole-punch between peers and the connection will fail. For this reason, phone-to-phone connections on LTE/3G are usually not feasible with this simple approach, but phone-to-office or phone-to-home may still work when the office or home has a stable public IP and doesn't do source port randomization.

    Using a signaling server

    A signaling server can improve the odds by telling each side the other's current public IP:PORT tuple, but it still depends on the NAT behavior of both networks. The official wireguard-tools nat-hole-punching example demonstrates the basic approach.

    Dynamic IP addresses

    If a peer's public IP changes, WireGuard can learn the new endpoint from authenticated packets sent by that peer, but DNS hostnames are not continuously polled in the background. If you depend on Dynamic DNS Endpoint hostnames being refreshed proactively, you may need external tooling; the official wireguard-tools repo includes reresolve-dns.sh for exactly this purpose.

    Testing it out

    You can see if a hole-punching setup is feasible by using netcat on the client and server to see what ports and connection order work to get a bidirectional connection open: run nc -v -u -p 51820 <address of peer2> 51820 (on peer1) and nc -v -u -l 0.0.0.0 51820 (on peer2), then type in both windows to see if you can get bidirectional traffic going. If it doesn't work regardless of which peer sends the initial packet, then WireGuard will be unable to work between the peers without a public relay server.

    NAT-to-NAT connections are often more unstable and have other limitations, which is why having a fallback public relay server is still advised.

    Further reading

    Example

    Peer1:

    ini
    [Interface]
    ...
    ListenPort = 12000
    
    [Peer]
    ...
    Endpoint = peer2.example-vpn.dev:12000
    PersistentKeepalive = 25

    Peer2:

    ini
    [Interface]
    ...
    ListenPort = 12000
    
    [Peer]
    ...
    Endpoint = peer1.example-vpn.dev:12000
    PersistentKeepalive = 25

    Dynamic IP Allocation

    Note: this section is about dynamic peer IPs within the VPN subnet, not dynamic public Endpoint addresses.

    Dynamic allocation of peer IPs (instead of only having fixed peers) is being developed, the WIP implementation is available here:
    wg-dynamic

    You can also build a dynamic allocation system yourself by reading in IP values from files at runtime by using PostUp (see below).

    Example

    ini
    [Interface]
    ...
    PostUp = wg set %i peer "$(cat /etc/wireguard/peer1.pub)" allowed-ips "$(cat /etc/wireguard/peer1.allowedips)"

    Platform Capture Technologies

    When people ask what WireGuard uses for packet capture, the answer depends on the platform and backend. The WireGuard protocol stays the same, but the local interface plumbing is different on each OS.

    So "Windows uses Wintun" and "Linux uses /dev/net/tun" are accurate for the generic userspace backend, but they do not describe the default official Windows client or a standard Linux kernel deployment. See the upstream Windows app README, the Linux quick start, and the wireguard-go TUN sources for Linux and Windows.

    Other WireGuard Implementations

    Userspace implementations are generally slower than native kernel implementations, but they provide other benefits (e.g. easier containerization, portability, compatibility on platforms without a kernel module, etc.).

    WireGuard Setup Tools

    These are some GUI and CLI tools that wrap WireGuard to assist with config, deployment, key management, and connection.

    Config Shortcuts

    Credit for these shortcuts goes to:
    New Things I Didn't Know About Wireguard

    Sharing a single peers.conf file

    WireGuard will ignore a peer whose public key matches the interface's private key. So you can distribute a single list of peers everywhere, and only define the [Interface] separately on each server.

    See: WireGuard mailing list discussion

    You can combine this with wg addconf like this:

    • Each peer has its own /etc/wireguard/wg0.conf file, which only contains its [Interface] section.

    • Each peer also has a shared /etc/wireguard/peers.conf file, which contains all the peers.

    • The wg0.conf file also has a PostUp hook: PostUp = wg addconf %i /etc/wireguard/peers.conf.

    It's up to you to decide how you want to share the peers.conf, be it via a proper orchestration platform, something much more pedestrian like Dropbox, or something kinda wild like Ceph. I dunno, but it's pretty great that you can just wildly fling a peer section around, without worrying whether it's the same as the interface.

    Setting config values from files or command outputs

    You can set config values from arbitrary commands or by reading in values from files, this makes key management and deployment much easier as you can read in keys at runtime from a 3rd party service like Kubernetes Secrets or AWS KMS.

    See: WireGuard mailing list discussion

    Example

    You can read in a file as the PrivateKey by doing something like:

    PreUp = wg set %i private-key /etc/wireguard/wg0.key

    Containerization

    WireGuard can be run in Docker with varying degrees of ease. In the simplest case, --privileged and --cap-add=all arguments can be added to the docker commands to enable the loading of the kernel module.

    Setups can get somewhat complex and are highly dependent on what you're trying to achieve. You can have WireGuard itself run in a container and expose a network interface to the host, or you can have WireGuard running on the host exposing an interface to specific containers.

    See below for an example of a Docker container vpn_test routing all its traffic through a WireGuard relay server.

    Example Relay Server Setup

    yaml
    version: '3'
    
    services:
      wireguard:
        image: linuxserver/wireguard
        ports:
          - 51820:51820/udp
        cap_add:
          - NET_ADMIN
          - SYS_MODULE
        volumes:
          - /lib/modules:/lib/modules
          - ./wg0.conf:/config/wg0.conf:ro

    wg0.conf:

    ini
    [Interface]
    # Name = relay1.wg.example.com
    Address = 192.0.2.1/24
    ListenPort = 51820
    PrivateKey = oJpRt2Oq27vIB5/UVb7BRqCwad2YMReQgH5tlxz8YmI=
    DNS = 1.1.1.1,8.8.8.8
    PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0  -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
    PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
    
    [Peer]
    # Name = peer1.wg.example.com
    PublicKey = I+hXRAJOG/UE2IQvIHsou2zTgkUyPve2pzvHTnd/2Gg=
    AllowedIPs = 192.0.2.2/32

    Example Client Container Setup

    In this example all the traffic from inside the vpn_test container will go through the wireguard VPN.
    To only route some traffic, replace 0.0.0.0/0 in wg0.conf below with the subnet ranges you want to route via the VPN.

    docker-compose.yml:

    yaml
    version: '3'
    
    services:
      wireguard:
        image: linuxserver/wireguard
        cap_add:
          - NET_ADMIN
          - SYS_MODULE
        volumes:
          - /lib/modules:/lib/modules
          - ./wg0.conf:/config/wg0.conf:ro
        
      vpn_test:
        image: curlimages/curl
        entrypoint: curl -s http://whatismyip.akamai.com/
        network_mode: 'service:wireguard'

    wg0.conf:

    ini
    [Interface]
    # Name = peer1.wg.example.com
    Address = 192.0.2.2/32
    PrivateKey = YCW76edD4W7nZrPbWZxPZhcs32CsBLIi1sEhsV/sgk8=
    DNS = 1.1.1.1,8.8.8.8
    
    [Peer]
    # Name = relay1.wg.example.com
    Endpoint = relay1.wg.example.com:51820
    PublicKey = zJNKewtL3gcHdG62V3GaBkErFtapJWsAx+2um0c0B1s=
    AllowedIPs = 192.0.2.1/24,0.0.0.0/0
    PersistentKeepalive = 21

    For more details see the Further Reading: Docker section below.

    Example Firecracker MicroVM Setup

    If you want to run application code inside a Firecracker microVM, the simplest stable pattern is usually to keep WireGuard on the host, attach the microVM to the host with a TAP device, and route a dedicated subnet from wg0 into the guest. Firecracker networking is host-managed: the Firecracker Go SDK supports Linux TAP devices or CNI-created TAP-backed interfaces, and firectl exposes this directly via --tap-device. wg-quick only provides interface lifecycle hooks (PreUp, PostUp, PreDown, PostDown), so "launch a VM when a peer connects" requires a separate control-plane or supervisor, not just a WireGuard config. Firecracker Go SDK networking docs / firectl / wg-quick(8)

    If you want to study a more opinionated end-to-end design, distvirt is an experimental project that combines Firecracker microVMs with WireGuard ingress and on-demand activation.

    Host setup:

    bash
    ip tuntap add dev fc-tap0 mode tap
    ip addr add 172.31.200.1/24 dev fc-tap0
    ip link set fc-tap0 up
    
    sysctl -w net.ipv4.ip_forward=1
    
    iptables -A FORWARD -i wg0 -o fc-tap0 -j ACCEPT
    iptables -A FORWARD -i fc-tap0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

    If you also want the guest to reach the public internet through the host, add a NAT rule on the host's external interface:

    bash
    iptables -t nat -A POSTROUTING -s 172.31.200.0/24 -o eth0 -j MASQUERADE

    Launch the microVM:

    bash
    firectl \
      --kernel=vmlinux \
      --root-drive=rootfs.ext4 \
      --tap-device=fc-tap0/06:00:ac:10:c8:02

    Guest setup:

    bash
    ip addr add 172.31.200.2/24 dev eth0
    ip link set eth0 up
    ip route add default via 172.31.200.1

    WireGuard client route to the guest subnet:

    ini
    [Peer]
    Endpoint = relay1.wg.example.com:51820
    PublicKey = zJNKewtL3gcHdG62V3GaBkErFtapJWsAx+2um0c0B1s=
    AllowedIPs = 192.0.2.1/24,172.31.200.0/24
    PersistentKeepalive = 21

    That keeps WireGuard termination and peer authentication on the host while forwarding decrypted traffic into the Firecracker guest. If you want all client traffic to go through the guest, replace the guest subnet above with 0.0.0.0/0 (and ::/0 for IPv6) and make the host/guest forwarding and NAT policy explicit.


    Further Reading

    Reference Docs

    Tutorials

    Papers, Articles, and Talks

    Docker

    Other

    Discussions

    For more detailed instructions, see the QuickStart guide and API reference above. You can also download the complete example setup here: wireguard-example.


    Suggest changes: wireguard-docs issues


    Overview (`example-full/README.md`)

    Example Server-To-Server Config with Roaming Devices

    WARNING: Make sure to change the IP addresses and ranges in your configs before running!
    The blocks used in these examples are reserved for documentation purposes by the IETF and should never be used in real network setups.

    • 192.0.2.0/24 (TEST-NET-1) IPv4 example range RFC5737
    • 2001:DB8::/32 IPv6 example range RFC3849

    You can use any private range you want instead, e.g. 10.0.44.0/24, just make sure
    it doesn't conflict with any of the LAN subnet ranges your peers are on.

    The complete example config for the setup below can be found here: https://github.com/pirate/wireguard-docs/tree/master/full-example (WARNING: do not use it on your devices without changing the public/private keys!).

    Overview

    Network Topology

    These 5 devices are used in our example setup to explain how WireGuard supports bridging across a variety of network conditions, they're all under an example domain example-vpn.dev, with the following short hostnames:

    • public-server1 (not behind a NAT, acts as the main VPN bounce server)
    • public-server2 (not behind a NAT, joins as a peer without bouncing traffic)
    • home-server (behind a NAT, joins as a peer without bouncing traffic)
    • laptop (behind NAT, sometimes shared w/ home-server/phone, sometimes roaming)
    • phone (behind NAT, sometimes shared w/ home-server/laptop, sometimes roaming)

    Explanation

    This VPN config simulates setting up a small VPN subnet 192.0.2.1/24 shared by 5 nodes. Two of the nodes (public-server1 and public-server2) are VPS instances living in a cloud somewhere, with public IPs accessible to the internet. home-server is a stationary node that lives behind a NAT with a dynamic IP, but it doesn't change frequently. Phone and laptop are both roaming nodes, that can either be at home in the same LAN as home-server, or out-and-about using public wifi or cell service to connect to the VPN.

    Whenever possible, nodes should connect directly to each other, depending on whether nodes are directly accessible or NATs are between them, traffic will route accordingly:

    The Public Relay

    public-server1 acts as an intermediate relay server between any VPN clients behind NATs, it will forward any 192.0.2.1/24 traffic it receives to the correct peer at the system level (WireGuard doesn't care how this happens, it's handled by the kernel net.ipv4.ip_forward = 1 and the iptables routing rules).

    Each client only needs to define the publicly accessible servers/peers in its config, any traffic bound to other peers behind NATs will go to the catchall 192.0.2.1/24 for the server and will be forwarded accordingly once it hits the main server.

    In summary: only direct connections between clients should be configured, any connections that need to be bounced should not be defined as peers, as they should head to the bounce server first and be routed from there back down the vpn to the correct client.

    Full Example Code

    To run this full example, simply copy the full wg0.conf config file for node section from each node onto each server, enable IP forwarding on the public relay, and then start WireGuard on all the machines.

    For more detailed instructions, see the Quickstart guide and API reference above. You can also download the complete example setup here: https://github.com/pirate/wireguard-docs/tree/master/full-example (WARNING: do not use it on your devices without changing the public/private keys!).

    Node Config

    public-server1.example-vpn.tld

    • public endpoint: public-server1.example-vpn.tld:51820
    • own vpn ip address: 192.0.2.1
    • can accept traffic for ips: 192.0.2.1/24
    • priv key: ``
    • pub key: ``
    • setup required:
      1. install wireguard
      2. generate public/private keypair
      3. create wg0.conf (see below)
      4. enable kernel ip & arp forwarding, add iptables forwarding rules
      5. start wireguard
    • config as remote peer:
    ini
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25
    • config as local interface:
    ini
    [Interface]
    # Name = public-server1.example-vpn.tld
    Address = 192.0.2.1/24
    ListenPort = 51820
    PrivateKey = <private key for public-server1.example-vpn.tld>
    DNS = 1.1.1.1
    • peers: public-server2, home-server, laptop, phone
    • full wg0.conf config file for node:
    ini
    [Interface]
    # Name = public-server1.example-vpn.tld
    Address = 192.0.2.1/24
    ListenPort = 51820
    PrivateKey = <private key for public-server1.example-vpn.tld>
    DNS = 1.1.1.1
    
    [Peer]
    # Name = public-server2.example-vpn.dev
    Endpoint = public-server2.example-vpn.dev:51820
    PublicKey = <public key for public-server2.example-vpn.dev>
    AllowedIPs = 192.0.2.2/32
    
    [Peer]
    # Name = home-server.example-vpn.dev
    PublicKey = <public key for home-server.example-vpn.dev>
    AllowedIPs = 192.0.2.3/32
    
    [Peer]
    # Name = laptop.example-vpn.dev
    PublicKey = <public key for laptop.example-vpn.dev>
    AllowedIPs = 192.0.2.4/32
    
    [Peer]
    # phone.example-vpn.dev
    PublicKey = <public key for phone.example-vpn.dev>
    AllowedIPs = 192.0.2.5/32

    public-server2.example-vpn.dev

    • public endpoint: public-server2.example-vpn.dev:51820
    • own vpn ip address: 192.0.2.2
    • can accept traffic for ips: 192.0.2.2/32
    • priv key: ``
    • pub key: ``
    • setup required:
      1. install wireguard
      2. generate public/private keypair
      3. create wg0.conf (see below)
      4. confirm main public relay server is directly accessible
      5. start wireguard
    • config as local interface:
    ini
    [Interface]
    # Name = public-server2.example-vpn.dev
    Address = 192.0.2.2/32
    ListenPort = 51820
    PrivateKey = <private key for public-server2.example-vpn.dev>
    DNS = 1.1.1.1
    • config as peer:
    ini
    [Peer]
    # Name = public-server2.example-vpn.dev
    Endpoint = public-server2.example-vpn.dev:51820
    PublicKey = <public key for public-server2.example-vpn.dev>
    AllowedIPs = 192.0.2.2/32
    • peers: public-server1
    • full wg0.conf config file for node:
    ini
    [Interface]
    # Name = public-server2.example-vpn.dev
    Address = 192.0.2.2/32
    ListenPort = 51820
    PrivateKey = <private key for public-server2.example-vpn.dev>
    DNS = 1.1.1.1
    
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25

    home-server.example-vpn.dev

    • public endpoint: (none, behind NAT)
    • own vpn ip address: 192.0.2.3
    • can accept traffic for ips: 192.0.2.3/32
    • priv key: ``
    • pub key: ``
    • setup required:
      1. install wireguard
      2. generate public/private keypair
      3. create wg0.conf (see below)
      4. confirm main public relay server is directly accessible
      5. start wireguard
    • config as local interface:
    ini
    [Interface]
    # Name = home-server.example-vpn.dev
    Address = 192.0.2.3/32
    ListenPort = 51820
    PrivateKey = <private key for home-server.example-vpn.dev>
    DNS = 1.1.1.1
    • config as peer:
    ini
    [Peer]
    # Name = home-server.example-vpn.dev
    PublicKey = <public key for home-server.example-vpn.dev>
    AllowedIPs = 192.0.2.3/32
    • peers: public-server1
    • full wg0.conf config file for node:
    ini
    [Interface]
    # Name = home-server.example-vpn.dev
    Address = 192.0.2.3/32
    ListenPort = 51820
    PrivateKey = <private key for home-server.example-vpn.dev>
    DNS = 1.1.1.1
    
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25

    laptop.example-vpn.dev

    • public endpoint: (none, behind NAT)
    • own vpn ip address: 192.0.2.4
    • can accept traffic for ips: 192.0.2.4/32
    • priv key: ``
    • pub key: ``
    • setup required:
      1. install wireguard
      2. generate public/private keypair
      3. create wg0.conf (see below)
      4. confirm main public relay server is directly accessible
      5. start wireguard
    • config as local interface:
    ini
    [Interface]
    # Name = laptop.example-vpn.dev
    Address = 192.0.2.4/32
    PrivateKey = <private key for laptop.example-vpn.dev>
    DNS = 1.1.1.1
    • config as peer:
    ini
    [Peer]
    # Name = laptop.example-vpn.dev
    PublicKey = <public key for laptop.example-vpn.dev>
    AllowedIPs = 192.0.2.4/32
    • peers: public-server1
    • full wg0.conf config file for node:
    ini
    [Interface]
    # Name = laptop.example-vpn.dev
    Address = 192.0.2.4/32
    PrivateKey = <private key for laptop.example-vpn.dev>
    DNS = 1.1.1.1
    
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25

    phone.example-vpn.dev

    • public endpoint: (none, behind NAT)
    • own vpn ip address: 192.0.2.5
    • can accept traffic for ips: 192.0.2.5/32
    • priv key: ``
    • pub key: ``
    • setup required:
      1. install wireguard
      2. generate public/private keypair
      3. create wg0.conf (see below)
      4. confirm main public relay server is directly accessible
      5. start wireguard
    • config as local interface:
    ini
    [Interface]
    # Name = phone.example-vpn.dev
    Address = 192.0.2.5/32
    PrivateKey = <private key for phone.example-vpn.dev>
    DNS = 1.1.1.1
    • config as peer:
    ini
    [Peer]
    # phone.example-vpn.dev
    PublicKey = <public key for phone.example-vpn.dev>
    AllowedIPs = 192.0.2.5/32
    • peers: public-server1
    • full wg0.conf config file for node:
    ini
    [Interface]
    # Name = phone.example-vpn.dev
    Address = 192.0.2.5/32
    PrivateKey = <private key for phone.example-vpn.dev>
    DNS = 1.1.1.1
    
    [Peer]
    # Name = public-server1.example-vpn.tld
    Endpoint = public-server1.example-vpn.tld:51820
    PublicKey = <public key for public-server1.example-vpn.tld>
    # routes traffic to itself and entire subnet of peers as bounce server
    AllowedIPs = 192.0.2.1/24
    PersistentKeepalive = 25

    Suggest changes: https://github.com/pirate/wireguard-docs/issues


    --- METRICS ---