All Bluesky content is public

Created by Bing AI

Needing an invite to join, the apps, etc. all gives a certain sense of privacy over on Blueskye. But that’s just show. The API that powers the app is publicly available, no authentication needed. Every post made on Bluesky can be queried publicly by everyone, even without having an invite.

Mario Zechner has demonstrated this well with his low-effort (but amazing!) tool Skyview (source-code available on Github).

It’s a pure client-side web application that requires the link to a Bluesky posting as input and then renders the entire discussion thread around it. Pure client-side, no server, no authentication. Amazing!

Screenshot of Skyview with one of my own postings (in German), reminding about the lack of privacy of postings on Bluesky.

That’s not a problem in itself, but just keep it in mind.

Bluesky with own domain-handle and .well-known/atproto-did

TLDR: Beware that there must be no newline at the end of the .well-known/atproto-did file and that the content type needs to be text/plain. echo -n to the rescue instead of vim.


I recently received an invite to Bluesky and so far I’ve enjoyed the experience. Early-day Twitter feeling. Can recommend to check it out if you get an invite.

One very intriguing thing is that Bluesky allows for your own domain to be your handle. So I decided to go with @martin.dont-panic.cc.

The process is described in a blog post by Bluesky. There are two main options to verify your domain ownership, DNS TXT entries or an HTTPS request to https://martin.dont-panic.cc/.well-known/atproto-did (in my case). Since everyone is doing DNS, I wanted to try out HTTPS/.well-known. (Of course, there needs to be a martin.dont-panic.cc DNS entry to get to the web-server, but no special TXT header for the verification.)

I wanted to serve the file as a static file in the filesystem via nginx. So I set up the following static nginx configuration:

server {
        listen 443 ssl;
        server_name martin.dont-panic.cc;

        root /var/www/cc/dont-panic/martin/;
        index index.html;
        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
        location = /.well-known/atproto-did {
                default_type text/plain;
        }
        # ... lots of SSL stuff omitted ...
}

So basically this tells nginx to try to serve any existing file or fall-back to directory or 404. It forces text/plain for the /.well-known/atproto-did file, since otherwise it is serves as application/octet-stream which violates the requirements.

Then I used vim to simply create the file and validated that the content of the file was accessible correctly via curl.

Looked good, so hit this verify button. And it failed. After a few retries, i decided that maybe it’s because of the final newline that end of the file?

New approach (note the “-n“!):

echo -n "did:plc:njnt2ukwkoljfxnsqsbs5mdm" > /var/www/cc/dont-panic/martin/.well-known/atproto-did

One click on verify later, Bluesky accepted the handle as verified and I could switch over from my previous user name.

So, looking forward to hearing from you either in the comments here or via Bluesky. Follow me! 😉