Andrew Mercer
on this page

The short version

Every one of these networks lets you swap a platform-issued identity (you.bsky.social, @[email protected]) for [email protected] or anything.yourdomain.tld — but they prove domain ownership in three genuinely different ways, and only some of them require you to actually run server software. None of them require registering a new domain; you're just publishing a small DNS record or a static file under one you already control.

Network Mechanism Requires self-hosting? Subdomain as the whole handle?
Bluesky (AT Protocol) DNS TXT record or HTTPS well-known file, scoped to the exact hostname No Yes — natively, this is the normal case
Mastodon (ActivityPub) WebFinger alias — a static JSON file at /.well-known/webfinger No (if aliasing an existing account) — Yes (if you want mastodon.yourdomain.tld to be your actual home server) Yes, same mechanism, scoped per-hostname
Nostr NIP-05 — a static JSON file at /.well-known/nostr.json No Yes
Matrix .well-known/matrix/server and /client delegation No (delegation points at a homeserver that can live anywhere) Yes — delegation is specifically designed for this

The common thread: in every case, "linking a domain" means proving you control a hostname by publishing something at a well-known DNS record or HTTPS path for that exact hostname. Since you control DNS for any subdomain you create, each subdomain can independently prove ownership and carry its own, separate identity — nothing stops bsky.yourdomain.tld and mstdn.yourdomain.tld and nostr.yourdomain.tld from all existing simultaneously, each verified on its own.


Bluesky: custom domain or subdomain handle

Bluesky's handle system is DNS-based by design — this is one of the clearest expressions of atproto's DID-based identity model (see your AT Protocol notes: the handle is just a human-readable pointer to your actual DID, and DNS is simply one accepted way to prove you control that pointer).

  1. In the Bluesky app: Settings → Account → Handle → "I have my own domain."
  2. Enter the domain or subdomain you want as your handle — e.g. bsky.yourdomain.tld, not just yourdomain.tld.
  3. Bluesky shows you a TXT record to create. The record name is _atproto prefixed to whatever hostname you entered: - Handle yourdomain.tld → TXT record at _atproto.yourdomain.tld - Handle bsky.yourdomain.tld → TXT record at _atproto.bsky.yourdomain.tld
  4. The value is did=did:plc:xxxxxxxxxxxxxxxxxxxxxxxx (your actual DID, prefilled for you).
  5. Save the record with your DNS provider, wait for propagation (usually minutes, occasionally up to 24 hours), then hit Verify in the app.

This is the only method that works if your DNS is managed somewhere you can't easily drop arbitrary files onto a web server (e.g., a registrar's basic DNS panel, or a DNS-as-code setup like is-a.dev/Terraform) — you never need to serve a web page at all.

Method 2 — HTTPS well-known file (works for subdomains too, but needs a live web server at that exact host)

  1. Same starting point in Bluesky's settings, but choose the file-verification option instead of DNS.
  2. Host a plain-text file at https://<the exact hostname>/.well-known/atproto-did containing nothing but your DID string, e.g.: did:plc:xxxxxxxxxxxxxxxxxxxxxxxx
  3. If your handle is bsky.yourdomain.tld, the file must be served from bsky.yourdomain.tld/.well-known/atproto-did — not from the apex domain. That means you need an actual web server (or static host) answering at that specific subdomain, not just a DNS record pointing somewhere.

