Blog|productApril 15, 2026

Meet tailscale-rs, our new Rust library preview

Green shapes—ovals, quarter-circles, squares, circles—of two different shades of green, against a tea-green light background.

We’re building a Rust library version of Tailscale to help more developers bring Tailscale into their applications. If you’ve used tsnet in Go, it’s pretty much that, but for languages that aren’t Go. We have initial bindings for Python, Elixir and C, and of course you can use the library natively in Rust as well.

We’ve published the code on GitHub as an experimental preview (do not use it in production yet! Read the warnings!). Please give it a go, give us feedback, and tell us what you’d like to see.

That’s the main announcement done, if you’re in a hurry. The rest of this post goes a little bit into why we’re doing this, and where we see it going.

Why Tailscale as a library?

Because it’s quite useful!

Tailscale’s goal is to build the new internet, one in which programs can talk to each other as easily and securely as on the LANs of old, without forcing everything to actually be on a small physical LAN in one place. We built Tailscale as a client application, which manifests that idea in a straightforward way: attach a virtual network interface to the system, and let the rest of the computer treat it like an ordinary LAN.

This works fine for a lot of things (and looks reassuringly like a corporate VPN solution, which I’m told is useful for sales), but sometimes doing this plumbing at the OS layer is inconvenient, or downright impossible in some environments: systems with stripped-down kernels that lack pieces we need, container environments that don’t allow changes to the OS network stack, and so on.

We felt this pain ourselves as we were building and using Tailscale, so we made tsnet, “Tailscale as a library,” that you can bundle into your own programs directly. That way you end up with a standalone program that just happens to have access to your tailnet while it goes about its business.

tsnet is great, and we’ve written a ton of software with it: setec for production secrets management, tsidp to expose Tailscale identities through OpenID Connect, the tclip pastebin, the golink link shortener, and many more internal tools that we haven’t published. Our community found it just as useful, and built their own tools and services using tsnet (e.g. the tnsrv reverse proxy, or the chat-tails messaging service).

Why build another tsnet?

Because, unbelievably, programming languages other than Go continue to exist and be useful.

If you’re a Go programmer, you have tsnet and life is good. But if you’re using another language, well … we did try to build something for that, and the result is libtailscale, tailscale as a C library. But the way it works is that the first time you call into libtailscale, it spins up an entire Go runtime inside your process, and then just runs tsnet again with some C glue on the front. The problem with that is the Go runtime needs to take over a bunch of the process lifecycle to function, and that goes poorly if there’s already another runtime trying to do that. For example, mixing libtailscale with a Ruby VM is a quick way to end up with a crash, because the two runtimes will step on each other’s toes and eventually blow themselves up.

