Protect your relays - Iroh<br>Blog Index<br>When two devices can't get a direct connection, a relay carries the<br>connection so data still flows. If the relay accepts anyone, then anyone who<br>learns its URL can push traffic through it. And they will learn it: it ships<br>inside every client you distribute and it's visible<br>to anyone watching a connection get established.
Because of this, we've decided that managed relays on Iroh Services are now<br>authenticated by default . Only endpoints carrying a token issued by your<br>project's API key can use them.
There's nothing to switch on. If you already connect through the<br>iroh_services preset, your endpoints authenticate themselves.
One caveat: this is the default for relays deployed from June 2026 onward. If<br>you deployed a relay before then, it stays open, so nothing breaks for the<br>endpoints already using it. To turn it on, head to your relay's<br>authentication settings under Relays > Settings.
The problem: a relay URL is a credential you can't revoke
Someone finds your relay URL in a public repo, a client bundle, or a screenshot,<br>and starts spamming your infrastructure until it falls over.
You spent effort spinning up your own relay, but someone else's traffic still<br>competes with yours. A relay has finite bandwidth and finite connection slots,<br>whether it's a box you're renting, a VM under your desk, or capacity you're<br>paying us for, and whoever else found the URL is now using it.
If you run your own relays, you can build your own authentication scheme -- iroh<br>is unopinionated about that. But if you're using our managed relays, until this month we didn't<br>give you a way to easily control access. Now we have shipped the first piece of<br>the authentication puzzle -- API keys. You can issue, rotate, and delete them without limits.<br>These are the same API keys you already use to push metrics, so if you're on<br>Iroh Services you have one.
Deploy a dedicated relay, free for 30 days.
How it works
Every relay connection starts with an HTTP handshake, the same one that upgrades<br>to the websocket. Authentication travels in a standard header:
Authorization: Bearer<br>CopyCopied!
The token is a signed capability token. It carries four things:
who issued it: your project's API key
who it's for : the public key of the endpoint presenting it
what it grants : permission to use the relay, and nothing else
when it expires : can be set to a short time window, so if it is compromised it can't be used for long
When an endpoint connects, iroh's relay handshake first proves the endpoint<br>actually owns its key. It does this for every connection, authenticated or not.<br>Then the relay checks the token: is the signature valid, is it unexpired, does<br>it grant relay use, is it addressed to this exact endpoint, and was it issued by<br>one of your project's API keys? If every answer is yes, the endpoint is<br>admitted.
Two properties fall out of this that we like.
A leaked URL is harmless. Without a token issued by your API key, dialing it gets you nothing.
A leaked token enables connections, but not impersonations. The token is addressed to one specific endpoint's public key, so presenting it from a different endpoint fails: the handshake would still have to prove ownership of that endpoint's secret key, which the token alone does not give you.
Revocation follows the same path. Your API key is the identity the relay<br>recognizes, so rotating or deleting a key stops honoring tokens it issued, and<br>connections riding those tokens are dropped.
Connecting an endpoint
You don't assemble any of this by hand. The iroh_services preset mints the token from your API secret and attaches it to every relay connection for you. Building an authenticated endpoint is the same few lines you would write anyway:
use iroh::Endpoint;
#[tokio::main]<br>async fn main() -> anyhow::Result {<br>let preset = iroh_services::preset()<br>.relays(["https://us-east1.your-project.iroh.link"])?<br>.api_secret_from_env()? // reads IROH_SERVICES_API_SECRET<br>.build()?;
// The endpoint now reaches your managed relays, authenticated.<br>let endpoint = Endpoint::bind(preset).await?;
Ok(())<br>CopyCopied!
Your API secret never leaves your process. The preset uses it to derive a<br>relay-scoped token, and that derived token is what travels to the relay. Point<br>.relays(...) at the relay URLs from your project dashboard, set<br>IROH_SERVICES_API_SECRET, and that's it.
What's next
Today, each endpoint gets the same capabilities. In the future, we'll add the<br>ability to mint tokens with different scopes, so you can grant some endpoints<br>more permissions than others. Additionally, we will be allowing you to revoke<br>access to endpoints individually, and an API to do all of this outside of the<br>dashboard. If any of this sounds interesting to you, please reach out on<br>Discord and let us know.
If you're running relays today, deploy at least two in different regions so one<br>region going down doesn't strand your endpoints. The managed relays guide<br>walks through the full...