WireGuard handshake did not complete: causes
What the 'did not complete after 5 seconds' line means and how each fault looks on both ends in a dated lab run: wrong key, wrong port, dropped UDP, clock.
Key takeaways
- Four lab cases that never completed printed the same retry line, differing only in the endpoint it names; only the responder's log told them apart.
- wg show latest-handshakes prints 0 for a peer that never completed and a Unix timestamp for one that did.
- A clock one day ahead on either peer did not stop the handshake; a clock set back on the initiator did, for about 15 seconds.
- A wrong AllowedIPs on the responder left the handshake complete while every ping failed; only the responder's debug log said why.
On this page (8)
- How do I tell whether a WireGuard handshake ever happened?
- What does "did not complete after 5 seconds, retrying" mean?
- How did we reproduce each fault?
- What does each fault look like on both ends?
- Can AllowedIPs stop traffic when the handshake completed?
- Does a wrong clock stop the WireGuard handshake?
- What should I check first, in order?
- What does ForestVPN's net.blocked_at_handshake mean?
In the WireGuard protocol, the initiator sends one message and waits for a response; "handshake did not complete" means none arrived. The line never says why. In our lab run the initiator printed it for four different faults, and the log on the other end was the only thing that told them apart.
Below: what each fault looks like on both ends, from one dated run on Linux network namespaces, and a test of the claim that a wrong clock stops the handshake. Self-run WireGuard comes first, then a short section for ForestVPN app users.
How do I tell whether a WireGuard handshake ever happened?
Run wg show latest-handshakes on the peer that starts the connection. The wg(8) manual describes the output as each peer's public key followed by a Unix timestamp, and a peer that has never completed one shows 0.
# two outputs from two cases (baseline, and a wrong key), shown together
$ wg show latest-handshakes
1j1ewIgiceU6utI3BHoNNjVR5TYbbZ+KUUqX+7QkPWc= 1793256522 # completed
zWzbIiyPqFcZmY/h/y3lq1xd+TWc3ecPHKYG1senKHs= 0 # never completed
Plain wg show tells the same story in words. A peer with a handshake shows latest handshake: 13 seconds ago; a peer without one has no such line at all, and its transfer counter shows bytes sent and none received.
Do this first, before you touch a config. If the time is non-zero, the handshake is fine and the fault is elsewhere: AllowedIPs (the list of tunnel addresses a peer may send from and receive), routes or packet size. If it is 0, the rest of this page applies.
What does "did not complete after 5 seconds, retrying" mean?
It is the kernel module's retry message: an initiation went out, nothing valid came back, so another is sent, as the protocol page describes for the Noise_IKpsk2 handshake (named here, not modified), and the 5 seconds is the figure our run's log printed. Each retry bumps a counter, so you see (try 2), (try 3) and so on.
Two practical points. The line is a debug message, so it shows up only when dynamic debug is on for the module (module wireguard +p), and our run used exactly that. And it names the endpoint it is retrying, which is the first thing to read: if the address or port in the line is not what you meant to type, you have found the fault.
Apart from the endpoint, the line matched across the four silent cases in our run. That is the finding of this page.
How did we reproduce each fault?
Each case below was set up one at a time on a fresh pair of peers, with the initiator pinging once to start the handshake and about 16 seconds of observation.
How this was tested
- Product
- WireGuard kernel module with the stock wireguard-tools package (open source), two network namespaces joined by a veth pair
- Version
- wireguard-tools 1.0.20250521-1ubuntu1, nftables 1.1.6, tcpdump 4.99.6, kernel 7.0.0-38-generic
- System
- Ubuntu 26.04.1 LTS virtual machine in our home lab
- Checked
- Runs
- 1 per case, plus a baseline before and after the script fixes
Not tested
Peer A (initiator, 192.0.2.1) had peer B (responder, 192.0.2.2) as its endpoint; B had no endpoint for A. Both listened on UDP 51820, with tunnel addresses 10.99.0.1 and 10.99.0.2. Interfaces were made with ip link and wg set, not wg-quick. Kernel dynamic debug was on, and tcpdump ran on B's side of the link. Keys were throwaway and made fresh per case.
The ForestVPN section maps one code, net.blocked_at_handshake, from the app's published message catalog; we did not provoke it in the lab.
Each case ran once. Not tested: wireguard-go, wg-quick logs, preshared keys, IPv6, NAT or carrier-grade NAT in the path, a real router or firewall appliance, MTU (maximum packet size), Windows, macOS, Android, and other clock offsets than one day. tcpdump sees packets before the firewall input rules, so dropped initiations still appear in its count.
What does each fault look like on both ends?
| Cause | Initiator after 16 s | Responder log | UDP seen, initiations |
|---|---|---|---|
| Baseline | handshake set, pings 2 of 2 | initiation received, response sent | 1 |
| Responder holds a wrong key for A | latest handshake absent; retry line | Invalid handshake initiation from 192.0.2.1:51820 | 4 |
| A holds a wrong key for B | same retry line | Invalid MAC of handshake, dropping packet | 4 |
| A's Endpoint port is wrong | same retry line, showing the wrong port | nothing | 4 |
| B's input firewall drops UDP 51820 | same retry line | nothing | 4 |
| B's AllowedIPs for A excludes A's address | handshake set, pings 0 of 2 | Packet has unallowed src IP (10.99.0.1) | 1 |
| A's AllowedIPs has no route to B | Network is unreachable, no initiation sent | nothing | 0 |
Wrong key: which end shows which line?
A wrong key shows up as a rejected packet on the responder, and the wording depends on which side holds the wrong value. When the responder had a wrong public key for A, it logged Invalid handshake initiation. When A had a wrong public key for B, the responder logged Invalid MAC of handshake, dropping packet. In both, the capture showed 148-byte initiations and no responses.
The protocol page explains the split: the first message carries a MAC keyed to the responder's public key, so a wrong key for the responder fails that check before anything else.
Wrong port or dropped UDP: what is the difference?
Both leave the responder silent, and a packet capture (tcpdump on the veth) is what separates them. With the wrong port, the host at the other end answered every initiation with an ICMP (control-message protocol) "udp port 51821 unreachable", four in all, and WireGuard ignored them: the retry went on regardless. With an nftables input rule on the responder itself dropping UDP 51820, all four initiations arrived at B's interface and there was no reply of any kind.
So silence plus ICMP means the endpoint address is right and nothing listens on that port. Silence without ICMP means the packets get no answer: a firewall on the responder host, a filter on the way, a NAT (network address translation) device that does not pass them, or a wrong address.
Can AllowedIPs stop traffic when the handshake completed?
AllowedIPs can stop traffic without stopping the handshake. On the responder, a wrong range let the handshake finish and wg show printed a handshake time on both ends, yet every ping failed. Only the debug log said why: Packet has unallowed src IP (10.99.0.1) from peer.
On the initiator, a range that excluded the peer's tunnel address left no route, so the ping failed locally with "Network is unreachable" and no initiation was ever sent. If your retry line never appears, check the routes first.
Does a wrong clock stop the WireGuard handshake?
Not for a clock that is simply wrong. VPNsmith lists clock skew as a cause; we tested it rather than repeating it.
| Case | Handshake | Detail |
|---|---|---|
| Initiator A one day ahead, fresh start | completed, pings 2 of 2 | normal log |
| Responder B one day ahead, fresh start | completed, pings 2 of 2 | normal log |
| A set one day back after a completed handshake, interface recreated | failed, then completed after about 15 s | B logged Invalid handshake initiation three times |
In the third case, B refused A's initiations because they carried an older timestamp than the last one B had accepted from that peer. B then started its own handshake, which its kernel log ties to having stopped hearing back from A, and that one completed. The protocol page describes the timestamp in the first message as replay protection, which fits what we saw, though we did not read kernel source.
Narrow result: one offset (a day), and the backward case only on the initiator. A fresh pair with a clock a day off still connected here. A clock that moved backward on a peer the other side remembers can be.
What should I check first, in order?
- Run
wg show latest-handshakeson the initiator. A non-zero time ends the handshake hunt. - Read the endpoint in the retry line. Fix a wrong address or port.
- Read the responder's log.
Invalid MACpoints at the initiator's copy of the responder's public key.Invalid handshake initiationpoints at the responder's copy of the initiator's key. - If the responder logs nothing, capture UDP on its interface for the listen port. Packets arriving and no reply points first to the responder's input firewall (a capture on the interface runs before those rules, so it still sees dropped packets), then to the listen port or the interface. No packets arriving means look at the path, including NAT.
- If the handshake completes and pings fail, read AllowedIPs on both peers and the route on the initiator.
Our run set no PersistentKeepalive and used no wg-quick, so we say nothing about either here. If a NAT with a conntrack entry sits in your path, RFC 4787 is the reference for how it treats UDP.
Compare keys by their public values on each end, never by pasting private keys anywhere.
What does ForestVPN's net.blocked_at_handshake mean?
ForestVPN's apps have the matching verdict, named BlockedAtHandshake, with the code net.blocked_at_handshake. The wording differs by client: the desktop reads "We are sending connection attempts and the server is not answering any of them", and the home-screen card reads "Traffic isn't getting through" with the code under Details. Either way the app keeps sending handshake attempts and nothing comes back. That is the situation of the wrong-port and dropped-UDP rows above, seen from the initiator.
As the help page puts it, turn ForestVPN off and on again. If it keeps happening, try a different network, such as mobile data. There is no config file in the app for you to correct. For the full list of connection messages see VPN not connecting and, when the tunnel is up but pages fail, VPN connected but no internet.
Questions and answers
What does 'Handshake for peer did not complete after 5 seconds, retrying' mean?
The kernel sent a handshake initiation, got no valid response in time (5 seconds in our run's log) and is sending another. It says nothing about why. Read the other end's log: an invalid-initiation or invalid-MAC line points to keys, silence points to the port or the path.
How do I tell if a WireGuard handshake ever happened?
Run wg show latest-handshakes. A peer that never completed prints 0 after its public key; one that did prints a Unix timestamp. The plain wg show output omits the latest handshake line when there has been none.
Does a wrong clock stop the WireGuard handshake?
Not in our run. A clock one day ahead on either peer completed normally. A clock set back on the initiator after a completed handshake got its initiations rejected for about 15 seconds, until the responder started its own handshake.
Can AllowedIPs stop the handshake?
On the initiator, an AllowedIPs range without a route to the peer's tunnel address meant no initiation was sent at all. On the responder, a wrong range let the handshake complete but dropped every data packet.
What should I do when ForestVPN shows net.blocked_at_handshake?
Turn ForestVPN off and on again. If it keeps happening, try a different network, such as mobile data.
Sources
- wg(8) manual page (read October 29, 2026)
- WireGuard protocol and cryptography (read October 29, 2026)
- WireGuard Quick Start (read October 29, 2026)
- RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP (read October 29, 2026)
- VPNsmith: WireGuard handshake troubleshooting (read October 29, 2026)
How this was made
Drafted with AI assistance from the wg(8) manual, the WireGuard protocol page and ForestVPN's own help page, plus a lab run of two WireGuard peers in network namespaces on 2026-10-29, checked by automated gates and an editor.
