DevTools Hub

Search tools

Search for a developer tool

WireGuard Config Explained

Part of the VPN Toolkit

WireGuard is deliberately small — its entire codebase is a fraction of OpenVPN's or IPsec's, with exactly one cipher suite instead of a negotiable menu of them. That simplicity extends to its configuration file: two kinds of sections, [Interface] and [Peer], and a short, fixed list of fields in each. This post covers what every field actually does, how the keys work, and the specific mistakes that turn a config that parses fine into a tunnel that silently doesn't pass traffic.

The two section types

A WireGuard .conf file has exactly one [Interface] section, describing this machine's own side of the tunnel, and one or more [Peer] sections, each describing one remote party it talks to. A simple point-to-point VPN has one peer on each side; a hub-and-spoke setup has a central server with many peer sections, one per client, while each client's own config has just one peer — the server.

[Interface]: this machine's own identity

Four fields cover the overwhelming majority of real configs:

[Interface]
PrivateKey = <base64-encoded private key>
Address = 10.0.0.2/24
ListenPort = 51820
DNS = 1.1.1.1

PrivateKey is this machine's half of a Curve25519 key pair, generated with wg genkey — it never leaves the machine it was generated on and is never transmitted anywhere, including to the peer. Address is the IP this interface gets inside the VPN's own address space, always written with a prefix length even for a single address (/24, or /32 if this interface genuinely only needs the one address). ListenPort matters mainly for a server — a client dialing out can omit it and let the OS pick an ephemeral port, while a server needs a fixed, known port for clients to reach. DNS is optional and only takes effect through wg-quick, which pushes it into the system's resolver configuration while the interface is up.

[Peer]: everything about one remote party

[Peer]
PublicKey = <base64-encoded public key>
PresharedKey = <optional base64-encoded key>
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25

PublicKey is derived from that peer's private key via wg pubkey and is safe to share — it's how this side identifies the peer and verifies packets actually came from it, but it reveals nothing about the corresponding private key. PresharedKey is optional: a separate, symmetric key added on top of the Curve25519 exchange specifically as a hedge against a future break of elliptic-curve cryptography by a sufficiently powerful quantum computer — it adds nothing against today's threats but costs nothing to include. Endpoint is only needed on the side that has to initiate the connection to a peer with a known, stable address; a client typically sets it for the server, while a server often omits it for clients whose address isn't fixed, discovering it dynamically from the first packet it receives instead.

AllowedIPs does two jobs at once

This is the field most likely to be misunderstood, because it genuinely serves two purposes simultaneously. First, it's a routing entry: traffic destined for any address in this list gets encrypted and sent to this peer. Second — and less obviously — it's a cryptographic filter on incoming packets: WireGuard decrypts a packet, checks which peer's key successfully decrypted it, and then verifies the packet's source address actually falls within that peer's configured AllowedIPs before accepting it. A peer that successfully authenticates but sends a packet claiming a source address outside its own AllowedIPs gets that packet silently dropped — a real security property that prevents one peer from spoofing traffic on another peer's behalf, not just a routing convenience.

0.0.0.0/0 (and its IPv6 equivalent ::/0) means "every address" — the setting for a full-tunnel VPN gateway that a client routes all its traffic through. Anything narrower is a split-tunnel setup, sending only specific destinations through the VPN while everything else uses the normal network path. Excluding specific subnets from an otherwise full tunnel ("route everything except my home network") isn't directly expressible in CIDR notation and needs to be computed — see VPN Split-Tunneling Explained for why, and VPN Split-Tunnel CIDR Calculator to compute the exact block list.

Why PersistentKeepalive exists

WireGuard itself doesn't need a heartbeat to function — encrypted packets flow exactly when there's real traffic to carry, with no background chatter. The problem is NAT and stateful firewalls, which forget a UDP mapping after a period of silence, typically well under a minute. A peer behind NAT that goes quiet for a few minutes can come back to find its outbound path still works but nothing reaches it from the other side, since the NAT device already discarded the mapping. PersistentKeepalive = 25 sends a small keepalive packet every 25 seconds specifically to keep that mapping alive — unnecessary for a peer with a stable, directly reachable address, and the standard fix for "WireGuard connects fine, then stops working a few minutes later."

