iPhone HEIC Uploads Failing in WordPress? Fixing iOS 18 Photos in PHP Imagick

Managing WP featured image: iPhone HEIC Uploads Failing in WordPress? Fixing iOS 18 Photos in PHP Imagick, with a cartoon phone being repaired next to a server

This page has had its content updated on October 28, 2026 EDT by Jordan

iOS 18 iPhone photos fail to decode on Ubuntu 22.04/24.04 and Debian 12 because the shipped libheif is too old. Here’s why upgrading PHP doesn’t help, the GD trap that breaks the obvious fix, and an open-source script that fixes PHP Imagick without touching system packages.

Content Error or Suggest an Edit

Notice a grammatical error or technical inaccuracy? Let us know; we will give you credit!

Introduction

If your WordPress sites accept photo uploads from registered users — membership communities, forms, WooCommerce reviews, anything with a media picker — there’s a good chance some of those uploads are quietly failing. Photos taken on iOS 18 and newer use a HEIC layout that the libheif shipped with Ubuntu 22.04, Ubuntu 24.04 and Debian 12 can’t read. PHP Imagick hands the file to ImageMagick, ImageMagick hands it to libheif, and libheif gives up before a single pixel is decoded.

We hit this on two separate client servers in the same week, both GridPane boxes on Ubuntu 22.04. The fix we ended up with is now an open-source script: managingwp/heic-ios18-fix. It builds a current libheif and ImageMagick 7 into /opt, compiles a matching Imagick for each PHP version, and leaves your system packages alone.

The one thing to do: run ./heic-fix.sh check on your servers. It’s read-only and tells you in seconds whether you’re affected.

Looking for Support?

Need help implementing this articles content? We can help!

The Problem