Even though I personally enjoy writing Go, I’ve found myself in situations where I could really use something like tsnet, but in a Django web app (Python), or a Godot game (GDScript or C#). In those situations I’d briefly consider libtailscale, before remembering that it’d probably end in tears. And porting all of Godot to Go just to have tsnet is a bit of an overreaction, and also quite a lot of work.

Some of our users clearly feel the same way. Over the years we’ve been approached many times with a problem statement that boils down to: please can we bundle Tailscale into our own software, so that we can have nice things without having to walk our own users through installing and setting up a third-party app? I don’t like having to say “We’d love to, but we can’t”. Some folks still make it work (for example LM Studio’s recent LM Link), but we could definitely make that development experience better for them.

One of those conversations last year prompted us to explore more seriously what it’d take to change our default answer. One thing led to another, and tailscale-rs is the result.

Why Rust?

Because it’s the best choice that met all our requirements.

Our starting point was “libtailscale, but that plays nice as a guest in someone else’s program”. That means we can’t have a language runtime that has strong opinions about how a process should be run, which rules out Go (and, if we want to be exhaustive, Python, Ruby, C#, Java, Haskell, …).

In the past, the obvious answer was C. It’s the lingua franca of computing. Every general purpose programming language knows how to talk to C code, and while the “high-level assembler” reputation is a bit inaccurate, there’s not much hidden stuff going on beyond what your code says. In the same vein, you could make a weaker argument for a tastefully chosen subset of C++.

However, in today’s world, I think you need an overwhelmingly good reason to start writing new code in a language that lacks basic memory safety properties. If a language lets you do things like out-of-bounds array access or arbitrary unchecked pointer arithmetic without having to explicitly push the “I’m in danger” button first, you should only use it if you really, truly have no other option.

So, our ideal language is one that doesn’t carry a mandatory opinionated runtime, is as universally interfaceable as C, and has a good memory safety story. It also wouldn’t hurt to have something fast: we’re trying to push packets around at tens of gigabits/sec, if we can. From a business standpoint, we should have reasonable confidence that it’ll stick around and be healthy for more than 10 years. We’d also like a robust ecosystem of code and tools that we can rely on, and experts we can hire.

Our conclusion was that Rust is clearly the best match for these requirements.

OMG are you rewriting Tailscale in Rust?

No. Well, yes and no.

Tailscale-rs is a reimplementation of the core of Tailscale in Rust. So in that sense we’re rewriting it in Rust. But on the other hand this Rust implementation isn’t replacing Go in the existing use cases (desktop apps, tsnet and so forth).

Just like the choice of Rust as an implementation language, going for multiple implementations is a pragmatic choice with tradeoffs, rather than an ideological one. If we want to bring Tailscale as a library to other languages, we can’t have just Go. So really, the question is whether we want to maintain two implementations of Tailscale, or just one (which would have to be Rust).

Obviously, framed like that, my immediate answer would be: Please, let’s have just one implementation. Maintaining two implementations and keeping them interoperating smoothly is a lot of work, not to mention implementing all new features twice.

But that framing assumes we can just wave a magic wand and get to feature and performance parity overnight (please don’t say AI, this is a serious discussion), and swap out the implementations without breaking anything. That’s not realistic, and in some cases it’s plainly impossible. To add tsnet to your code, you run go get tailscale.com/tsnet. The go tool doesn’t know how to build a mixed Go/Rust package, so no matter what, this would be a breaking change for some of our users.

Our Go implementation also has six years of feature development, debugging and optimization, and it’s going to take a while for tailscale-rs to catch up to that. While we’re catching up, we can’t just stop shipping features and improvements to our existing userbase. And that means we’ll be actively developing both implementations at the same time.

We did think about doing an in-place incremental rewrite, swapping out bits and pieces of the Go implementation with Rust components over time. We concluded this would be the worst of all worlds: we’re still a mostly Go shop, so having to work in a mixed Go/Rust codebase would hurt our ability to ship stuff. We’d have to structure the new Rust code along the same lines as existing Go components, which will make it harder to write good, idiomatic Rust. And we’d have to do this difficult refactor while keeping the whole thing stable and functional for our existing users, slowing us down even further.

And so, for the foreseeable future, two implementations is how we can get something like tsnet into more places, with the least amount of pain for everyone involved.

What’s implemented today?

The basics, enough to do some demos and have packets flowing.

The top-level exported API looks like a slightly trimmed tsnet: you can spin up a Tailscale device in your program, give it some configuration, and then speak TCP and UDP to other devices in your tailnet. That includes interoperating with Go tailscale clients.

In addition to using this library in Rust directly, we have FFI bindings into Python, Elixir, and C, with the same API surface and feature set as Rust. And for Rust code, we also have a couple of utility crates to glue Tailscale into axum, and to help you write CLIs that expose the right flags and settings.

What’s not there yet is everything else, more or less. Peer-to-peer communication and NAT traversal are TODO, so all traffic to other devices goes through DERP and will have limited throughput (we also haven’t spent much time yet optimizing the data plane, so there are probably other bottlenecks). There’s no DNS resolution, so you have to dial things by IP address. More advanced networking features like exit nodes and app connectors won’t work. Higher level features like Tailscale SSH, Taildrop, Taildrive, and device posture aren’t there, and may never be, as they aren’t a great fit for a library use case. The code hasn’t been externally audited yet, so you should be conservative in how much you trust its security. I’m sure I’m forgetting other stuff that we haven’t implemented yet.

So, yeah, it’s early days, and we have a lot of work still to do. But we wanted to show you what we’ve been up to as soon as the code could do something, and continue building in the open with your feedback, rather than wait for it to be perfect.

What’s next?

In the immediate future, we have some obvious things to work on next: peer-to-peer communication and NAT traversal for direct connections are obviously the next big-ticket item (good thing some guy wrote down how NAT traversal works, that might come in handy). We’re also filling out the gaps in the networking feature set: DNS, exit nodes, TLS certificates, and so on.

From there, our long-term goal is to give you a way to plug Tailscale into your code, no matter what you’re up to, and reach for that ideal of being as ubiquitously available as the internet. So, we’re going to keep building out the feature set that tailscale-rs supports, plumb it into more languages, improve its performance, write more documentation and guidance for developers …

This is where we need your input! Now that we’ve got the basics working, we have an absolute smorgasbord of things to do in order to get from experimental-grade software to 1.0, enough to keep us busy for a while. We have a clear roadmap in the short term, but we’d love to know where you think we should take things beyond that. I can’t promise we’ll work on Tailscale for COBOL right away, but if that’s something people crave, we do want to at least know about it and have it on the planning board.

So, if tailscale-rs sounds like something you want to use, please go and kick the tires and give us feedback! You can file issues and feature requests on our GitHub (please do a quick check for duplicates and upvote existing issues if applicable), and we’re also lurking on Tailscale’s community Discord if you just want to chat or ask questions.

Share

Author

David AndersonDavid Anderson
Loading...

Try Tailscale for free

Schedule a demo
Contact sales
cta phone
mercury
instacrt
Retool
duolingo
Hugging Face