Ghost upload cover shows Maximum call stack size exceeded

I am using cloudflare R2 as storage for Ghost CMS, and when I am uploading the cover it shows “ghost upload cover shows Maximum call stack size exceeded”

This error usually indicates an infinite loop or recursion issue within the Ghost Node.js process. When using Cloudflare R2 with Ghost, this is almost always caused by one of two things:

  1. Image Optimization Loop: Ghost’s image processing library (sharp) failing to resize the image and retrying indefinitely.
  2. S3 SDK Retry Loop: The storage adapter is trying to set an S3 permission (ACL) that Cloudflare R2 does not support, causing the AWS SDK to fail and retry until the stack explodes.

Here are the steps to fix this, ordered from the most likely solution to the least.

Solution 1: Disable Image Optimization (The most common fix)

Ghost attempts to resize images (creating thumbnails, mobile versions, etc.) before uploading them to R2. If there is a mismatch between the image format and the R2 upload stream, this process can crash.

  1. Open your config.production.json file.
  2. Add or modify the imageOptimization block to disable resizing.
{
  "url": "https://your-blog.com",
  "server": { ... },
  "database": { ... },
  "storage": { ... },
  "imageOptimization": {
    "resize": false
  },
  ...
}
  1. Restart Ghost: ghost restart
  2. Test: Try uploading the cover image again.
    • Why this works: It forces Ghost to upload the original file directly to R2 without trying to process it locally first.

Solution 2: Fix R2 Configuration (ACL Issues)

Cloudflare R2 is S3-compatible, but it does not support Access Control Lists (ACLs) like public-read in the same way AWS S3 does.

If your storage adapter is configured to set the file permission to “public-read” during upload, R2 may reject the header, causing the adapter to retry the upload infinitely.

  1. Check your storage block in config.production.json.
  2. Ensure you are using the correct endpoint and force path style.

Here is a standard working configuration for R2 using the ghost-storage-adapter-s3:

"storage": {
  "active": "s3",
  "s3": {
    "accessKeyId": "YOUR_CLOUDFLARE_ACCESS_KEY_ID",
    "secretAccessKey": "YOUR_CLOUDFLARE_SECRET_ACCESS_KEY",
    "region": "auto",
    "bucket": "your-bucket-name",
    "endpoint": "https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com",
    "forcePathStyle": true, 
    "signatureVersion": "v4",
    "acl": null
  }
}

Key settings to check:

  • acl: null: This prevents the adapter from trying to set “public-read”. R2 manages public access via Custom Domains or Bucket Policies, not per-object ACLs via the API.
  • forcePathStyle: true: R2 requires this because it doesn’t support the bucket.endpoint DNS style used by AWS.

Solution 3: Check the Image Dimensions/Type

If the “Maximum call stack” error persists even after disabling optimization, the specific image might be corrupt or too large in terms of dimensions (pixels), causing the memory buffer to overflow during the read process.

  1. Convert the image to a standard JPG (if it is a heavy PNG or WebP).
  2. Resize the image locally on your computer to a max width of 2000px.
  3. Attempt to upload this smaller version.

Solution 4: Update the Storage Adapter

If you are using an older version of ghost-storage-adapter-s3 (or ghost-storage-base), it may rely on an older AWS SDK that creates conflicts with R2.

  1. Go to your Ghost installation directory (usually /var/www/ghost).
  2. Update the adapter:
    npm install ghost-storage-adapter-s3@latest
    
  3. Restart Ghost: ghost restart

Summary Checklist for R2

To ensure R2 works generally:

  1. Make sure your R2 bucket allows public access (usually by connecting a Custom Domain in the Cloudflare R2 dashboard).
  2. In your Ghost config, your assetHash URL (if you use a custom domain for images) should look like this:
    "assetHost": "https://files.yourdomain.com"
    
    (Where files.yourdomain.com is the domain you connected to the R2 bucket in Cloudflare settings).