Deploy a WireGuard server on Ubuntu 24.04 LTS
Create a minimal routed WireGuard VPN with protected keys, explicit peers, constrained forwarding, and peer removal.
Provide low-overhead remote access with a small configuration and one key pair per peer.
- Ubuntu Server 24.04 LTS
- WireGuard tools 1.x
- Console safety Retain console access while editing firewall rules.
- Address plan Reserve a VPN subnet that does not overlap client networks.
- Public endpoint Choose a stable hostname and UDP port.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A routed WireGuard interface named wg0 on an Ubuntu 24.04 gateway, listening on one explicitly selected UDP port.
- One independently revocable peer identity with a unique tunnel address and an encrypted path to the server-side VPN subnet.
- A forwarding and firewall policy that exposes only the WireGuard listener and only the routes the peer is meant to use.
- The gateway reports wg0 as UP, shows the expected peer public key, and records a recent handshake after the client connects.
- The client reaches the server tunnel address and only the networks listed in its AllowedIPs policy.
- Removing the peer from the server configuration immediately prevents that public key from authenticating again.
Architecture
How the parts fit together
WireGuard treats both ends as peers. The Ubuntu host owns the public endpoint and routes encrypted packets between wg0 and approved networks; the remote device owns a private key and decides which destinations enter the tunnel.
- The client sends an authenticated UDP handshake to the gateway public endpoint.
- WireGuard associates the client public key with its configured AllowedIPs and decrypts accepted packets onto wg0.
- The host answers tunnel-local traffic or forwards approved destinations according to the kernel routing and nftables policies.
- Return traffic follows the route back to wg0 and is encrypted for the matching peer public key.
Assumptions
- The gateway is a dedicated or carefully reviewed Ubuntu 24.04 host with console access, a stable public IPv4 address or DNS name, and one known public interface.
- The example creates a split tunnel for 10.9.0.0/24. It does not route all client internet traffic and does not add NAT unless the surrounding network lacks a return route.
- The chosen VPN subnet does not overlap the client LAN, server LAN, container networks, or another VPN. Overlap makes route selection ambiguous.
- UDP 51820 can reach the gateway and any upstream security group or provider firewall is managed separately from the host nftables policy.
- Commands are run interactively by an administrator. Private keys are not pasted into tickets, shell history, CI logs, or OneLiners.
Key concepts
- Peer
- A WireGuard identity represented by a public key and a set of allowed tunnel addresses. There is no separate client/server protocol role even when one host acts as the public gateway.
- AllowedIPs
- A route selector for outbound traffic and an authorization boundary for inbound traffic. On the server, a peer should normally own unique host routes such as 10.9.0.2/32.
- Endpoint
- The current public UDP address of a peer. Roaming clients configure the gateway endpoint; the gateway learns a client endpoint from authenticated packets.
- PersistentKeepalive
- An optional authenticated packet sent at an interval to preserve a NAT mapping. Twenty-five seconds is useful for a peer behind NAT but should not be enabled without that need.
- Cryptokey routing
- WireGuard's mapping between public keys and AllowedIPs. The mapping determines both which key encrypts outbound traffic and which source addresses a peer may send.
Before you copy
Values used in this guide
{{vpnPort}}Public UDP port used by the wg0 interface.
Example: 51820{{vpnServerAddress}}Gateway address and prefix inside the WireGuard network.
Example: 10.9.0.1/24{{vpnNetwork}}Tunnel subnet routed through WireGuard.
Example: 10.9.0.0/24{{clientVpnAddress}}Unique host address assigned to this peer.
Example: 10.9.0.2/32{{vpnHost}}Public DNS name or address that resolves to the gateway.
Example: vpn.example.com{{serverPrivateKey}}secretBase64 private key read from the protected server.key file.
Example: SERVER_PRIVATE_KEY_BASE64{{serverPublicKey}}Public key derived from the server private key.
Example: SERVER_PUBLIC_KEY_BASE64{{clientPrivateKey}}secretPrivate key generated and retained on the client device.
Example: CLIENT_PRIVATE_KEY_BASE64{{clientPublicKey}}Public key derived on the client and installed on the gateway.
Example: CLIENT_PUBLIC_KEY_BASE64Security and production boundaries
- A WireGuard private key is the credential. File mode 0600 limits local disclosure, but backups, terminal scrollback and configuration-management logs must also protect it.
- AllowedIPs is not a substitute for a forwarding firewall. Use both: unique peer routes bind addresses to keys, while nftables controls services and network paths.
- A peer has no username, password, expiry, or certificate revocation list. Removing the public key from the live configuration is the revocation operation.
- Full-tunnel routing, DNS pushing and NAT are intentionally outside this split-tunnel baseline. Add them only after documenting privacy, logging, capacity, and return-path implications.
Stop before continuing if
- Stop if 10.9.0.0/24 overlaps any route shown on the gateway or client. Choose a new subnet before creating peer configuration.
- Stop if you cannot reach a recovery console or preserve a known-good firewall ruleset before enabling forwarding.
- Stop if a generated private key appears in terminal logs, chat, source control, or automation output. Rotate that key before continuing.
- Stop if nftables validation fails or the second administration session cannot connect after the policy change.
command
Install WireGuard
Install Ubuntu's supported userspace tooling.
Why this step matters
Install the distribution-supported tooling before creating keys so every later command, service unit and manual page comes from one known package source.
What to understand
The Ubuntu wireguard package installs wg for key and peer management plus wg-quick for declarative interface setup. The kernel implementation is already available on supported Ubuntu 24.04 kernels.
The command refreshes package metadata first and then performs a non-interactive install. Review pending package changes in a controlled production change window before accepting them.
System changes
- APT metadata is refreshed and the WireGuard userspace tools, systemd template unit and documentation are installed.
Syntax explained
apt update- Refreshes the local package index without upgrading installed packages.
apt install --yes wireguard- Installs the supported package and accepts the package-manager confirmation prompt.
sudo apt update && sudo apt install --yes wireguardSetting up wireguard-tools ...
Checkpoint: Confirm the tools are available
wg --version && systemctl cat wg-quick@.service | headContinue whenwg reports its installed tools version and systemd can display the wg-quick template unit.
Stop whenEither command is missing or APT reports an incomplete transaction.
If this step fails
APT cannot locate the wireguard package.
Likely causePackage indexes are stale or the supported Ubuntu repositories are disabled.
apt-cache policy wireguardgrep -Rh '^deb ' /etc/apt/sources.list /etc/apt/sources.list.d
ResolutionRestore supported Ubuntu repositories and refresh metadata; do not install an arbitrary third-party script.
Security notes
- Package installation requires root and may start no tunnel by itself; no private key exists yet.
Alternatives
- Use the distribution package rather than downloading unverified binaries.
Stop conditions
- Do not continue until wg and wg-quick are both installed from the intended repository.
command
Generate protected server keys
Create keys under a restrictive umask and never print the private key into logs.
Why this step matters
WireGuard authenticates a peer entirely by possession of its private key, so key generation and file permissions establish the most important trust boundary in the deployment.
What to understand
umask 077 makes newly created files readable and writable only by their owner. The pipeline writes the private key to server.key while sending the same bytes to wg pubkey to derive the shareable public key.
sudo tee is used because shell redirection would otherwise run as the unprivileged shell user. The public key may be distributed; server.key must remain only on the gateway and in an approved encrypted backup.
System changes
- Creates /etc/wireguard with mode 0700, /etc/wireguard/server.key with private material and /etc/wireguard/server.pub with the derived public key.
Syntax explained
install -d -m 700- Creates a directory accessible only to root.
umask 077- Removes group and other permissions from files created by the remaining shell.
wg genkey- Generates a new base64-encoded WireGuard private key on standard output.
wg pubkey- Reads a private key from standard input and derives its public key.
tee- Writes the pipeline data to a root-owned file while passing it to the next stage.
sudo install -d -m 700 /etc/wireguard && umask 077 && wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wireguard/server.pubserver.key and server.pub created with owner-only permissions
Checkpoint: Verify ownership without displaying secrets
sudo stat -c '%U:%G %a %n' /etc/wireguard/server.key /etc/wireguard/server.pubContinue whenBoth files are owned by root; server.key is mode 600 or stricter.
Stop whenThe private key is empty, printed into a log, or readable by group or other users.
If this step fails
server.key exists but server.pub is empty.
Likely causeThe pipeline stopped before wg pubkey could read the generated private key.
sudo stat /etc/wireguard/server.key /etc/wireguard/server.pubsudo journalctl -n 30 --no-pager
ResolutionDelete both incomplete files securely and regenerate the pair with the restrictive umask.
Security notes
- Never use cat on server.key for troubleshooting output that may be recorded.
Alternatives
- Generate the key in a hardware-backed or secrets-managed workflow only if that system can supply it to WireGuard without logging it.
Stop conditions
- Rotate immediately if the private key leaves the trusted host or approved encrypted backup.
config
Define the server interface
Use a unique allowed address for each peer and never reuse private keys.
Why this step matters
The server configuration binds the gateway address and listening socket to one private key, then authorizes exactly one tunnel address for the client public key.
What to understand
The [Interface] section describes local state. The [Peer] section contains only remote public data and its permitted source/destination addresses.
A /32 for the client prevents two peers from claiming the same address. Add one distinct [Peer] block per device rather than sharing a key between people or machines.
System changes
- Creates /etc/wireguard/wg0.conf, which wg-quick@wg0 reads to create interface wg0 and load the peer map.
Syntax explained
[Interface]- Begins settings owned by this gateway.
Address- Assigns the gateway address and connected VPN prefix to wg0.
ListenPort- Selects the UDP port on which the gateway accepts WireGuard packets.
PrivateKey- Loads the gateway credential; this value must never be published.
[Peer]- Begins the authorization record for one remote public key.
AllowedIPs = .../32- Assigns one unique tunnel address to the peer for routing and source authorization.
/etc/wireguard/wg0.confValues stay on this page and are never sent or saved.
[Interface]
Address = {{vpnServerAddress}}
ListenPort = {{vpnPort}}
PrivateKey = {{serverPrivateKey}}
[Peer]
PublicKey = {{clientPublicKey}}
AllowedIPs = {{clientVpnAddress}}wg0.conf saved with mode 0600
Checkpoint: Inspect the configuration safely
sudo wg-quick strip wg0 | sed -E 's/^(PrivateKey = ).*/\1[redacted]/'Continue whenThe stripped configuration contains one interface and the intended peer /32 without exposing the private key.
Stop whenA placeholder remains, two peers own the same address, or the file mode is broader than 0600.
If this step fails
wg-quick reports an invalid key or configuration line.
Likely causeA placeholder was copied literally, a base64 key is truncated, or a field is in the wrong section.
sudo wg-quick strip wg0sudo stat -c '%a %n' /etc/wireguard/wg0.conf
ResolutionCorrect the local copy from the protected key files and re-run the strip check before starting the interface.
Security notes
- The configuration file contains the server private key and must be mode 0600.
Alternatives
- Use separate files with a configuration-management secret provider if the rendered file remains equally protected.
Stop conditions
- Do not start wg0 with unresolved variables, reused peer addresses or an exposed private key.
config
Enable forwarding only when required
Persist IPv4 forwarding when peers need access beyond the server.
Why this step matters
Linux does not forward IPv4 packets between interfaces by default; enable it only after defining which destinations peers are allowed to reach.
What to understand
The sysctl changes a kernel routing gate, not the firewall. Without a matching nftables forward rule, packets can still be blocked.
A tunnel used only to reach services on the WireGuard gateway does not require forwarding. Skip this persistent change in that design.
System changes
- Creates /etc/sysctl.d/99-wireguard-forward.conf and changes net.ipv4.ip_forward to 1 when sysctl settings are applied.
Syntax explained
net.ipv4.ip_forward = 1- Allows the IPv4 stack to route packets between interfaces.
/etc/sysctl.d/99-wireguard-forward.confnet.ipv4.ip_forward = 1sudo sysctl --system && sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1
Checkpoint: Read the effective kernel value
sysctl net.ipv4.ip_forwardContinue whenReturns net.ipv4.ip_forward = 1 only when forwarding is part of the approved design.
Stop whenForwarding is enabled before the firewall path and return route have been planned.
Security notes
- Forwarding expands the host from endpoint to router; nftables must constrain the new path.
Alternatives
- Leave forwarding disabled for gateway-local access only.
Stop conditions
- Do not enable forwarding on a general-purpose host without reviewing every attached network.
warning
Constrain the tunnel firewall
Permit UDP 51820 and only intended forwarding. Add NAT only when return routes are unavailable.
Why this step matters
Opening the WireGuard socket is necessary for handshakes, while a separate narrow forward policy prevents authenticated peers from becoming unrestricted network clients.
What to understand
nft --check parses the persistent file without replacing the active ruleset. Reload only after this dry validation succeeds.
Permit the selected UDP port on the public interface and allow forwarding from wg0 only to explicit destination prefixes or services. NAT is a fallback for networks that cannot route 10.9.0.0/24 back to the gateway.
System changes
- Reloads the host nftables policy and can immediately change SSH, service and tunnel reachability.
Syntax explained
nft --check- Parses rules without applying them.
--file /etc/nftables.conf- Reads the candidate persistent ruleset from the named file.
systemctl reload nftables- Asks the service to load the validated persistent rules without disabling it.
sudo nft --check --file /etc/nftables.conf && sudo systemctl reload nftablesValidated ruleset reloaded
Checkpoint: Prove administration still works
sudo nft list ruleset && sudo ss -lunp | grep ':51820'Continue whenThe intended accepts are present, the management session remains alive and the WireGuard UDP socket is visible after startup.
Stop whenThe dry check fails, the management allow rule is absent, or no console recovery path is available.
If this step fails
SSH or another management path stops accepting new connections.
Likely causeThe default-drop policy was loaded without the correct source, interface or port exception.
Use the retained session or console to run sudo nft list rulesetCompare the active rules with the saved ruleset
ResolutionRestore the known-good policy from console, correct the management rule, validate again and repeat the second-session test.
Security notes
- Do not use a blanket accept from wg0; authenticated peers may still be compromised.
Alternatives
- Install a routed return path instead of masquerading when you control the destination network.
Stop conditions
- Never reload a firewall remotely without a validated file, backup and independent recovery path.
command
Start the interface
Enable the systemd wrapper and inspect effective peer state.
Why this step matters
Starting through the systemd template proves the persistent configuration can create wg0 now and after reboot, rather than testing only transient wg commands.
What to understand
wg-quick@wg0 derives the configuration path from the instance name. systemd records failures and ensures the interface is brought up during later boots.
wg show displays public keys, endpoints, AllowedIPs, handshake times and counters. It does not reveal private keys.
System changes
- Enables and starts wg-quick@wg0, creates wg0, installs its addresses and routes, and loads peers into the kernel.
Syntax explained
systemctl enable --now- Starts the instance immediately and enables it for subsequent boots.
wg-quick@wg0- Uses /etc/wireguard/wg0.conf to create and manage interface wg0.
wg show wg0- Reads safe runtime peer and interface status.
sudo systemctl enable --now wg-quick@wg0 && sudo wg show wg0interface: wg0 listening port: 51820 peer: <client-public-key>
Checkpoint: Confirm interface and listener
systemctl is-active wg-quick@wg0 && ip -brief address show wg0 && sudo wg show wg0Continue whenThe service is active, wg0 owns 10.9.0.1/24 and the intended peer public key is listed.
Stop whenThe unit is failed, wg0 has the wrong address, or an unknown peer appears.
If this step fails
wg-quick@wg0 fails with RTNETLINK answers: File exists.
Likely causeThe address or route is already owned by another interface or stale wg0 state remains.
ip address showip route show table allsystemctl status wg-quick@wg0 --no-pager
ResolutionResolve the address overlap or remove only the known stale interface, then start the unit again.
Security notes
- Runtime output exposes public keys and endpoints, which are operational metadata even though they are not credentials.
Alternatives
- Use NetworkManager or systemd-networkd only when that is the host's established network management plane.
Stop conditions
- Do not distribute a client profile until the server interface, peer mapping and firewall are all confirmed.
config
Create the client profile
Use the narrowest AllowedIPs that meets the access goal.
Why this step matters
The client profile completes the two-sided key and route mapping; a narrow AllowedIPs value keeps unrelated traffic outside the VPN.
What to understand
The client owns 10.9.0.2/32 and retains its private key locally. It learns the server public key and stable endpoint.
PersistentKeepalive is included for a roaming client behind NAT. Remove it for a continuously reachable peer that does not need an idle mapping.
System changes
- Creates a client-local WireGuard profile containing the client private key and the gateway public endpoint.
Syntax explained
Address = .../32- Assigns one host address to the client interface.
Endpoint- Sets the gateway DNS name and UDP port contacted by the client.
AllowedIPs- Routes only the listed tunnel subnet through this peer.
PersistentKeepalive = 25- Keeps a NAT mapping alive with an authenticated packet every 25 seconds.
client.confValues stay on this page and are never sent or saved.
[Interface]
Address = {{clientVpnAddress}}
PrivateKey = {{clientPrivateKey}}
[Peer]
PublicKey = {{serverPublicKey}}
Endpoint = {{vpnHost}}:{{vpnPort}}
AllowedIPs = {{vpnNetwork}}
PersistentKeepalive = 25Client profile contains one interface and one server peer
Checkpoint: Review the profile before import
Continue whenThe profile has one client private key, the correct server public key, a unique /32, and only the intended AllowedIPs.
Stop whenThe profile reuses a private key, includes 0.0.0.0/0 unintentionally, or contains unresolved variables.
If this step fails
The client imports the profile but refuses to activate it.
Likely causeA key is malformed, the address syntax is invalid, or the client platform requires a different import location.
Validate both public keys with wg pubkey from protected private-key inputInspect the client application error without sharing the profile
ResolutionCorrect the profile locally or follow the official WireGuard client import method for that platform.
Security notes
- Transfer the profile over an authenticated encrypted channel and remove temporary copies.
Alternatives
- Use a QR code only in a private setting where screens and camera roll backups are controlled.
Stop conditions
- Never send a profile through public chat, unencrypted email or a ticket attachment.
verification
Verify handshake and routes
Connect externally and confirm recent handshake, byte counters, and intended reachability.
Why this step matters
A successful service start proves only local configuration; a recent handshake plus routed traffic from an external network proves the real endpoint, keys and path.
What to understand
latest-handshakes returns Unix timestamps per peer. A non-zero recent value after the client sends traffic proves authenticated packets reached the gateway.
The ping checks the tunnel-local gateway address. Add a separate approved destination test only after this simpler path works.
System changes
- No persistent server state changes; test traffic updates handshake timestamps and byte counters.
Syntax explained
wg show wg0 latest-handshakes- Prints each peer public key with its most recent authenticated handshake timestamp.
ping -c 3- Sends exactly three ICMP echo requests and then exits.
sudo wg show wg0 latest-handshakes && ping -c 3 10.9.0.1<peer-key> 1784898000 3 packets transmitted, 3 received
Checkpoint: Correlate handshake and reachability
sudo wg show wg0 latest-handshakes && ping -c 3 10.9.0.1Continue whenThe peer timestamp is current and all three tunnel-address probes return.
Stop whenThe handshake is zero or stale, packets are lost, or traffic reaches a route that was not intended.
Security notes
- Test from a network outside the server LAN so a local route cannot hide endpoint or firewall problems.
Alternatives
- Use an application health endpoint instead of ICMP where ICMP is deliberately filtered.
Stop conditions
- Do not claim the deployment complete based only on systemctl active.
instruction
Remove a peer
Delete the peer block, apply the reviewed config, and confirm the key disappears.
Why this step matters
WireGuard has no certificate revocation service; removing the public key from both persistent and live configuration is the direct way to revoke a device.
What to understand
wg-quick strip emits only the WireGuard-relevant configuration, and wg syncconf updates peers without tearing down the interface or unrelated sessions.
Edit and review wg0.conf first. syncconf then makes the live peer set match that persistent source of truth.
System changes
- Removes one peer from the live wg0 key map after its block has been removed from /etc/wireguard/wg0.conf.
Syntax explained
wg-quick strip wg0- Produces the effective WireGuard configuration without wg-quick-only address and route settings.
wg syncconf wg0- Synchronizes the live peer configuration while preserving runtime state where possible.
<(...)- Bash process substitution presents generated configuration as a readable file descriptor.
sudo wg syncconf wg0 <(sudo wg-quick strip wg0)Removed peer is absent from sudo wg show wg0
Checkpoint: Confirm revocation
sudo wg show wg0 peersContinue whenThe retired public key is absent, while every intended active peer remains.
Stop whenAn intended peer disappeared or the retired peer still appears.
If this step fails
syncconf fails with a shell syntax error.
Likely causeThe command was run in a shell without Bash-compatible process substitution.
ps -p $$ -o comm=sudo wg-quick strip wg0
ResolutionRun the reviewed command from Bash or write the stripped configuration to a protected temporary file and pass that path to syncconf.
Security notes
- Record the public key fingerprint as retired; never retain or request the peer private key.
Alternatives
- Restart wg-quick@wg0 only during a maintenance window if a live sync cannot be safely performed.
Stop conditions
- Do not sync a configuration until its complete remaining peer list has been reviewed.
Finish line
Verification checklist
ip address show wg0Shows 10.9.0.1/24 on an UP interface.ss -lunp | grep ':51820'Shows a UDP listener.sudo wg show wg0 latest-handshakesPeer timestamp updates after connection.Recovery guidance
Common problems and safe checks
The client sends packets but the gateway shows no recent handshake.
Likely causeThe endpoint or UDP port is wrong, an upstream firewall blocks UDP, or the two peers do not have matching public keys.
Resolve the configured endpoint and compare it with the gateway public address.Run sudo ss -lunp and sudo wg show wg0 without printing any private key.Capture only UDP port 51820 metadata with sudo tcpdump -ni any udp port 51820.
ResolutionCorrect the endpoint, permit the selected UDP port at every network boundary, or replace the mismatched public key. Do not exchange private keys.
A handshake succeeds but the client cannot ping the server tunnel address.
Likely causeThe client route is missing, the addresses overlap another network, or AllowedIPs assigns the wrong address to the peer.
Inspect ip route get 10.9.0.1 on the client.Compare the client Address with the server peer AllowedIPs host route.Inspect nft monitor trace only during a controlled test if ordinary counters are insufficient.
ResolutionMake the client address and server-side /32 ownership agree, remove overlapping routes, then cycle only the client interface.
The tunnel address works but a network behind the gateway does not.
Likely causeIPv4 forwarding is disabled, nftables does not allow the forward path, or the destination network has no return route to the VPN subnet.
Read sysctl net.ipv4.ip_forward.Inspect ip route get for the destination and nft list ruleset.Test the destination from the gateway before testing through WireGuard.
ResolutionEnable forwarding, add the narrow forward rule, and install a return route on the destination network. Use scoped NAT only when a return route is impossible.
The tunnel works initially but an idle mobile peer becomes unreachable.
Likely causeA client-side NAT mapping expires because the silent peer has not transmitted traffic.
Compare the latest handshake and transfer counters before and after client traffic.Confirm PersistentKeepalive is configured only on the NATed client.
ResolutionSet PersistentKeepalive = 25 on that client peer and re-test after an idle interval; do not add it to every peer by habit.
Reference
Frequently asked questions
Should AllowedIPs be 0.0.0.0/0?
Only for an intentional IPv4 full tunnel. This guide uses 10.9.0.0/24 so unrelated internet traffic stays on the client's normal route.
Does WireGuard distribute addresses or DNS settings?
No. Address, DNS and route values are supplied by configuration or a separate management system; the protocol itself exchanges keys and encrypted packets.
How is a peer revoked?
Remove its [Peer] block from the persistent configuration and apply the reviewed configuration with syncconf. A removed public key can no longer authenticate.
Recovery
Rollback
Stop the tunnel and restore firewall and forwarding state.
- Disable and stop wg-quick@wg0.
- Restore and validate the saved firewall rules.
- Remove the WireGuard sysctl file and apply sysctl settings.
- Securely archive or destroy retired private keys.
Evidence