404 error on Activitypub (/.ghost/activitypub/v1/site)

Finally I had installed Ghost 6 with Activitypub local server on Docker without using Caddy and/or Traeffik.

I can see the Network UI and configure my profile.

Everything seems to be fine but I see 404 errors on specific pages (/activitypub/v1/site /activitypub/v1/recommendations /activitypub/v1/topics).

curl -i “``https://mydomain.tld.ghost/activitypub/v1/site``” shows {“error”:“Forbidden”,“code”:“ROLE_MISSING”}

docker activitypub container shows ERR activitypub: User role ‘Anonymous’ is not allowed to access this resource

I wonder what could be wrong? That’s my files:

services:

  ghost:
    image: ghost:${GHOST_VERSION:-6-alpine}
    restart: always
    ports:
      - "2368:2368"
    env_file:
      - .env
    environment:
      NODE_ENV: production
      url: https://${DOMAIN:?DOMAIN environment variable is required}
      admin__url: ${ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}}
      ap__enabled: "true"
      ap__domain: "mydomain.tld"
      ap__actor: "https://mydomain.tld/activitypub/actor"
      ap__storage_adapter: "filesystem"
      ap__storage_filesystem__path: "/var/lib/ghost/activitypub"
      database__client: mysql
      database__connection__host: db
      database__connection__user: ${DATABASE_USER:-ghost}
      database__connection__password: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
      database__connection__database: ghost
    volumes:
      - ${UPLOAD_LOCATION:-./data/ghost}:/var/lib/ghost/content
      - ap_data:/var/lib/ghost/activitypub
    depends_on:
      db:
        condition: service_healthy
      activitypub:
        condition: service_started
        required: false
    networks:
      - ghost_network
      
  db:
    image: mysql:8.0.42@sha256:4445b2668d41143cb50e471ee207f8822006249b6859b24f7e12479684def5d9
    restart: always
    expose:
      - "3306"
    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:
      - ${MYSQL_DATA_LOCATION:-./data/mysql}:/var/lib/mysql
      - ./mysql-init:/docker-entrypoint-initdb.d
    healthcheck:
      test: mysqladmin ping -p$$MYSQL_ROOT_PASSWORD -h 127.0.0.1
      interval: 1s
      start_period: 30s
      start_interval: 10s
      retries: 120
    networks:
      - ghost_network

  activitypub:
    image: ghcr.io/tryghost/activitypub:1.1.0@sha256:39c212fe23603b182d68e67d555c6b9b04b1e57459dfc0bef26d6e4980eb04d1
    restart: always
    ports:
      - "8080:8080"
    volumes:
      - ${UPLOAD_LOCATION:-./data/ghost}:/opt/activitypub/content
    environment:
      # See https://github.com/TryGhost/ActivityPub/blob/main/docs/env-vars.md
      NODE_ENV: production
      ACTIVITYPUB_COLLECTION_PAGE_SIZE: 20
      MYSQL_HOST: db
      MYSQL_USER: ${DATABASE_USER:-ghost}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
      MYSQL_DATABASE: activitypub
      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:
      - ghost_network

#  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(db:3306)/activitypub
#    networks:
#      - ghost_network
#    depends_on:
#      db:
#        condition: service_healthy
#    profiles: [activitypub]
#    restart: no

volumes:
  ap_data:

networks:
  ghost_network:
server {
    listen 443 ssl http2;
    server_name mydomain.tld;
    ssl_certificate /etc/letsencrypt/live/mydomain.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mydomain.tld/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

client_max_body_size 300m;

    location ^~ /.ghost/activitypub/ {
    proxy_pass              http://127.0.0.1:8080;
    proxy_set_header        Host              $host;
    proxy_set_header        Authorization     $http_authorization;
    proxy_set_header        X-Real-IP         $remote_addr;
    proxy_set_header        X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;

        # Websocket support (often needed for real-time status)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

    proxy_no_cache          1;
    proxy_cache_bypass      1;
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    }

    location ~ ^/.well-known/(webfinger|nodeinfo) {
        proxy_set_header Authorization $http_authorization;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
#        proxy_ssl_server_name on;
        proxy_pass http://127.0.0.1:8080;
#        add_header X-Content-Type-Options nosniff;
}

    # Enable CORS for ActivityPub inbox and outbox if desired
    location ~* ^/activitypub/(inbox|outbox|followers) {
        add_header Access-Control-Allow-Origin *;
        proxy_set_header   Host $host;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:8080;
    }

location / {

        proxy_pass http://127.0.0.1:2368;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;

        proxy_redirect off;
        proxy_buffering off;
        proxy_read_timeout 300s;

    # WebSocket support for real-time admin (test ghost_backend)
    location /ghost/socket {
        proxy_pass               http://127.0.0.1:2368;
        proxy_http_version       1.1;
        proxy_set_header         Upgrade $http_upgrade;
        proxy_set_header         Connection "upgrade";
        proxy_set_header         Host $host;
    }

}

    proxy_connect_timeout 90s;
    proxy_send_timeout 90s;
    proxy_read_timeout 90s;

add_header Access-Control-Allow-Origin mydomain.tld;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'";
add_header Content-Security-Policy "img-src * https: blob: data:";
#add_header Content-Security-Policy "default-src https: data:";
add_header Content-Security-Policy "object-src 'none'";
add_header Content-Security-Policy "script-src MYSCRIPTS 'unsafe-inline'";
add_header Content-Security-Policy "style-src 'unsafe-inline' https:";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN";
proxy_hide_header X-Frame-Options;

}