{"id":167804,"date":"2026-05-20T23:07:17","date_gmt":"2026-05-20T20:07:17","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=167804"},"modified":"2026-05-21T01:40:05","modified_gmt":"2026-05-20T22:40:05","slug":"wireguard-vpn-fedora","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/wireguard-vpn-fedora\/","title":{"rendered":"Setup WireGuard VPN on Fedora 44 \/ 43 \/ 42"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">WireGuard ships in the upstream Linux kernel and has been the default modern VPN choice on Fedora for years. The tooling is small (one userspace package, one CLI), the config files are short, and a working tunnel between a Fedora server and a phone or laptop takes about fifteen minutes from a clean install. There is no daemon to keep running, no certificate chain to renew, and the protocol&#8217;s resistance to traffic analysis makes it the right pick for both remote-worker access and personal &#8220;always-on&#8221; mobile VPNs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide installs WireGuard tooling on Fedora, generates server and client keypairs, writes the two configuration files, opens the right firewalld port, brings the wg0 interface up, and shows how to render the client config as a QR code that a phone WireGuard app can import in a single scan. Every command was executed on a real Fedora install and the captured output is what your terminal will show.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"800\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-install-fedora.png\" alt=\"dnf install wireguard-tools qrencode firewalld kernel module on Fedora\" class=\"wp-image-167796\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-install-fedora.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-install-fedora-300x261.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-install-fedora-768x668.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">What you will have at the end<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Fedora server running WireGuard on UDP\/51820 as the VPN endpoint.<\/li>\n\n\n\n<li>A 10.99.99.0\/24 private network with the server at 10.99.99.1 and one phone client at 10.99.99.2.<\/li>\n\n\n\n<li>firewalld zone configured to accept WireGuard traffic and masquerade outbound from the VPN subnet.<\/li>\n\n\n\n<li>A client config you can render as a QR code, then scan from the WireGuard mobile app to import in one tap.<\/li>\n\n\n\n<li>A systemd unit so the tunnel comes back automatically after reboot.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The same shape works for laptop clients; only the import step differs (paste the config file into <code>\/etc\/wireguard\/wg0.conf<\/code> on the laptop and run <code>wg-quick up wg0<\/code> there).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Install WireGuard tooling and supporting packages<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WireGuard itself is built into the Linux kernel that ships with Fedora, so there is no kernel module to install. Only the userspace tools (<code>wg<\/code> and <code>wg-quick<\/code>), the QR code renderer, and firewalld are needed:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo dnf install -y wireguard-tools qrencode firewalld<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the userspace tooling and the in-kernel module:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>wg --version\nrpm -q wireguard-tools qrencode firewalld\nmodinfo wireguard | head -2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>modinfo<\/code> line confirms the kernel module is present even before you bring an interface up. WireGuard has been in-tree since Linux 5.6, so every supported Fedora release has it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Generate keypairs for the server and the phone client<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WireGuard uses Curve25519 keypairs for both authentication and encryption. Generate one pair per peer:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>cd \/etc\/wireguard\numask 077\nsudo bash -c 'wg genkey | tee server.key | wg pubkey &gt; server.pub'\nsudo bash -c 'wg genkey | tee phone.key | wg pubkey &gt; phone.pub'\nsudo ls -l<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The command output is shown above.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"800\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-keygen-fedora.png\" alt=\"wg genkey wg pubkey generate WireGuard server and phone client keys on Fedora\" class=\"wp-image-167797\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-keygen-fedora.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-keygen-fedora-300x261.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-keygen-fedora-768x668.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Private keys live in the <code>.key<\/code> files (mode 600) and public keys in the <code>.pub<\/code> files. Treat the private keys like SSH private keys: they grant full VPN membership, never paste them anywhere outside the configuration files.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Write the server configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open <code>\/etc\/wireguard\/wg0.conf<\/code> in your editor and write the server-side config. Substitute the real key values from the files you just generated:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo $EDITOR \/etc\/wireguard\/wg0.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Server config body:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>&#91;Interface]\nAddress = 10.99.99.1\/24\nListenPort = 51820\nPrivateKey = &lt;contents of \/etc\/wireguard\/server.key&gt;\n\n# Forward and masquerade outbound traffic from VPN clients\nPostUp   = firewall-cmd --zone=public --add-masquerade\nPostUp   = sysctl -w net.ipv4.ip_forward=1\nPostDown = firewall-cmd --zone=public --remove-masquerade\n\n&#91;Peer]\n# Phone client\nPublicKey = &lt;contents of \/etc\/wireguard\/phone.pub&gt;\nAllowedIPs = 10.99.99.2\/32<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>AllowedIPs<\/code> in the server&#8217;s <code>[Peer]<\/code> stanza is the source filter, not a route: it tells the server which inbound IPs from this peer are legitimate. One address per phone.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Open the firewall and bring the interface up<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open UDP\/51820 in firewalld and turn on masquerading for the outbound interface. The PostUp lines in the config will reapply masquerade on every interface bring-up, but you need the permanent firewalld rules so the port survives a reboot:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl enable --now firewalld\nsudo firewall-cmd --permanent --add-port=51820\/udp\nsudo firewall-cmd --permanent --add-masquerade\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now bring the tunnel up. <code>wg-quick<\/code> reads the configuration file, sets the interface address, applies the peer list, and runs the PostUp hooks:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo wg-quick up wg0\nsudo wg show wg0\nip -4 addr show wg0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The command output is shown above.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"800\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-up-fedora.png\" alt=\"wg-quick up wg0 firewalld masquerade wg show on Fedora\" class=\"wp-image-167798\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-up-fedora.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-up-fedora-300x261.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-up-fedora-768x668.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The interface is now listening for inbound peer handshakes. To make it come back automatically after a reboot, enable the unit:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl enable wg-quick@wg0<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Write the phone client configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The phone config is the mirror of the server config. Save it as <code>\/tmp\/wg-phone.conf<\/code> on the server (you will not keep it there long, just long enough to QR-encode it):<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo $EDITOR \/tmp\/wg-phone.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Phone config body. Replace <code>vpn.example.com<\/code> with your server&#8217;s public hostname or IP:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>&#91;Interface]\nAddress = 10.99.99.2\/32\nPrivateKey = &lt;contents of \/etc\/wireguard\/phone.key&gt;\nDNS = 1.1.1.1, 9.9.9.9\n\n&#91;Peer]\nPublicKey = &lt;contents of \/etc\/wireguard\/server.pub&gt;\nEndpoint = vpn.example.com:51820\nAllowedIPs = 0.0.0.0\/0, ::\/0\nPersistentKeepalive = 25<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Three values matter most:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AllowedIPs = 0.0.0.0\/0, ::\/0<\/strong>: route ALL phone traffic through the tunnel. Drop to <code>10.99.99.0\/24<\/code> for split tunneling (only LAN reachable through VPN, regular internet stays direct).<\/li>\n\n\n\n<li><strong>Endpoint<\/strong>: the public address the phone connects to. Use a hostname behind dynamic DNS if your server&#8217;s IP changes.<\/li>\n\n\n\n<li><strong>PersistentKeepalive = 25<\/strong>: sends a keepalive packet every 25 seconds. Required when the client sits behind NAT so the NAT translation stays open.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Render the phone config as a QR code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The WireGuard mobile app imports configurations from QR codes. <code>qrencode<\/code> can render straight to the terminal in ANSI block characters; the phone scans the screen and the entire config flows across in one go:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo qrencode -t ansiutf8 &lt; \/tmp\/wg-phone.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The command output is shown above.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"800\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-qrencode-fedora.png\" alt=\"qrencode ansiutf8 WireGuard phone client config on Fedora\" class=\"wp-image-167799\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-qrencode-fedora.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-qrencode-fedora-300x261.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/05\/wm-wg-qrencode-fedora-768x668.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Open the WireGuard app on the phone, tap <strong>Add Tunnel<\/strong>, choose <strong>Create from QR code<\/strong>, scan the terminal. The tunnel imports with the right name, keys, and routes. Toggle it on and you are routing through the Fedora server. Toggle off when not needed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If terminal QR is awkward (font scaling, SSH session colour issues), write to a PNG instead and scan that:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo qrencode -o \/tmp\/wg-phone-qr.png &lt; \/tmp\/wg-phone.conf\n# Then copy the PNG off the server (sftp, paste in chat, etc.) and scan<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once the phone has the config, delete the working copy and the QR PNG from the server. The private key has already moved to the phone; leaving copies on the server adds risk without benefit:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo shred -u \/tmp\/wg-phone.conf \/tmp\/wg-phone-qr.png 2&gt;\/dev\/null\nsudo shred -u \/etc\/wireguard\/phone.key<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep <code>\/etc\/wireguard\/phone.pub<\/code>; the server still references it via the <code>[Peer]<\/code> stanza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify end-to-end connectivity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">From the phone, after the toggle is on, check that traffic exits via the server&#8217;s public IP:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Open a browser to <code>https:\/\/api.ipify.org<\/code>. The reported IP should match your Fedora server&#8217;s WAN IP, not the phone&#8217;s mobile-carrier IP.<\/li>\n\n\n\n<li>From the server, <code>sudo wg show wg0<\/code> should now show a non-zero <code>latest handshake<\/code> timestamp and traffic counters that go up when you browse on the phone.<\/li>\n\n\n\n<li>From the phone, ping <code>10.99.99.1<\/code>. The reply should arrive in a few milliseconds; that confirms the tunnel is up and routed.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Add a laptop client<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Repeat the keypair + config pattern on a laptop, with the laptop generating its own keypair locally so the private key never leaves that device:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code># On the laptop\nsudo dnf install -y wireguard-tools           # Fedora laptop\n# or: sudo apt install wireguard               # Ubuntu\/Debian\n# or: brew install wireguard-tools             # macOS\ncd \/etc\/wireguard\numask 077\nsudo bash -c 'wg genkey | tee laptop.key | wg pubkey'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Send <em>only<\/em> the laptop&#8217;s public key back to the Fedora server, then on the server append a new <code>[Peer]<\/code> stanza pointing at <code>10.99.99.3\/32<\/code>, reload the config, and the laptop joins:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code># On the server\nsudo wg set wg0 peer &lt;LAPTOP-PUBLIC-KEY&gt; allowed-ips 10.99.99.3\/32\nsudo wg-quick save wg0   # persists the runtime change to \/etc\/wireguard\/wg0.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The laptop config mirrors the phone config except <code>Address = 10.99.99.3\/32<\/code> and a different private key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Handshake never completes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>wg show<\/code> reports a peer with <code>latest handshake: (none)<\/code> after a minute of trying:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>From the phone, try a different network. Many corporate Wi-Fi networks block UDP\/51820.<\/li>\n\n\n\n<li>Confirm the <code>Endpoint<\/code> in the phone config is reachable: <code>nc -uvz vpn.example.com 51820<\/code> from another machine.<\/li>\n\n\n\n<li>Check the server&#8217;s firewall: <code>sudo firewall-cmd --list-all<\/code> must show <code>51820\/udp<\/code> in the ports list.<\/li>\n\n\n\n<li>Verify the PublicKey in the server&#8217;s <code>[Peer]<\/code> stanza matches the phone&#8217;s actual public key. A copy-paste typo here is the most common cause.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Tunnel comes up but the phone has no internet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ping <code>10.99.99.1<\/code> from the phone: if that works but external IPs do not, the server is not forwarding or masquerading. Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo sysctl net.ipv4.ip_forward         # must be 1\nsudo firewall-cmd --query-masquerade    # must be yes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Persist <code>ip_forward<\/code> across reboots:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>echo 'net.ipv4.ip_forward = 1' | sudo tee \/etc\/sysctl.d\/99-wireguard.conf\nsudo sysctl -p \/etc\/sysctl.d\/99-wireguard.conf<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Where this fits in the Fedora 44 Workstation series<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For non-Fedora hosts running the same protocol, the patterns translate directly to <a href=\"https:\/\/computingforgeeks.com\/configure-wireguard-vpn-rocky-almalinux\/\">Rocky Linux 10 and AlmaLinux 10<\/a> and to <a href=\"https:\/\/computingforgeeks.com\/install-wireguard-ubuntu-2604\/\">Ubuntu 26.04 LTS<\/a>. To restrict which subnets clients can reach (split tunneling at the server side) combine WireGuard with the firewalld rich rules covered in the <a href=\"https:\/\/computingforgeeks.com\/configure-firewalld-fedora\/\">Configure firewalld on Fedora<\/a> guide in this series.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>WireGuard ships in the upstream Linux kernel and has been the default modern VPN choice on Fedora for years. The tooling is small (one userspace package, one CLI), the config files are short, and a working tunnel between a Fedora server and a phone or laptop takes about fifteen minutes from a clean install. There &#8230; <a title=\"Setup WireGuard VPN on Fedora 44 \/ 43 \/ 42\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/wireguard-vpn-fedora\/\" aria-label=\"Read more about Setup WireGuard VPN on Fedora 44 \/ 43 \/ 42\">Read more<\/a><\/p>\n","protected":false},"author":21,"featured_media":167789,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29,299,47,50],"tags":[681,35849,254,583,2944],"cfg_series":[39847],"class_list":["post-167804","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-fedora","category-how-to","category-linux","category-linux-tutorials","tag-fedora","tag-linux-security","tag-networking","tag-vpn","tag-wireguard","cfg_series-fedora-44-workstation"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/167804","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=167804"}],"version-history":[{"count":2,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/167804\/revisions"}],"predecessor-version":[{"id":167813,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/167804\/revisions\/167813"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/167789"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=167804"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=167804"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=167804"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=167804"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}