Which to pick: DNS TXT if you just want the identity and don't want to stand up hosting for every subdomain; the file method only if you're already serving a site from that exact host (e.g., you're using the subdomain for something else anyway) or your DNS provider makes TXT records awkward.

Can you run multiple Bluesky-linked subdomains?

Yes — each subdomain is verified independently and nothing links them together except that they share a parent zone. you.yourdomain.tld and art.yourdomain.tld could be two entirely separate Bluesky accounts, each with its own DID and its own TXT/file verification. There's no limit imposed by the protocol; the limit is just how many DNS records or web endpoints you're willing to maintain.


Mastodon: two very different things people call "linking a domain"

This is the one place where the question splits into two genuinely unrelated setups. Mastodon's handle format, @[email protected], ties your identity to the server you're registered on — there's no DID-style portable pointer the way Bluesky has. So "linking a domain" either means:

Option A — You want to actually run your own server at that domain

This is full self-hosting: Mastodon (or a lighter ActivityPub server like GoToSocial) runs at mastodon.yourdomain.tld, and your real handle becomes @[email protected]. There's no special protocol trick here — it's simply pointing an A/CNAME record at wherever you're hosting the instance, same as any other web service. This is the heavier option (ongoing server maintenance, moderation responsibility for your own instance, PostgreSQL/Redis/Sidekiq to run) and is what your existing install-mastdon.md notes are presumably building toward.

Option B — You want @[email protected] as an alias for an account you already have elsewhere

This is the interesting one, and it's the direct Mastodon analog of Bluesky's custom-handle trick — except Mastodon (and ActivityPub generally) has no official feature for it. It's a community-discovered workaround built entirely on WebFinger, the discovery protocol your ActivityPub notes already cover.

How it works: when someone searches for @[email protected], their server does a GET to https://yourdomain.tld/.well-known/webfinger?resource=acct:[email protected]. Normally your domain wouldn't answer that at all — but if you make it return the same JSON your real instance already returns for your real account, every client resolves @[email protected] straight through to your actual actor (inbox, outbox, posts, everything) on mastodon.social or wherever you actually live.

Setup steps:

  1. Fetch what your real account's WebFinger document looks like: https://your-real-instance.social/.well-known/webfinger?resource=acct:[email protected]
  2. Save that exact JSON response.
  3. Serve it, byte-for-byte, from your own domain at: https://yourdomain.tld/.well-known/webfinger?resource=acct:[email protected] In practice this usually means a tiny serverless function or static-host rewrite rule that responds to that query string with the saved JSON — a bare static file alone usually isn't enough, since the response needs to match the resource= query parameter.
  4. Tell people your handle is now @[email protected]. Searching for it resolves correctly; replying, following, and boosting all work normally because the actor document underneath is still your real one.

Caveats worth knowing before you build this: - It's static — if you ever migrate instances or change your username, you have to manually regenerate and re-publish the file. Nothing keeps it in sync automatically. - It's not officially supported by Mastodon; it's a side effect of WebFinger being a generic, pluggable protocol rather than a feature anyone designed. It can break if an instance changes its WebFinger response shape. - Since this works at the WebFinger/ActivityPub layer rather than anything Mastodon-specific, the same trick works for aliasing a Pixelfed or any other ActivityPub account to a custom domain — not just Mastodon.

Subdomains: works exactly the same way — serve the WebFinger response from mstdn.yourdomain.tld/.well-known/webfinger instead of the apex, and your alias becomes @[email protected]. Nothing about the mechanism cares whether the hostname is an apex domain or a subdomain.


Other networks

Nostr — NIP-05 verification

Nostr's equivalent is NIP-05, and it's the simplest of the four: your actual identity is already just a keypair (no server, no handle at all unless you add one), and NIP-05 is a voluntary, cosmetic layer that lets clients display [email protected] next to your profile and show a verified checkmark.

  1. Host a static JSON file at https://yourdomain.tld/.well-known/nostr.json: json { "names": { "you": "<your-pubkey-in-hex-format>" } } (If your client shows your key as an npub... string, convert it to hex first — most clients have a "copy hex pubkey" option.)
  2. In your Nostr client's profile settings, set your "NIP-05 Verification" field to [email protected].
  3. Clients that support NIP-05 will fetch that file, confirm the pubkey matches, and show the verified identifier.

Subdomains: fully supported — host the file at nostr.yourdomain.tld/.well-known/nostr.json and your identifier becomes [email protected]. You can also list multiple names in the same names object if you want several identities verified under one host.

Unlike Bluesky and Mastodon, there's no "move your whole account" implication here — NIP-05 never controls your actual identity (your keypair does, independent of any domain); it's purely a human-friendly, verifiable label layered on top.

Matrix — a genuinely different mechanism: server delegation

Since your docs already cover Matrix as the architectural odd one out, it's worth knowing its domain story is also different in kind, not just in detail — and it's arguably the most flexible of the four, because Matrix built delegation into the spec on purpose.

Your Matrix identity is @you:yourdomain.tld — the part after the colon, called the server_name, is permanent and baked into every event you ever send; it can't be changed later the way a Bluesky handle can be repointed. But server_name doesn't have to be where your homeserver software actually runs. Two well-known files handle that split:

  • https://yourdomain.tld/.well-known/matrix/server — tells other homeservers where to actually send federation traffic: json { "m.server": "matrix.yourdomain.tld:443" }
  • https://yourdomain.tld/.well-known/matrix/client — tells clients (like Element) where the homeserver's client API lives: json { "m.homeserver": { "base_url": "https://matrix.yourdomain.tld" } }

With these in place, your identity stays the clean @you:yourdomain.tld, while the actual Synapse/Dendrite/Conduit process can run on a subdomain, a different port, or even a third-party hosting provider entirely.

Subdomains: this is the native use case — the whole point of delegation is routing the apex identity to a subdomain (or anywhere else) running the real software. You can also skip delegation entirely and just accept @you:matrix.yourdomain.tld as your identity if you don't mind "matrix." showing up in every handle.

PeerTube, Pixelfed, Lemmy, WriteFreely

These are ActivityPub apps like Mastodon, so the same two options apply: self-host at a subdomain (normal DNS, no trick needed) or — since it's the same underlying WebFinger mechanism — alias an existing account to a custom domain the same way as the Mastodon workaround above. There's no dedicated per-app variant; WebFinger aliasing is protocol-level, not Mastodon-specific.


Answering the subdomain question directly

Yes — every network here supports giving each subdomain its own, independently verified identity:

  • bsky.yourdomain.tld → its own Bluesky DID verification (TXT or file, scoped to that host)
  • mstdn.yourdomain.tld → either your real self-hosted instance, or a WebFinger alias to an account elsewhere
  • nostr.yourdomain.tld → its own NIP-05 file
  • yourdomain.tld (apex) with Matrix delegated to matrix.yourdomain.tld → one identity, infrastructure split across hosts

None of these conflict with each other on the same parent domain, because verification in every case is scoped to the exact hostname, not the domain as a whole — _atproto.bsky.yourdomain.tld and _atproto.art.yourdomain.tld are unrelated DNS records as far as any of these protocols are concerned. The only shared constraint is DNS/web server management on your end: more subdomains means more records (and, for the file-based methods, more places actually serving a response) to keep current.

Further reading