You bring the tunnel up, ping fe80::1 with the tunnel as the source interface, and get nothing back. It is not a firewall rule you forgot. A link-local address only means something on the single link where it was formed, and a routed VPN is not that link. The remote router's fe80::1 lives on the remote LAN segment; you sit one hop away on a different segment, and IPv6 has no mechanism for a router to forward link-local packets at all.

The fe80 prefix describes a scope, not a range

Link-local unicast is fe80::/10, and the defining property is not the number but the scope. The address is valid only on one link, and every interface forms one from its own identifier, which is why two unrelated machines on two unrelated networks can both answer to fe80::1 without the slightest conflict. The prefix basically says "on some link"; the link itself comes from context.

On a host, that context is the zone, or scope ID, you append after a percent sign: fe80::1%eth0 on Linux, fe80::1%3 or fe80::1%12 on Windows and in tools that expose interface indexes. The kernel resolves the address together with the interface index and then sends. Drop the zone and the destination is ambiguous, so the stack has no way to choose a segment.

Why a routed tunnel cannot deliver it

A routed VPN is a point-to-point interface carrying its own addresses, usually a /64 of unique-local or private IPv6 space inside the tunnel. The remote LAN is a separate link, reached by the tunnel endpoint routing and forwarding on your behalf. Those are two distinct segments, so an address from one is not a valid destination on the other.

The forwarding rule is explicit in the standard rather than a quirk of your client: routers do not forward packets with a link-local source or destination. That is why the packet dies even when the route table looks correct. Add a route for fe80::/64 pointing at the tunnel and a conforming stack either rejects it or accepts it and drops every matching packet: the destination scope says "this link only", and the outgoing interface is not that link.

Neighbor discovery reinforces the same boundary. Hosts resolve link-local next hops with neighbor solicitation sent to link-scoped multicast with a hop limit of 255, so routers can reject anything forwarded. Making that work across a tunnel would mean proxying discovery for a remote segment, which is Layer 2 bridging wearing a different hat.

There is a quieter second failure, and it trips up people who try to force the route anyway: the zone. Whatever zone you type locally names the tunnel interface, while the address you want is bound to the remote LAN interface. The two never match, so the packet cannot be steered onto the right interface at the far end.

What the failure looks like in practice

Tool output for a scope mistake reads like a routing or firewall error, which is exactly why people spend an afternoon in the wrong layer.

What you runWhat you seeWhat it means
ping fe80::1 with no zoneInvalid argument on Linux, or a transmit failure on WindowsThe stack cannot pick an interface for an ambiguous destination
ping -6 -I tun0 fe80::1Destination unreachable, or a silent timeoutThe address is not on the tunnel link, so there is nothing on-link to answer
ssh user@fe80::1 after the tunnel is upHangs, then no route to hostThe SYN never left the machine in a deliverable form; the server logs nothing
ip -6 neigh show dev tun0Empty tableNo neighbor entry can exist for a link-local host that is not on this link
fe80::/64 inside AllowedIPs or route-ipv6Config accepted, traffic still droppedThe tunnel carried the packet; the receiving stack refused to forward it

The third row wastes the most time, because an ssh client with no output looks like a listening-service problem. If you have any way in, check the far side and confirm whether sshd is bound to all addresses or just one.

Use an address that was meant to be routed

For anything you need to reach through the tunnel, use the address the service was given for that purpose: a global unicast address, or a unique-local address out of fc00::/7, the range reserved for exactly this internal case. Confirm the listener actually binds it, and confirm the return path and any host firewall rule.

Look at the remote host rather than guessing. ip -6 addr show lists the addresses that exist, and ss -6 -ltn lists what the daemon is listening on. A service bound only to fe80:: is reachable only from its own segment, which is usually a default nobody chose on purpose and can be changed with the administrator's agreement.

When the service genuinely cannot be rebound, move the bridge up to the application layer instead of the network layer. A relay on the remote host that listens on a routable address and connects to the link-local one solves the problem without extending any segment:

socat TCP6-LISTEN:8080,bind=[fd00:1:2::10] TCP6:[fe80::1%eth0]:80

Here the zone sits on the correct side, on the host that actually has eth0 on that link. Your client talks to fd00:1:2::10, which routes normally, and the relay makes the link-local hop locally. The same trick works for an appliance web UI or an old BMC that only advertises a link-local address.

When bridging really is the answer, and what to check

Some deployments do need one shared segment: a device that only talks link-local discovery, an appliance that depends on multicast, a lab that reproduces a flat LAN. That is a Layer 2 problem and it needs a Layer 2 answer, such as a tap device bridged to the remote interface or a client product with an explicit bridged mode. It costs you broadcast and multicast traffic crossing the tunnel plus neighbor discovery that must work end to end.

Do not approximate it by adding fe80::/64 to a routed profile. That produces the worst state of all: the config looks finished, the route table shows an entry, and every packet is still discarded somewhere between two stacks that are both behaving correctly.

Verify with the interface rather than the ping. ip -6 addr show tun0 tells you whether the tunnel link formed a link-local address of its own, and ip -6 route get fe80::1%tun0 tells you what the kernel would actually do with a scoped destination. When the kernel says the destination is unreachable on that interface, no tunnel setting will change that, and it is time to stop editing the profile.

A short sequence that settles the question:

  • Read the address on the remote host and note whether it is fe80:: or a routable prefix.
  • If it is fe80::, rebind the service, relay on that host, or bridge the segment; if it is routable, fix the route, firewall, or bind address.
  • Recheck the zone every time you retest, because a copied command usually keeps the wrong interface.

Sources: RFC 4291, IPv6 addressing architecture.

Winndannde jowitiiɗe heen