Uploading a theme zip to Ghost should take ten seconds. When it does not work, the error messages are often vague and the upload dialog gives little feedback. This guide covers every Ghost theme zip upload issue you might encounter, explains the underlying cause, and gives you the fix.

The Ghost Theme Upload Process

Understanding what happens during a Ghost theme zip upload helps you diagnose problems faster. Here is what Ghost does when you select a zip file in Ghost Admin → Design → Change theme → Upload a theme:

  1. Your browser sends a multipart/form-data POST request to the Ghost Admin API
  2. Your web server (Nginx, Apache, or Ghost(Pro) infrastructure) receives the request
  3. Ghost validates the file is a zip and checks size limits
  4. The zip is extracted to a temporary directory
  5. Ghost validates the theme structure and package.json
  6. GScan runs validation checks on the theme templates
  7. On success: theme is copied to content/themes/ and becomes available for activation

A failure at any stage produces a zip upload issue. Here is how to identify and fix each one.

Issue 1: "File Is Not a Valid Zip"

Symptom: Ghost Admin immediately rejects the file with a message about invalid file format.

Causes and fixes:

Wrong file type. Ghost only accepts .zip files. If you downloaded a .tar.gz or .rar archive, convert it:

# Convert tar.gz to zip
tar xzf theme.tar.gz
cd theme-folder
zip -r ../theme.zip .

File extension mismatch. Sometimes a file saved as .zip is actually in another format. Check the real type:

file your-theme.zip

If it returns something other than Zip archive data, the file is not a valid zip. Re-download from the original source.

macOS .DS_Store interference. On Mac, zipping via Finder sometimes produces a zip that contains hidden Mac-specific metadata files. Ghost handles these, but some zips from Finder are malformed. Use the terminal instead:

cd theme-folder
zip -r ../theme.zip . -x "*.DS_Store" -x "__MACOSX"

Issue 2: Upload Stalls or Never Completes

Symptom: The upload progress bar stops mid-way, spins indefinitely, or the browser shows a network error.

Causes and fixes:

File too large for your server. If you self-host Ghost, your web server likely has a default upload size limit of 1MB–10MB. Theme zips can exceed this.

For Nginx: add or update in your server block:

client_max_body_size 50M;

Then reload: sudo nginx -s reload

For Apache: add to .htaccess:

LimitRequestBody 52428800

Browser timeout. On slow connections or a slow server, the upload takes too long and the browser gives up. Use a faster connection or upload via SSH and place the theme directly in content/themes/:

# Copy theme to server directly (skip Ghost Admin upload)
scp your-theme.zip user@yourserver:/var/www/ghost/content/themes/
ssh user@yourserver "cd /var/www/ghost/content/themes && unzip your-theme.zip -d theme-name"
ghost restart

Ghost process not running. If the Ghost process is down, the Admin API is unavailable and uploads stall. Check:

ghost status

If stopped: ghost start

Issue 3: Upload Completes But Theme Does Not Appear in the List

Symptom: The upload progress finishes with no error, but the theme does not show up under Ghost Admin → Design → Change theme.

Cause: Ghost received the zip but could not add the theme to its internal registry due to a structural problem.

Fix: check for double-nested folder structure:

Unzip locally and confirm package.json is at the root level, not inside a subfolder:

unzip -l your-theme.zip | head -20

If package.json appears as theme-name/package.json rather than package.json, the zip has double nesting. Recreate it:

cd your-theme-folder
zip -r ../your-theme.zip .

Fix: check package.json has a valid name field:

The theme name in package.json must be unique and use only lowercase letters, numbers, and hyphens. If another theme already uses the same name, Ghost may silently overwrite without showing the updated version.

Issue 4: "Theme Upload Failed: Validation Error"

Symptom: Ghost Admin shows a validation error after the upload completes.

This is a GScan validation failure. Run the theme through GScan first to see exactly what is wrong:

  1. Go to gscan.ghost.org
  2. Upload the zip
  3. Address all Error items (red)

Common validation errors and fixes:

Missing required template files:

Error: Missing files: ['post.hbs', 'index.hbs']

Create the missing template files. index.hbs renders the homepage. post.hbs renders individual posts. Both are required.

Invalid helper usage:

Error: 'has' is not a supported Ghost helper

Remove or replace the unsupported helper. Check the Ghost Handlebars theme documentation for the current supported helper list.

Missing {{asset}} helper for CSS/JS files: Theme assets must be loaded using {{asset "css/main.css"}} not hardcoded paths like /assets/css/main.css. Fix all asset references in your templates.

Issue 5: Ghost(Pro): "File Exceeds Size Limit"

Ghost(Pro) has a strict 5MB zip size limit. This is not configurable.

Reduce theme zip size:

Remove node_modules. This should never be in the zip:

# Before zipping, confirm node_modules is not included
zip -r theme.zip . --exclude="node_modules/*"

Build and use the distribution zip. Most commercial Ghost themes include a build process. Look for:

npm install
npm run zip

The output zip in dist/ will be minified and properly sized.

Compress included images. If the theme bundles preview screenshots or demo images:

# Using ImageMagick to batch compress
find assets/ -name "*.jpg" -exec mogrify -quality 75 {} \;
find assets/ -name "*.png" -exec optipng -o5 {} \;

Issue 6: CORS or Authentication Error During Upload

Symptom: Browser console shows a CORS error or 401/403 response during the upload.

Causes:

  • Your Ghost Admin session expired: log out and log back in, then retry
  • Ghost is served from a different domain than Ghost Admin: check your url and adminUrl settings in config.production.json
  • A reverse proxy is stripping authentication headers

Fix for session expiry: Simply log out of Ghost Admin, log back in, and try the upload again immediately.

Fix for proxy header stripping: Ensure your Nginx config passes headers correctly:

location / {
    proxy_pass http://localhost:2368;
    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;
}

Ghost Theme Zip Upload: Checklist Before You Upload

Run through these checks before every theme upload to avoid common issues:

  • File is a valid .zip archive (not .tar.gz, .rar, or .7z)
  • package.json is at the root of the zip, not inside a subfolder
  • package.json is valid JSON with a lowercase name field
  • engines.ghost matches your Ghost version
  • Zip is under 5MB (Ghost Pro) or within server body size limit
  • node_modules is not included in the zip
  • GScan shows no critical errors
  • You are currently logged into Ghost Admin with an active session

Resolving Ghost theme zip upload issues almost always comes down to file structure, size limits, or validation errors. This checklist catches 95% of them before you even attempt the upload.