Bringing the interface up

wg-quick up wg0 (reading /etc/wireguard/wg0.conf) is what most people actually run day to day — it creates the interface, assigns the Address, adds routes for every peer's AllowedIPs, and runs any PreUp/PostUp commands defined in the file, commonly used to enable IP forwarding or add firewall rules on a gateway. The lower-level wg command can configure all of this by hand without a config file at all, but it'swg-quick's file format that essentially every guide, GUI client, and config generator actually targets.

Confirming the tunnel actually came up

sudo wg show

This is the fastest way to check reality rather than assume it from the config alone — it reports each peer's latest handshake time and total data transferred. latest handshake updates roughly every two minutes on an active connection (WireGuard re-keys automatically in the background); a peer with no handshake at all, or one that stopped updating, means the tunnel isn't actually passing traffic even if wg-quick up reported success and the interface exists. This is usually the first command worth running before assuming a routing or firewall problem is the cause of a WireGuard connection that isn't behaving as expected.

Common mistakes

  • An Address without a prefix length. WireGuard needs one even for a single address — 10.0.0.2/32, not bare 10.0.0.2.
  • Reusing a key pair across multiple peers. Each peer needs its own identity; a shared key makes them indistinguishable and impossible to revoke individually.
  • Forgetting IP forwarding on a gateway peer. The tunnel itself comes up fine; traffic that should be forwarded onward is what silently disappears.
  • AllowedIPs too broad or too narrow for the intent. A client meant for split-tunnel access with 0.0.0.0/0 pasted in by habit routes everything through the VPN unintentionally; the reverse leaves an intended full-tunnel client exposed on its normal network path.
  • No PersistentKeepalive on a peer behind NAT. The most common cause of "it worked, then silently stopped" a few minutes into a session.

FAQ

Do I need to run a separate command to generate WireGuard keys?

Yes — wg genkey generates a private key, and piping it through wg pubkey derives the matching public key. Nothing in the config file format generates keys for you; you always generate a key pair first and paste the results into PrivateKey and PublicKey fields.

Can I use the same key pair for more than one peer?

Each peer should have its own unique key pair. Reusing a key across multiple peers means WireGuard can no longer tell them apart by identity, and revoking access for one of them means revoking it for all of them, since they're cryptographically indistinguishable.

What happens if I forget to set net.ipv4.ip_forward = 1 on a gateway peer?

The tunnel itself still comes up fine — WireGuard doesn't need IP forwarding to encrypt and deliver packets addressed to itself. But a peer meant to route other peers' traffic onward (a gateway or exit node) needs the kernel's forwarding enabled to actually pass packets between the WireGuard interface and the rest of the network; without it, traffic that should be forwarded is silently dropped at that hop.

Is wg-quick required, or can I configure WireGuard without it?

wg-quick is a convenience script that reads the .conf file, creates the interface, assigns the Address, sets routes based on AllowedIPs, and runs any PreUp/PostUp commands. The underlying wg tool can configure everything manually without a config file at all, but wg-quick's file format is what nearly every guide and every config you'll encounter actually uses.

Why does my peer's AllowedIPs also affect what WireGuard accepts, not just what it sends?

AllowedIPs is genuinely two things at once: a routing table entry describing what traffic should be sent to this peer, and a cryptographic source filter describing which addresses WireGuard will accept as legitimately coming from this peer. A packet that decrypts correctly but claims a source address outside the peer's AllowedIPs is dropped — this is a real security property, not just a routing convenience.

Try it yourself

WireGuard Config Validator checks every field covered here — key format, AllowedIPs, endpoints, and duplicate peers — entirely in your browser.

Related tools