iOS 18 photos carry extra HEIF metadata — a tmap tone-map/gain-map item and additional auxiliary images. Older libheif releases don’t understand them and fail at parse time. Support landed in libheif 1.18.2 (libheif #1190), with tmap handling formalized later (PR #1503).

DistributionlibheifiOS 18 HEIC
Ubuntu 22.041.12.0Fails
Ubuntu 24.041.17.6Fails
Debian 121.15.1Fails
Ubuntu 26.041.21.2Works — needs the HEVC decoder plugin (see below)
Any build with libheif ≥ 1.18.2≥ 1.18.2Works, with an HEVC decoder available

The chain on a typical stack looks like this: PHP Imagick → ImageMagick 6 → system libheif. On the servers we looked at, that was ImageMagick 6.9.11-60 on top of libheif 1.12.0.

Why upgrading PHP doesn’t help

Every PHP version on the server — 7.4 through 8.5 — shares the same system ImageMagick and libheif. Switching a site from PHP 8.1 to 8.3 changes nothing here. The PHP version was never the problem.

Symptoms

A failed iPhone upload usually shows up as one of these:

  • PHP Imagick: ImagickException: Failed to read the file
  • heif-convert 1.12: Invalid input: Unspecified: Metadata not correctly assigned to image
  • libheif 1.17.x: Too many auxiliary image references
  • ImageMagick 7: Metadata not correctly assigned to image (2.0) … error/heic.c/IsHEIFSuccess/…
  • After a half-finished fix, with PHP GD installed: undefined symbol: heif_has_compatible_filetype

Or nothing obvious at all — the old library gets used silently and the upload just doesn’t produce an image.

One thing worth knowing about iOS: the photo picker often converts HEIC to JPEG on the way out, which is why not every iPhone upload fails. Picking a file through Choose File (the Files app) sends the original HEIC. Every iOS browser runs on WebKit, so Chrome or Firefox on an iPhone behaves the same as Safari.

The Fix: heic-ios18-fix

heic-ios18-fix is a single bash script (MIT licensed) that:

  1. Builds a private libde265, libheif (pinned to v1.23.7, verified against a real iOS 18 sample) and ImageMagick 7 into /opt/heic-ios18-fix.
  2. Builds a matching Imagick extension for each PHP version you choose.
  3. Runs a realistic pre-flight — with GD loaded exactly as it is in production — before enabling anything.
  4. Swaps the stock imagick module for imagick-heic and reloads PHP-FPM, with a one-command rollback.

Nothing under /usr is replaced. The system ImageMagick 6 and libheif stay exactly as the distro shipped them, so other software keeps working and distro updates don’t fight you.

Quick start

On a test server first, as root:

git clone https://github.com/managingwp/heic-ios18-fix.git
cd heic-ios18-fix

# 1. See what's wrong. Read-only, safe on production.
./heic-fix.sh check

# 2. Build the private libraries (installs build packages and compiles).
sudo ./heic-fix.sh build

# 3. Build Imagick for one PHP version, run the pre-flight, enable it.
sudo ./heic-fix.sh php --php 8.3

# 4. Prove CLI and FPM are both using ImageMagick 7.
sudo ./heic-fix.sh verify --php 8.3

A clean build takes roughly 5–15 minutes and needs 1–2 GB free. Add --sample /path/to/photo.HEIC to test with a real failing photo from your users.

To undo it for a PHP version:

sudo ./heic-fix.sh rollback --php 8.3

Rollback re-enables stock imagick and reloads FPM immediately. The build stays in /opt, so enable --php 8.3 brings it back without recompiling.

Don’t let apt restart your stack

While investigating, a plain apt install of a libheif tools package triggered needrestart on a production GridPane server — nginx, MariaDB and every php-fpm pool restarted. The script runs every apt call with NEEDRESTART_MODE=l NEEDRESTART_SUSPEND=1, and you should do the same any time you’re installing packages by hand on a live box.

The GD Trap (Why a Normal /opt Build Breaks)

This is the part that makes the obvious fix — “just build a newer libheif into /opt and point Imagick at it” — fall over.

PHP’s GD extension (gd.so) is linked against the system libheif.so.1, and on Debian/Ubuntu it loads before imagick.so. By the time your new Imagick asks for libheif.so.1, the dynamic linker already has the old one mapped and reuses it — regardless of rpath or LD_LIBRARY_PATH. You get either undefined symbol: heif_... or, worse, the old library silently used and iOS 18 files still failing. It’s reproducible on Ubuntu 24.04 with PHP 8.3 and php8.3-gd.

The script sidesteps this by giving its private libraries unique SONAMEs (libheif-heicfix.so.1, libde265-heicfix.so.0) with patchelf before ImageMagick is built. The system libheif and the private one are now different libraries and can be loaded side by side. verify proves it by resolving both private SONAMEs inside the prefix, and the test suite even re-runs with the system libheif.so.1 in LD_PRELOAD.

This is also why the pre-flight deliberately does not use php -n — that skips GD and hides the bug.

Ubuntu 26.04: No Build Needed

Ubuntu 26.04 ships libheif 1.21.2 and ImageMagick 7.1.2, so the iOS 18 bug is fixed natively. Don’t build this prefix on a stock 26.04 server.

There’s one catch: libheif 1.21 moves HEVC decoding into a plugin that libheif1 only suggests, so a stock install can still fail with No decoding plugin installed for this compression format. That’s a different problem, and it’s one package:

sudo apt install libheif-plugin-libde265
sudo systemctl reload php8.5-fpm   # use the PHP version your sites run

check spots this case and prints the plugin hint instead of recommending a rebuild. Longer term, moving affected sites to a 26.04 server is the clean fix; the script is for when that isn’t happening this week.

Platform Notes

GridPane

GridPane is detected automatically and uses the standard /etc/php/X.Y layout, so it follows the generic path. GridPane stack or security updates may reinstall packages or re-enable the stock imagick module. They don’t touch /opt/heic-ios18-fix, so after an update run check and verify, and enable --php X.Y if the stock module came back.

On some GridPane/Sury servers, installing php8.3-dev fails over libpcre2-dev 10.39/10.42 mismatches. Install the matching Sury PCRE2 development set rather than downgrading — the troubleshooting doc has the exact commands.

We haven’t confirmed what triggered the failures to start being reported in September 2026 when iOS 18 shipped in 2024. A GridPane update changing the imagick/HEIC setup is a plausible theory, but it’s unconfirmed.

RunCloud

RunCloud uses non-standard PHP paths (/RunCloud/Packages/phpXXrc, /etc/phpXXrc/conf.d). The library build works, but the PHP wiring isn’t implemented yet — the script refuses to guess and tells you what’s missing. If you run RunCloud and want to help test, the paths are marked TODO(runcloud) in the script.

Generic Debian/Ubuntu

Any Debian or Ubuntu box with PHP under /etc/php/X.Y (Ondřej’s PPA or distro packages) works out of the box.

Keep It Fixed: Monitoring

Re-run check after platform stack updates, security updates touching ImageMagick/libheif/PHP/GD, PHP upgrades (each new PHP version needs its own Imagick build), and snapshot restores.

check -q prints one line per PHP version and uses exit codes for health, so it drops straight into cron or monit:

/opt/heic-ios18-fix/heic-fix.sh check -q --php 8.3 || echo "HEIC check failed"

# Example output
php8.3 status=ok imagick=3.8.1 imagemagick=7 heic=yes libheif=1.23.7 decode=ok

Examples are in monitoring/README.md.

Alternatives

If a private ImageMagick build isn’t a good fit for your environment:

  1. Convert at upload with a standalone heif-dec (from this build or any libheif ≥ 1.18.2) and hand ImageMagick a JPEG. Avoids rebuilding Imagick.
  2. Decode in the browser with a client-side WASM HEIC decoder. No server changes, but every visitor pays the download and CPU cost.
  3. Rely on iOS auto-conversion. It works through some paths, but not the Files app or many upload pickers. Not reliable enough to depend on.
  4. Upgrade the distro to one that ships libheif ≥ 1.18.2. The right long-term answer, but usually a much bigger job.

FAQ

Will this break other software that uses ImageMagick?

No. The system ImageMagick 6 and libheif are untouched; only PHP’s Imagick extension is pointed at the private build.

Do I need to do this for every PHP version?

Only the versions your sites run. Imagick is compiled per PHP ABI, so each one gets its own build via php --php X.Y.

Is it safe to run on production?

check, status, deps and verify are read-only. For the build, take a snapshot and run it on a test server first — it’s been validated in containers (Ubuntu 22.04/24.04 in CI) and on live GridPane servers, but your stack is your stack.

Does WordPress core handle HEIC itself?

WordPress relies on the server’s image editor (Imagick or GD) to read uploads. If Imagick can’t decode the file, WordPress can’t create thumbnails or convert it.

Found this useful? Managing WP is written in the gaps between client work. If it saved you some time, a coffee keeps it going.

Updates

  • Oct 28, 2026: First draft. Fix applied to two client GridPane servers on Ubuntu 22.04 / PHP 8.3.

Sources

0 Shares:

You May Also Like
How to Silence Netdata Alarms
Read More

How to Silence Netdata Alarms

Understanding Netdata Alarms Before silencing an alarm, it’s crucial to understand how Netdata alarms work. Netdata provides a…