Bring Up a WireGuard Tunnel Safely with wg-quick

wg-quick does the boring plumbing around WireGuard: write a config, bring up an interface, check it actually routes, then tear it down cleanly. The examples use the Ubuntu wireguard-tools package version 1.0.20210914-1ubuntu4 installed on this machine.

Allow about fifteen minutes if you already have the peer's public key, endpoint and allowed IPs. You will need:

This configures one client interface. It does not replace a network manager.

1. Check the command and choose an interface

Confirm the binary and package before changing network state. These checks are ordinary and do not need elevated privileges:

$ command -v wg-quick
/usr/bin/wg-quick
$ dpkg-query -W -f='${Package} ${Version}\n' wireguard-tools
wireguard-tools 1.0.20210914-1ubuntu4

Use a short, free-form interface name such as wg0. The installed manual accepts up to 15 characters made from letters, digits, underscore, equals, plus, full stop and hyphen. The name is also used to find the configuration file.

Checkpoint: Decide whether this interface already exists before continuing:

$ ip link show wg0
Device "wg0" does not exist.

If it exists and carries traffic, do not bring it up with a replacement configuration until you understand who owns it. Use sudo wg-quick down wg0 only when taking that tunnel down is deliberate.

2. Write the configuration with real peer values

Store the file as /etc/wireguard/wg0.conf. Root is required because this directory contains private key material and the file will control a system interface:

$ sudo install -d -m 700 /etc/wireguard
$ sudoedit /etc/wireguard/wg0.conf

Use this shape, replacing every value in angle brackets. Do not paste the brackets into the file. The Address and DNS entries are wg-quick additions; the peer fields are passed straight to wg:

[Interface]
Address = 10.20.0.2/24
PrivateKey = <client-private-key>
DNS = 10.20.0.1

[Peer]
PublicKey = <server-public-key>
AllowedIPs = 10.20.0.0/24
Endpoint = vpn.example.net:51820
PersistentKeepalive = 25

AllowedIPs is not just a peer label: wg-quick infers routes from it. The value above sends traffic for the VPN subnet through the tunnel. A value of 0.0.0.0/0 means all IPv4 traffic and triggers special default-route handling; only add ::/0 too when IPv6 full-tunnel routing is configured and wanted.

DNS invokes resolvconf when the interface is brought up and down. If resolvconf is absent, or its changes do not suit this host, leave DNS out and manage the resolver the normal way instead. PersistentKeepalive = 25 is for a peer behind NAT that needs to stay reachable while idle; it is not something every config needs.

Protect the file after saving it:

$ sudo chmod 600 /etc/wireguard/wg0.conf
$ sudo wg-quick strip wg0 | sed -n '/\[Interface\]/,/\[Peer\]/p'
[Interface]
PrivateKey = <client-private-key>

Warning: that last command prints configuration with the wg-quick-specific fields stripped out, but it still contains the private key. Do not paste its output into a ticket or shell history.

3. Bring the tunnel up

This changes system networking and normally requires elevated privileges:

$ sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.20.0.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] resolvconf -a tun.wg0 -m 0 -x
[#] ip -4 route add 10.20.0.0/24 dev wg0

Your exact lines vary with the addresses, MTU, DNS support and routes. What matters is a zero exit status and no error from a command that could not find the endpoint, key or helper.

Checkpoint: Inspect the interface and WireGuard state:

$ ip address show dev wg0
$ ip route show dev wg0
$ sudo wg show wg0

Look for the configured address and route. A peer's latest handshake only appears in wg show after traffic has actually reached it, so no handshake yet is not proof the local interface failed.

4. Test only the route you intended

For the example above, test a destination inside the VPN subnet rather than immediately changing your default route:

$ ip route get 10.20.0.10
10.20.0.10 dev wg0 src 10.20.0.2

Then use the application or probe the remote administrator expects, such as ping or ssh. A route result confirms local selection, not remote reachability. If traffic still does not pass, check the peer endpoint, firewall policy, allowed IPs on both ends and the latest handshake.

Warning: do not bolt on a kill-switch or firewall snippet copied from an example without reviewing it for this host. A PostUp rule runs as part of interface activation and can block ordinary traffic if its assumptions are wrong. If a firewall rule is already blocking recovery, use the matching PreDown rule or your documented firewall rollback from a console session.

5. Take it down and recover from a failed start

Remove the interface and its routes with:

$ sudo wg-quick down wg0
[#] ip link delete dev wg0

That is the normal undo for this guide. If SaveConfig = true is present, shutdown writes the current interface state back to the configuration file and can overwrite edits made since activation. Leave that setting out unless you mean it.

If up fails part way through, read the first failing command. Check the file without exposing its contents:

$ sudo test -r /etc/wireguard/wg0.conf && echo readable
$ sudo wg-quick down wg0
$ sudo wg show interfaces

The second command is safe to try after a partial start; if there is no wg0, it just reports that the interface does not exist. A few things this points to:

Do not repeatedly run up hoping the error disappears: each attempt can run configured hooks again.

6. Reload settings without dropping the session

For changes that only affect WireGuard's peer and interface settings, strip can feed wg syncconf without removing the interface:

$ sudo wg syncconf wg0 <(sudo wg-quick strip wg0)

This does not apply wg-quick's address, DNS, route or hook changes. If those changed, plan a proper down and up and accept the interruption. Process substitution needs Bash, which is also the shell wg-quick hooks run under.

Done means