ActivityPub partially working

I am trying to run ActivityPub myself next to my ghost instance.

It works kinda half way.

One thing I changed the volume to UPLOAD_LOCATION=./activitypub_data and I can not see any data in there.
The error also says something about that keys could not be found…

Here is the log output when I try to follow my mastodon account from the ghost afmin site.

activitypub-1          | 18:01:53.862 INF activitypub: 'POST' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/v1/actions/follow/@ajfriesen@techhub.social' '832f557c-5ebe-450a-a6b9-9d926e1e5752'
ghost-1                | [2025-09-18 18:01:53] INFO "GET /ghost/api/admin/identities/" 200 118ms
activitypub-1          | 18:01:53.864 INF activitypub: KnexKvStore: Get key cachedJwks,www.ajfriesen.com
activitypub-1          | 18:01:54.092 INF activitypub: Looking up actor locally ('https://www.ajfriesen.com/.ghost/activitypub/users/index')
activitypub-1          | 18:01:54.092 INF activitypub: KnexKvStore: Get key https://www.ajfriesen.com/.ghost/activitypub/users/index
activitypub-1          | 18:01:54.101 INF activitypub: Looking up actor locally ('https://techhub.social/users/ajfriesen')
activitypub-1          | 18:01:54.101 INF activitypub: KnexKvStore: Get key https://techhub.social/users/ajfriesen
activitypub-1          | 18:01:54.149 INF activitypub: KnexKvStore: Set key https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0
activitypub-1          | 18:01:54.179 WRN fedify·federation·outbox: No supported key found to create a proof for the activity 'https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0'.  The activity will be sent without a proof.  In order to create a proof, at least one Ed25519 key must be provided.
activitypub-1          | 18:01:54.181 INF activitypub: KnexKvStore: Get key _fedify,httpMessageSignaturesSpec,https://techhub.social
activitypub-1          | 18:01:54.760 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'b86d1e5a-4699-4c30-b272-c2cb48b48d2a'
activitypub-1          | 18:01:54.835 WRN fedify·federation·actor: You configured inbox listeners, but the actor does not have a endpoints.sharedInbox property.  Set the property with Context.getInboxUri().
activitypub-1          | 18:01:54.835 WRN fedify·federation·actor: You configured a key pairs dispatcher, but the actor does not have an assertionMethod property.  Set the property with Context.getActorKeyPairs(identifier).
activitypub-1          | 18:01:54.842 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'b86d1e5a-4699-4c30-b272-c2cb48b48d2a' 200 82ms
activitypub-1          | 18:01:55.204 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'df9f98dd-bd94-40e6-ae9c-454fff1256d0'
activitypub-1          | 18:01:55.261 WRN fedify·federation·actor: You configured inbox listeners, but the actor does not have a endpoints.sharedInbox property.  Set the property with Context.getInboxUri().
activitypub-1          | 18:01:55.261 WRN fedify·federation·actor: You configured a key pairs dispatcher, but the actor does not have an assertionMethod property.  Set the property with Context.getActorKeyPairs(identifier).
activitypub-1          | 18:01:55.266 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'df9f98dd-bd94-40e6-ae9c-454fff1256d0' 200 62ms
activitypub-1          | 18:01:55.669 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.well-known/webfinger?resource=acct:index@www.ajfriesen.com' '4ab013d2-a754-4d42-a938-2d4ca6526acc'
activitypub-1          | 18:01:55.688 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.well-known/webfinger?resource=acct:index@www.ajfriesen.com' '4ab013d2-a754-4d42-a938-2d4ca6526acc' 200 19ms
ghost-1                | [2025-09-18 18:01:56] INFO "GET /.well-known/webfinger?resource=acct:index@ajfriesen.com" 301 27ms
ghost-1                | [2025-09-18 18:01:56] INFO "GET /.well-known/webfinger/?resource=acct:index@ajfriesen.com" 404 24ms
ghost-1                | [2025-09-18 18:01:56] INFO "GET /.well-known/host-meta" 301 1ms
ghost-1                | [2025-09-18 18:01:57] INFO "GET /.well-known/host-meta/" 404 44ms
activitypub-1          | 18:01:57.619 ERR fedify·federation·outbox: Failed to send activity 'https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0' to 'https://techhub.social/users/ajfriesen/inbox' (401 'Unauthorized'):
activitypub-1          | '{"error":"Error parsing signature parameters"}'
activitypub-1          | 18:01:57.626 ERR activitypub: Error: Failed to send activity https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0 to https://techhub.social/users/ajfriesen/inbox (401 Unauthorized):
activitypub-1          | {"error":"Error parsing signature parameters"}
activitypub-1          |     at sendActivityInternal (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:1745:9)
activitypub-1          |     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
activitypub-1          |     at async file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:1677:4
activitypub-1          |     at async Promise.all (index 0)
activitypub-1          |     at async FederationImpl.sendActivity (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:2313:4)
activitypub-1          |     at async RequestContextImpl.sendActivityInternal (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:3073:4)
activitypub-1          |     at async file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:2992:5
activitypub-1          |     at async FollowController.handleFollow (file:///opt/activitypub/dist/app.js:5625:5)
activitypub-1          |     at async dispatch (file:///opt/activitypub/node_modules/hono/dist/compose.js:22:17)
activitypub-1          |     at async dispatch (file:///opt/activitypub/node_modules/hono/dist/compose.js:22:17)
activitypub-1          | 18:01:57.630 INF activitypub: 'POST' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/v1/actions/follow/@ajfriesen@techhub.social' '832f557c-5ebe-450a-a6b9-9d926e1e5752' 500 3767ms

Here is my compose:

services:
  ghost:
    image: ghost:${GHOST_VERSION:-6-alpine}
    restart: always
    env_file:
      - .env
    volumes:
      - ./data:/var/lib/ghost/content
    # links:
    #   - db
    networks:
      - proxy-network
    environment:
      NODE_ENV: production
      url: https://${DOMAIN:?DOMAIN environment variable is required}
      admin__url: ${ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}}
      database__client: mysql
      database__connection__host: blog-db
      database__connection__user: ${DATABASE_USER:-ghost}
      database__connection__password: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
      database__connection__database: ghost
      tinybird__tracker__endpoint: https://${DOMAIN:?DOMAIN environment variable is required}/.ghost/analytics/api/v1/page_hit
      tinybird__adminToken: ${TINYBIRD_ADMIN_TOKEN:-}
      tinybird__workspaceId: ${TINYBIRD_WORKSPACE_ID:-}
      tinybird__tracker__datasource: analytics_events
      tinybird__stats__endpoint: ${TINYBIRD_API_URL:-https://api.tinybird.co}
    depends_on:
      db:
        condition: service_healthy
      tinybird-sync:
        condition: service_completed_successfully
        required: false
      tinybird-deploy:
        condition: service_completed_successfully
        required: false
      activitypub:
        condition: service_started
        required: false
    labels:
       - "traefik.enable=true"
       - "traefik.http.routers.blog.rule=Host(`www.ajfriesen.com`) || Host(`ajfriesen.com`)"
       - "traefik.http.routers.blog.entrypoints=websecure"
       - "traefik.http.routers.blog.tls.certresolver=dnsresolver"
       - "traefik.http.routers.blog.tls=true"
       - "traefik.http.routers.blog.service=blog"
       - "traefik.http.services.blog.loadbalancer.server.port=2368"
       - "traefik.http.routers.blog-ap-main.priority=10"
       # Redirection from ajfriesen.com to www.ajfriesen.com
       - "traefik.http.routers.redirect.rule=Host(`ajfriesen.com`)"
       - "traefik.http.middlewares.redirect-to-www.redirectregex.regex=^https://ajfriesen.com/(.*)"
       - "traefik.http.middlewares.redirect-to-www.redirectregex.replacement=https://www.ajfriesen.com/$${1}"
       - "traefik.http.middlewares.redirect-to-www.redirectregex.permanent=true"
       - "traefik.http.routers.redirect.middlewares=redirect-to-www"


  db:
    container_name: blog-db
    image: mysql:8.0
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD:?DATABASE_ROOT_PASSWORD environment variable is required}
      MYSQL_USER: ${DATABASE_USER:-ghost}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
      MYSQL_DATABASE: ghost
      MYSQL_MULTIPLE_DATABASES: activitypub
    volumes:
      - ./database:/var/lib/mysql
      - ./mysql-init:/docker-entrypoint-initdb.d
    healthcheck:
      # test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      test: mysqladmin ping -p$$MYSQL_ROOT_PASSWORD -h blog-db
      # test: mysqladmin ping -h localhost
      interval: 1s
      start_period: 30s
      start_interval: 10s
      retries: 120
    networks:
      - proxy-network

  
  activitypub:
    image: ghcr.io/tryghost/activitypub:1.1.0@sha256:39c212fe23603b182d68e67d555c6b9b04b1e57459dfc0bef26d6e4980eb04d1
    restart: always
    # expose:
    #   - "8080"
    volumes:
      - ${UPLOAD_LOCATION:-./data/ghost}:/opt/activitypub/content
    environment:
      NODE_ENV: production
      ACTIVITYPUB_COLLECTION_PAGE_SIZE: 20
      MYSQL_HOST: blog-db
      MYSQL_USER: ${DATABASE_USER:-ghost}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
      MYSQL_DATABASE: activitypub
      ALLOW_PRIVATE_ADDRESS: true
      USE_MQ: false
      LOCAL_STORAGE_PATH: /opt/activitypub/content/images/activitypub
      LOCAL_STORAGE_HOSTING_URL: https://${DOMAIN}/content/images/activitypub
    depends_on:
      db:
        condition: service_healthy
      activitypub-migrate:
        condition: service_completed_successfully
    profiles: [activitypub]
    networks:
      - proxy-network
    labels:
     - "traefik.enable=true"
     - "traefik.http.services.blog-activitypub.loadbalancer.passhostheader=true"
     - "traefik.http.services.blog-activitypub.loadbalancer.server.port=8080"
     - "traefik.http.routers.blog-activitypub.entrypoints=websecure"
     - "traefik.http.routers.blog-activitypub.service=blog-activitypub"
     - "traefik.http.routers.blog-activitypub.tls=true"
     - "traefik.http.routers.blog-activitypub.tls.certresolver=dnsresolver"
     - "traefik.http.routers.blog-activitypub.rule=Host(`www.ajfriesen.com`) && (PathPrefix(`/.ghost/activitypub/`) || Path(`/.well-known/webfinger`) || Path(`/.well-known/nodeinfo`))"

 

  activitypub-migrate:
    image: ghcr.io/tryghost/activitypub-migrations:1.1.0@sha256:b3ab20f55d66eb79090130ff91b57fe93f8a4254b446c2c7fa4507535f503662
    environment:
      MYSQL_DB: mysql://${DATABASE_USER:-ghost}:${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}@tcp(blog-db:3306)/activitypub
    networks:
      - proxy-network
    depends_on:
      db:
        condition: service_healthy
    profiles: [activitypub]
    restart: no

networks:
  proxy-network:
    external: true

and part of my .env:

UPLOAD_LOCATION=./activitypub_data
ACTIVITYPUB_TARGET=activitypub:8080
DOMAIN=www.ajfriesen.com
COMPOSE_PROFILES=activitypub

Does anybody have an idea where I can check further to get this running?
Is this an ActivityPub problem or a routing problem, because I use Traefik?

I deleted the activitypub database and started fresh:

CREATE DATABASE IF NOT EXISTS activitypub;
GRANT ALL ON activitypub.* TO 'ghost'@'%';

And here the log from the start.

Interesting part in my opinion:

activitypub-1          | 21:03:12.129 WRN fedify·sig·key: No algorithm specified.  Using RSASSA-PKCS1-v1_5 by default, but it is recommended to specify the algorithm explicitly as the parameter will be required in the future.


activitypub-1          | 21:03:16.604 ERR fedify·federation·outbox: Failed to send activity 'https://www.ajfriesen.com/.ghost/activitypub/follow/3fbbd4da9f0645618cbb55c212d37113' to 'https://mastodon.social/users/ghostexplore/inbox' (401 'Unauthorized'):

activitypub-1          | '{"error":"Error parsing signature parameters"}'

activitypub-1          | 21:03:16.604 ERR activitypub: Failed to follow Ghost Explore account for 1


ghost-1                | [2025-09-18 21:03:16] ERROR Could not find webhook for post.published https://www.ajfriesen.com/.ghost/activitypub/v1/webhooks/post/published


Full log here because this ofrum does not allow more: