You need a private tunnel across untrusted networks
You just deployed a new Fedora server in a cloud provider, or maybe you are traveling with your laptop and need to access your home network securely. The internet between you and your machine is public. You need a tunnel that encrypts everything, routes correctly, and survives a reboot. WireGuard is the standard for this exact scenario. It lives in the kernel, it is fast, and on Fedora it integrates cleanly with systemd and firewalld. This guide walks through the server setup, the client setup, and the verification steps so you can route traffic without guessing.
How WireGuard actually works on Fedora
WireGuard operates as a virtual network interface. It looks like a regular Ethernet adapter to the operating system, but instead of sending frames over a physical cable, it encrypts packets and sends them over UDP. The kernel module handles the cryptography and packet processing. Userspace tools like wg-quick read a simple configuration file, generate the necessary routing rules, and hand the interface to the kernel. Fedora ships the wireguard-tools package, which includes the wg utility and the wg-quick helper. The helper is what actually brings the interface up and manages the systemd service unit.
Think of WireGuard like a secure pipe buried under a public road. The pipe itself is managed by the kernel. The configuration file is just the blueprint that tells the kernel where the pipe starts, where it ends, and who is allowed to walk through it. You do not need to manually craft iptables rules or write custom routing scripts. The wg-quick tool handles the plumbing. You only need to provide the keys and the IP addresses.
Fedora's network stack expects configuration files in /etc/. Files in /usr/lib/ ship with packages and get overwritten on updates. Always place WireGuard configurations in /etc/wireguard/. The wg-quick helper reads from there by default. If you place files elsewhere, you must pass the path explicitly, which breaks the systemd template integration.
Set up the server
Start by installing the package. Fedora keeps WireGuard in the main repository, so no third-party repos are required. The package pulls in the kernel module automatically if your running kernel does not already have it built in.
Here is how to install the package and verify the kernel module is ready.
sudo dnf install -y wireguard-tools # Installs the userspace tools and pulls in the kernel module if missing
modprobe wireguard # Loads the module immediately so you can test key generation
lsmod | grep wireguard # Confirms the kernel module is active and ready
WireGuard relies on asymmetric cryptography. Each side needs a private key and a public key. The private key stays on the machine. The public key gets shared with the peer. Generate them securely so no other user on the system can read them.
Here is how to generate the key pair with strict file permissions.
sudo umask 077 # Restricts file creation permissions to owner-only read/write
sudo wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey # Generates private key, saves it, and derives the public key
sudo chmod 600 /etc/wireguard/privatekey # Ensures only root can read the sensitive material
Create the server configuration file. WireGuard uses an INI-style format. The [Interface] section defines the local side. The [Peer] section defines the remote side. You will need the client's public key later, but you can start with a placeholder or generate the client keys first. For this walkthrough, we will assume you already have the client public key ready.
Here is how to write the server configuration with proper routing and firewall integration.
[Interface]
Address = 10.0.0.1/24 # Assigns the tunnel IP to the wg0 interface
ListenPort = 51820 # UDP port the server listens on for incoming connections
PrivateKey = <YOUR_SERVER_PRIVATE_KEY> # Paste the contents of /etc/wireguard/privatekey here
PostUp = firewall-cmd --zone=public --add-port=51820/udp # Opens the port in firewalld when the interface comes up
PostDown = firewall-cmd --zone=public --remove-port=51820/udp # Closes the port when the interface goes down
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY> # The client's public key
AllowedIPs = 10.0.0.2/32 # Only accepts traffic from this specific tunnel IP
Save this as /etc/wireguard/wg0.conf. The PostUp and PostDown directives are a Fedora convention. They keep your firewall rules synchronized with the tunnel lifecycle. If you manage firewalld manually, you can skip these lines and run firewall-cmd separately, but the hooks prevent configuration drift. Always run firewall-cmd --reload after manual rule changes. Otherwise the runtime config and the persistent config diverge.
Enable IP forwarding so the server can route traffic between the tunnel and the outside world. Fedora disables forwarding by default for security. You need to make the change persistent across reboots.
Here is how to enable IPv4 forwarding and verify it is active.
sudo sysctl -w net.ipv4.ip_forward=1 # Enables forwarding immediately in the running kernel
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf # Persists the setting across reboots
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf # Applies the persistent configuration without rebooting
Start the tunnel using the systemd service template. The wg-quick@.service template matches the configuration filename. wg-quick@wg0 reads /etc/wireguard/wg0.conf.
Here is how to enable and start the server tunnel.
sudo systemctl enable --now wg-quick@wg0 # Creates the symlink for boot and starts the service immediately
sudo systemctl status wg-quick@wg0 # Shows the active state and recent journal entries
Set up the client
The client configuration mirrors the server but points outward. The client does not need to listen on a port. It only needs to know where the server lives and what traffic to send through the tunnel.
Generate a client key pair using the same method as the server. Then create the client configuration file.
Here is how to write the client configuration with a full tunnel route.
[Interface]
Address = 10.0.0.2/24 # Assigns the tunnel IP to the client wg0 interface
PrivateKey = <CLIENT_PRIVATE_KEY> # Paste the client private key here
DNS = 10.0.0.1 # Optional: routes DNS queries through the tunnel
[Peer]
PublicKey = <SERVER_PUBLIC_KEY> # The server's public key
Endpoint = <SERVER_PUBLIC_IP>:51820 # The reachable address and port of the server
AllowedIPs = 0.0.0.0/0 # Sends all traffic through the tunnel
PersistentKeepalive = 25 # Sends a heartbeat every 25 seconds to keep NAT mappings alive
Save this as /etc/wireguard/wg0.conf on the client machine. The AllowedIPs = 0.0.0.0/0 directive tells the client to route everything through the tunnel. If you only want to access the server's local network, change it to 10.0.0.0/24. The PersistentKeepalive setting is crucial for clients behind NAT or firewalls. It prevents the connection from timing out when idle.
Start the client tunnel.
Here is how to enable and start the client tunnel.
sudo systemctl enable --now wg-quick@wg0 # Starts the client connection and registers it for boot
sudo wg # Displays the current tunnel state, peer status, and transfer counters
Manage multiple peers
Real deployments rarely stop at one client. You can add multiple [Peer] blocks to the server configuration. Each block needs a unique public key and a unique AllowedIPs range. The client configurations remain identical except for the Address and PrivateKey fields.
Here is how to add a second client to the server configuration.
[Peer]
PublicKey = <CLIENT2_PUBLIC_KEY> # The second client's public key
AllowedIPs = 10.0.0.3/32 # Assigns a unique tunnel IP to the second client
[Peer]
PublicKey = <CLIENT3_PUBLIC_KEY> # The third client's public key
AllowedIPs = 10.0.0.4/32 # Assigns a unique tunnel IP to the third client
After editing the server configuration, restart the service to apply the changes. The wg-quick helper reloads the entire file atomically. Existing connections drop briefly while the interface resets.
Here is how to reload the server configuration safely.
sudo systemctl restart wg-quick@wg0 # Applies the new peer list and resets the interface
sudo wg show wg0 # Verifies all peers are registered and shows their assigned IPs
Verify the tunnel
Do not assume the tunnel is working just because the service started. Check the routing table and test connectivity.
Here is how to verify the tunnel interface and routing rules.
ip addr show wg0 # Confirms the interface has the correct IP address and is UP
ip route show table main # Shows the default route and tunnel-specific routes
ping -c 4 10.0.0.1 # Tests basic layer 3 connectivity to the server
Check the systemd journal for handshake errors. WireGuard logs handshake failures directly to the system journal.
Here is how to inspect recent WireGuard logs.
journalctl -xeu wg-quick@wg0 # Shows explanatory text and jumps to the end of the unit logs
journalctl -t wireguard # Filters for kernel-level WireGuard messages
If the ping succeeds and wg shows latest handshake: X seconds ago, the tunnel is operational. Run journalctl -xe first. Read the actual error before guessing.
Common pitfalls and what the error looks like
Configuration drift and permission errors cause most failures. WireGuard is strict about key formatting and file permissions.
The wg-quick service will refuse to start and print Error: Could not read private key if the file contains trailing newlines or Windows-style line endings. Run dos2unix /etc/wireguard/wg0.conf to clean the file.
If you see [FAILED] Failed to start WireGuard via wg-quick(8) for wg0 during boot, your configuration probably references a missing interface name or contains a syntax error. Run wg-quick strip wg0 to validate the configuration without applying it.
SELinux rarely blocks WireGuard because the package ships with proper policies. If you see denials, check journalctl -t setroubleshoot. Do not disable SELinux to fix a routing issue. The denial is usually about a custom script in PostUp, not the tunnel itself.
Firewall rules are the most common blocker. Fedora's firewalld drops UDP 51820 by default. If you skip the PostUp hook, run sudo firewall-cmd --permanent --add-port=51820/udp && sudo firewall-cmd --reload. Always reload after a permanent change. The runtime configuration and the persistent configuration diverge otherwise.
Reboot before you debug. Half the time the symptom is gone after a clean boot cycle.
When to use WireGuard versus alternatives
Use WireGuard when you need a fast, modern VPN with minimal configuration overhead. Use OpenVPN when you must support legacy clients or require TCP fallback. Use IPsec when your organization mandates enterprise-grade certificate management and IKEv2. Use wg-quick when you want systemd integration and automatic firewall hooks. Use raw wg commands when you are building a custom network stack and need granular control over routing tables.