UniClipboard
Try onlineBlogCompareUse casesDocsChangelogSponsor
Install
UniClipboard

Clipboard sync between your own devices. Built in the open.

Links
RepositoryIssuesReleasesDocsSponsor
Built with
RustTauriirohshadcn/ui
Contact
X · @uniclipboardX · @mkdir700Bilibili · @mkdir700[email protected]
© 2026 mkdir700made with Rust 🦀 · AGPL-3.0
UniClipboard
Try onlineBlogCompareUse casesDocsChangelogSponsor
Install
UniClipboard

Clipboard sync between your own devices. Built in the open.

Links
RepositoryIssuesReleasesDocsSponsor
Built with
RustTauriirohshadcn/ui
Contact
X · @uniclipboardX · @mkdir700Bilibili · @mkdir700[email protected]
© 2026 mkdir700made with Rust 🦀 · AGPL-3.0
UniClipboard
Try onlineBlogCompareUse casesDocsChangelogSponsor
Install
HomeBlogWhy iroh, not libp2p or WebRTC

Last updated·2026-05-05

Why iroh, not libp2p or WebRTC: Building a P2P clipboard

A year of building a serverless, cross-device clipboard came down to one interesting decision: which P2P stack. Here's the long version of that answer.

I’ve been building UniClipboard for about a year now. It’s a clipboard sync app for macOS, Windows, and Linux.

The boring part turned out to be most of the work. Clipboard APIs are weird in different ways on every OS. Images are annoying. Files are more annoying. Packaging a Tauri app for three platforms is its own little punishment.

But one part was actually interesting: picking the P2P layer.

I wanted UniClipboard to work without an account and without a central server holding clipboard data. Pair two devices, copy something on one, paste it on the other. Ideally before you even think about it.

That sounds simple until you remember home routers exist.

So I tried a few options: libp2p, WebRTC, Syncthing’s protocol, magic-wormhole, and eventually iroh.

This is why I ended up with iroh.

What the network layer had to do

For UniClipboard, I did not need a global peer network.

There is no “clipboard swarm”. No public discovery. No DHT. No content-addressed storage.

Most users have two to five devices:

  • a MacBook
  • maybe a Windows desktop
  • maybe a Linux box somewhere
  • maybe another laptop later

Those devices are paired explicitly. After that they need to keep talking.

The network layer had to handle a few things:

  • small messages, most of the time
  • occasional images or files
  • long-lived connections between known devices
  • NAT traversal that works in normal ugly networks
  • a relay fallback when direct connection fails
  • no browser requirement

The latency target is also different from file sync. Clipboard sync feels broken if it takes a few seconds. If I copy a command on one machine and move my hand to paste it on another, the entry should already be there.

Not “eventually consistent”. Just there.

libp2p was the obvious first try

I started with libp2p because, well, it’s libp2p.

It is mature. It has a Rust implementation. It powers IPFS and a lot of other real systems. If you search “Rust P2P”, it is probably one of the first things you find.

And it works.

The problem is that it works like a framework for building a protocol stack. I needed something closer to “dial this peer and give me a connection”.

With libp2p I had to think about transports, secure channels, multiplexing, peer identity, identify protocol, relay client, hole punching, swarm behavior, DHT choices, and a bunch of version/configuration details before I could send the first real application message.

That is not libp2p being bad. That is libp2p being what it is.

For a bigger peer network, or a protocol where you actually need that flexibility, it makes sense. For a clipboard app, it felt like building a small operating system before I could copy “hello”.

I had a prototype after a couple of weeks. It mostly worked. But “mostly” is the dangerous word here. NAT edge cases were hard to reason about, and when something failed, I was not always sure which layer was the problem.

That was the point where I started looking for something smaller.

WebRTC felt wrong pretty quickly

WebRTC is great if one side is a browser, or if you are doing audio/video.

UniClipboard is not that.

I do not need media tracks. I do not need browser support. I do not want to drag a giant WebRTC stack into a desktop app just to send clipboard entries.

The other issue is signaling. WebRTC gives you a lot, but peers still need to find each other somehow. So now I would have to run signaling infrastructure anyway, or build my own pairing/signaling story around it.

And if I need relay fallback, TURN can do it, but then I’m paying bandwidth and operating another service-shaped thing.

Could it be made to work? Sure.

Would I enjoy maintaining it for a desktop clipboard app? No.

Syncthing is great, but it syncs folders

I also looked at Syncthing’s BEP protocol because Syncthing is honestly impressive.

It already does encrypted sync between known devices. It has a relay network. It has years of real-world NAT pain baked into it.

But Syncthing syncs folder state.

That sounds obvious, but it matters. Its loop is basically:

  1. file changes
  2. filesystem watcher fires
  3. Syncthing scans
  4. hashes blocks
  5. sends index updates
  6. peers request blocks
  7. blocks transfer

That is a good shape for “keep this music folder synced”.

It is a bad shape for “I copied a token and want it on the other machine now”.

I could have stuffed every clipboard entry into a temp file and waited for it to sync. That would probably work in a demo. It would also be cursed. And the first time latency felt bad, I’d be debugging around a protocol that was never trying to solve this problem.

So I dropped that path.

magic-wormhole is beautiful, but too one-shot

I like magic-wormhole a lot.

The human-readable codes are great. The PAKE design is nice. The “no account, send something to another device” experience is very close to the kind of thing I like.

But magic-wormhole is a one-shot transfer tool.

You create a code, the other side enters it, something gets transferred, and then the session is done.

