All notes

Use your own domain as a Mastodon handle without running a server

MastodonWebFingerSelf-hosting

A static WebFinger file and two response headers let people find @rubricdaily@mastodon.social as @rubric@rubric.hrih.si, with no Mastodon server. That’s Rubric’s account, named rubricdaily because @rubric was taken on mastodon.social, and I wanted people to find it on the domain the site already has.

How Mastodon finds an account

When someone searches for @rubric@rubric.hrih.si, their server asks the domain:

GET https://rubric.hrih.si/.well-known/webfinger?resource=acct:rubric@rubric.hrih.si

The answer is a small JSON document called a JRD. Its self link points at the account’s ActivityPub actor, and its subject names the account. When the subject isn’t the handle that was asked for, Mastodon sends a second WebFinger request to the server named in the subject, and that answer has to point at the same actor. That second request is what lets a domain you own vouch for an account that lives somewhere else. Mastodon’s WebFinger docs describe both steps.

The file

The file is a copy of what mastodon.social already says about the account. Fetch it:

curl -s "https://mastodon.social/.well-known/webfinger?resource=acct:rubricdaily@mastodon.social"

Save the answer unchanged as .well-known/webfinger in the site’s public folder. Rubric’s is 505 bytes on one line, spread out here:

{
  "subject": "acct:rubricdaily@mastodon.social",
  "aliases": [
    "https://mastodon.social/@rubricdaily",
    "https://mastodon.social/ap/users/117518336990487890"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://mastodon.social/@rubricdaily"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://mastodon.social/ap/users/117518336990487890"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://mastodon.social/authorize_interaction?uri={uri}"
    }
  ]
}

Nothing in it gets edited: the subject stays the real account, and the actor stays on mastodon.social.

The two headers

The file has no extension, so the server won’t send the right type on its own. It needs Content-Type: application/jrd+json, and Access-Control-Allow-Origin: * so that web clients on other origins can read it. In Caddy, inside the site block:

@webfinger path /.well-known/webfinger
header @webfinger Content-Type "application/jrd+json"
header @webfinger Access-Control-Allow-Origin "*"
file_server

In nginx, Rubric sets the CORS header at the server level from a map on the path, and gives the file its type in a location of its own:

map $uri $cors_origin {
    default                  "";
    /.well-known/webfinger   "*";
}

server {
    add_header Access-Control-Allow-Origin $cors_origin always;

    location = /.well-known/webfinger {
        types { }
        default_type application/jrd+json;
        try_files $uri =404;
    }
}

I left the location without an add_header of its own on purpose: nginx drops the server-level add_header lines in any location that sets one, and the CORS header would go with them.

Check both headers from outside:

curl -sI "https://rubric.hrih.si/.well-known/webfinger?resource=acct:rubric@rubric.hrih.si"

It should answer 200 with content-type: application/jrd+json and access-control-allow-origin: *.

What a static file can’t do

A static server ignores the query string, so every name at the domain gets the same answer: acct:nobody@rubric.hrih.si resolves to Rubric’s account just as acct:rubric@rubric.hrih.si does. That makes it one account per domain: if you want a second one there, you need a server that reads resource. The file also answers a request with no resource at all, where the WebFinger spec, RFC 7033, asks for a 400. Mastodon always sends one, so it hasn’t mattered.

It’s an alias, not a server. The account, its posts and its followers stay on mastodon.social, and the domain is one more way to find them.

To link the two the other way, Rubric’s footer carries the profile link with rel="me":

<a href="https://mastodon.social/@rubricdaily" rel="me">Mastodon</a>

List the site in one of the profile’s fields, and Mastodon marks that field verified once it finds the rel="me" link back.