Docker Deployment and ActivityPub

Hi,

I recently migrated from an old docker set-up to your official docker deployment.

So far everything has been fine-ish, and I absolutely applaud you guys for your work on Ghost, it’s simply amazing.

However, I have run into a few issues using your official docker deployment (caddy) when using an admin domain (dashboard.mywebsite.com).

  1. When previewing a post, I get an error saying connection refused (iframe related)

This is an easy fix as I just added a rule to Caddy that allows this for my admin domain.

  1. ActivityPub in general refuses to work (self hosted or not)

This is the log:

ghost-1  | [2025-08-14 11:15:09] ERROR Could not get webhook secret for ActivityPub FetchError: invalid json response body at https://mywebsite.com/.ghost/activitypub/v1/site reason: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
ghost-1  | [2025-08-14 11:15:09] ERROR No webhook secret found - cannot initialise

Is the problem that it’s not reaching the admin domain? Or am I mistaken in how this setup works?

I’ve read some of the suggestions on here (editing nginx config file,… not really doable since it’s Caddy).
Should I edit the caddy ActivityPub snippet to make this work? If so, I’m quite unfamiliar with Caddy, what should it look like?

# ActivityPub
# Proxy activitypub requests /.ghost/activitypub/
handle /.ghost/activitypub/* {
        reverse_proxy {$ACTIVITYPUB_TARGET}
}

handle /.well-known/webfinger {
        reverse_proxy {$ACTIVITYPUB_TARGET}
}

handle /.well-known/nodeinfo {
        reverse_proxy {$ACTIVITYPUB_TARGET}
}

I have pulled the last docker images, pulled the git repo,.. recreated the necessary containers (caddy and ghost),.. but to no avail.

Has anyone had a similar issue or resolved this themselves?

1 Like

1 - This is a security settings problem. In the security headers snippet (in caddy), remove the X-frame options setting, or set a more permissive value. (I’ve reported it, and the team has an issue open internally. Preview post and visit site do not work (Ghost 6.0 container) )

2 - If you haven’t already, can you bump Ghost to 6.0.3 (hopefully a fresh pull of the container will get you that). There was a missing redirect that was impacting specifically sites with separate admin domains, and it is fixed at 6.0.3.

Check your browser’s network console for errors also - any 401s?

I had very similar behavior (without docker) and fixed it with a redirect not-admin-site.tld/ghost/* over to actual-admin-site.tld/ghost/, but that should be fixed and working in 6.0.3.

Another possibility is that your containers aren’t talking to each other correctly

5 posts were split to a new topic: ActivityPub: {“error”:“Forbidden”,“code”:“SITE_MISSING”}

Hi, thank you for your reply.

I did update to 6.03 and double checked in the container itself with the CLI that I am indeed running that.
I do see a few CORS errors in my network tab, which are probably the problem.

Access to XMLHttpRequest at 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' from origin 'https://dashboard.mywebsite.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Will probably need to update something in one of the Caddy files

Yep. Check your securityHeaders snippet.

Aside from the X-Frame, my config is pretty much the same as the default

header {
        # Enable HSTS
        Strict-Transport-Security max-age=31536000;
        # Prevent embedding in frames
        X-Frame-Options  "allow-from {$ADMIN_DOMAIN}"
        # Enable XSS protection
        X-XSS-Protection "1; mode=block"
        # Prevent MIME sniffing
        X-Content-Type-Options nosniff
        # Referrer policy
        Referrer-Policy strict-origin-when-cross-origin
}

I’m not quite familiar with Caddy, I tried adding Access-Control-Allow-Origin "{$ADMIN_DOMAIN}" but the issue remains the same (I also recreated the containers with each change).. am I doing something wrong or overlooking something?

After a lot of messing around (turns out it was cached,…), I am now getting:

{"error":"Forbidden","code":"SITE_MISSING"}
Similar to @ppittman in his thread

Despite CORS not being an issue anymore, I do still encounter:

[2025-08-14 21:57:56] ERROR Could not get webhook secret for ActivityPub FetchError: invalid json response body at https://mywebsite.com/.ghost/activitypub/v1/site reason: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
[2025-08-14 21:57:56] ERROR No webhook secret found - cannot initialise

What’s happening on your ActivityPub container? Does it receive the requests properly?

The AP server has a concept of “sites” and should generally add a new site when it connects properly for the first time.

I have also seen cases where previous attempts failed, and a malformed site was put into the database, though this only happened in migrations from one server to another (with the webhook secrets getting lost on the way).

So if I check my AP docker logs, this is what’s inside:

21:57:54.534 INF activitypub: Message queue is disabled
21:57:54.794 INF activitypub: listening on :::8080, booted in 2885ms
21:58:15.323 INF activitypub: 'GET' 'mywebsite.com' 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' 'e4f14fe6-911d-4f31-8a5c-42eef46eda98'
21:58:15.325 INF activitypub: KnexKvStore: Get key cachedJwks,mywebsite.com
21:58:15.416 INF activitypub: No site found for 'mywebsite.com'
21:58:15.417 INF activitypub: 'GET' 'mywebsite.com' 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' 'e4f14fe6-911d-4f31-8a5c-42eef46eda98' 403 95ms
21:58:18.835 INF activitypub: 'GET' 'mywebsite.com' 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' 'f1ba31a2-3462-428e-9715-02bddb8b0482'

So it does seem to get requests
I do also seem to see the admin dashboard occur in there at times:

22:13:12.419 INF activitypub: KnexKvStore: Get key cachedJwks,dashboard.mywebsite.com
22:13:12.673 INF activitypub: KnexKvStore: Set key cachedJwks,dashboard.mywebsite.com

I’ve just restarted the containers and I do notice that there’s no signs of AP getting any requests at startup (when the webhook secret can’t be initialised):

08:13:26.032 INF activitypub: Message queue is disabled
08:13:26.271 INF activitypub: listening on :::8080, booted in 3382ms

If I then go to the dashboard and look at the Network tab I do see some requests coming through:

08:13:26.032 INF activitypub: Message queue is disabled
08:13:26.271 INF activitypub: listening on :::8080, booted in 3382ms
08:14:47.773 INF activitypub: 'GET' 'mywebsite.com' 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' 'fafa474c-7753-4d33-83f0-63f2976fa0f0'
08:14:47.774 INF activitypub: KnexKvStore: Get key cachedJwks,mywebsite.com
08:14:47.835 INF activitypub: No site found for 'mywebsite.com'
08:14:47.836 INF activitypub: 'GET' 'mywebsite.com' 'https://mywebsite.com/.ghost/activitypub/v1/notifications/unread/count' 'fafa474c-7753-4d33-83f0-63f2976fa0f0' 403 63ms

I did notice the request to AP happens at around the same time AP initialises, however when turning off Network in the dashboard, and back on, I get the same error (no secret found), implying it’s not a timing issue either.

What I see based on the logs is that AP receives the request, yeah.

But the site doesn’t exist. Toggling AP in the settings should re-initialise the webhook. Is there any sign of that?

If not, it might be worth checking the database to see if there is an entry in the sites table of the ActivityPub database.

If that’s the only site relying on your AP server, deleting and re-creating the database might also be worth a try to force a restart.

Thank you @jannis (and @Cathy_Sarisky) for your help it led me in the right direction.

I saw the AP database existed but that all the tables were empty. This led me to check the error once again and I started doing some thinking. It mentioned an HTML response instead of JSON (that’s why it failed, it couldn’t parse it) – but when I visited the URL I did get JSON. So maybe it was cached?

Since this is the 2nd time a caching related issue came up I started looking at Cloudflare.

I removed them as a proxy, and miraculously it started working! I then turned back on the proxy and it’s still working..

Mess of a roller coaster but thank you all for your help!

1 Like