UniClipboard needs paired devices that keep a relationship. They reconnect tomorrow. They send tiny messages all day. They survive network changes. They need a channel, not a one-time handoff.

You could use magic-wormhole for pairing, derive keys, then build a long-lived encrypted transport somewhere else.

But then I’m building the transport anyway.

iroh was the first one that felt like the right shape

iroh gave me the API shape I wanted.

Not a pile of protocol components. More like:

let endpoint = Endpoint::builder()
    .secret_key(secret_key)
    .alpns(vec![ALPN.to_vec()])
    .discovery_n0()
    .bind()
    .await?;

let conn = endpoint.connect(peer_node_addr, ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await?;

That is much closer to the mental model I wanted:

I have a peer. I know its address/key. Give me a connection.

Under the hood, iroh uses QUIC through Quinn. NAT traversal is handled inside iroh-net. Relay fallback is part of the model. Direct connection upgrade is automatic when it works.

From the app side, I do not need to care whether the first packets went through a relay and then upgraded to direct. I still have a connection.

That matters a lot for a small app.

The P2P part of UniClipboard ended up being roughly a few hundred lines of Rust: endpoint setup, ALPN, connection lifecycle, message framing, some peer discovery glue.

The libp2p version was much larger and still felt less boring. And boring is exactly what I want from this layer.

The relay question

A relay is still infrastructure. There is no magic here.

If direct P2P fails, traffic needs to go somewhere. With iroh, it can fall back to relay. The relay sees peer IDs and encrypted bytes. It does not see clipboard contents.

That distinction matters, especially for a clipboard app. Sometimes the clipboard is just a URL. Sometimes it is a password, an API key, or half a config file you definitely did not want sitting in some random service forever.

So I care a lot about what the relay can see.

Right now, iroh’s default relay/discovery infra makes the app much easier to use. Users do not have to host anything. They pair devices and it works.

But there is a tradeoff: if you are strict about self-hosting every piece of infrastructure, hosted relay fallback is still hosted relay fallback. Even if it only sees ciphertext.

That is one of the reasons UniClipboard has LAN-only mode, and why a self-hosted relay story matters.

What about Tailscale?

I use Tailscale. It is excellent.

But I did not want UniClipboard to require it.

If UniClipboard required Tailscale, then every user would need:

  • a Tailscale account
  • every device in the same tailnet
  • Tailscale’s control plane as part of the trust/setup story

For some users, that is totally fine. If you already live inside Tailscale, you could probably build 80% of a clipboard sync setup with a small daemon and some private networking.

But that is not the product I wanted.

I wanted “install on two machines, pair them, done”. No account. No tailnet. No “first go set up another thing”.

Which is not always easy, but it is the point.

The risk with iroh

iroh is younger than libp2p.

That is the main downside. Smaller ecosystem. More API churn. I have already had to do a couple of 0.x migrations that were not just search-and-replace.

So the bet is not “iroh will never change”. It will.

The bet is that the part I depend on is small:

  • Endpoint
  • Connection
  • SecretKey
  • NodeAddr
  • QUIC streams
  • relay fallback

If those primitives stay sane, I can absorb some churn around them.

And if iroh disappeared tomorrow, the underlying pieces are not exotic. QUIC, STUN-ish discovery, DERP-style relay. Rebuilding the exact SDK shape on top of Quinn would be annoying, but bounded. Maybe a few weeks. Not fun, but not existential.

For me, that risk was worth it.

I would rather spend those weeks later if I have to, instead of spending every week now maintaining a networking stack I barely need.

How I’d choose today

If I were picking again in 2026, I’d roughly think about it like this:

Use iroh if you are building a desktop app and want a small set of known devices to talk to each other.

Use libp2p if you are actually building a protocol or a large peer network, and you need the flexibility.

Use WebRTC if browsers or media are central to the product.

Use Syncthing if the thing you want is folder sync. It is very good at that. Clipboard sync is not folder sync.

Use magic-wormhole for one-shot transfers or pairing flows.

Use Tailscale if your users already live in Tailscale and you are okay making that a requirement.

For UniClipboard, iroh still feels like the right tradeoff.

Small payloads. Known devices. Desktop only. P2P when possible. Relay fallback when needed. No account.

That is the shape of the problem, and iroh fits it better than the other options I tried.

About UniClipboard

UniClipboard is an open-source clipboard sync app for macOS, Windows, and Linux.

It syncs text, images, and files. Devices pair without an account. They try direct P2P first, and fall back to encrypted relay traffic when that does not work.

The relay cannot read clipboard contents.

Repo: https://github.com/uniclipboard/uniclipboard
Site: https://uniclipboard.app

If you disagree with the networking choice, I’d actually like to hear why.

On this page

  1. What the network layer had to do
  2. libp2p was the obvious first try
  3. WebRTC felt wrong pretty quickly
  4. Syncthing is great, but it syncs folders
  5. magic-wormhole is beautiful, but too one-shot
  6. iroh was the first one that felt like the right shape
  7. The relay question
  8. What about Tailscale?
  9. The risk with iroh
  10. How I’d choose today
  11. About UniClipboard
UniClipboard

Clipboard sync between your own devices. Built in the open.

Links
RepositoryIssuesReleasesDocsSponsor
Built with
RustTauriirohshadcn/ui
Contact
X · @uniclipboardX · @mkdir700Bilibili · @mkdir700[email protected]
© 2026 mkdir700made with Rust 🦀 · AGPL-3.0