Fix Pillow Errors on Django Image Uploads in Production
Fix Pillow errors when uploading images to Django in production: Cannot use ImageField because Pillow is not installed, decoder jpeg not available, cannot identify image file, DecompressionBombError, and HEIC uploads.
Problem statement
Image uploads work on your laptop and fail on the server with one of these:
ERRORS: shop.Product.photo: (fields.E210) Cannot use ImageField because Pillow is not installed.
OSError: decoder jpeg not available
PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7f...>
PIL.Image.DecompressionBombError: Image size (220000000 pixels) exceeds limit of 178956970 pixels
ImageField uses Pillow to verify that an upload is a real image and to read its dimensions. Production problems come from Pillow being missing from the deployed environment, being built without image codecs, receiving formats it cannot read (HEIC from iPhones, truncated files), or protecting the server from giant images.
Pillow is not installed: addPillowto production requirements and install it in the venv or image Django runs from.decoder jpeg not available: Pillow was compiled without libjpeg; install a binary wheel (pip install --only-binary=:all: Pillow) or thelibjpegdev package before building.cannot identify image file: the upload is not a supported image (HEIC, SVG, truncated); return a clear form error or add a HEIC plugin.DecompressionBombError: reject huge images, or raiseImage.MAX_IMAGE_PIXELSdeliberately.- Check
DATA_UPLOAD_MAX_MEMORY_SIZE,FILE_UPLOAD_MAX_MEMORY_SIZE, and Nginxclient_max_body_sizefor size-related failures.
Step-by-step solution
1. Confirm Pillow is in the running environment
/srv/app/.venv/bin/python -c "import PIL; print(PIL.__version__)"
docker compose exec web python -c "import PIL; print(PIL.__version__)"
If this fails, Pillow is in a dev requirements file only, or the server runs a different venv than you installed into. Add it to the requirements that production installs, rebuild the image, and restart.
2. Check which codecs Pillow has
python -m PIL.report
Look for lines such as --- JPEG support ok and --- WEBP support ok. A source build on a machine without libjpeg, zlib, or libwebp headers produces a Pillow that installs fine but cannot read those formats.
Prefer the official binary wheels, which bundle the codecs:
pip install --only-binary=:all: Pillow
Pillow publishes wheels for Linux (glibc and musl), macOS, and Windows on common architectures. If you must build from source (unusual platforms), install the dev packages first, for example libjpeg62-turbo-dev zlib1g-dev libwebp-dev libtiff-dev on Debian.
3. Handle formats Pillow cannot read
iPhones upload HEIC by default when the browser does not convert it. Pillow does not read HEIC natively. Either add the plugin:
pip install pillow-heif
# apps.py ready()
from pillow_heif import register_heif_opener
register_heif_opener()
or accept only formats you support and say so in the form:
from django.core.validators import FileExtensionValidator
photo = models.ImageField(upload_to="products/", validators=[FileExtensionValidator(["jpg", "jpeg", "png", "webp"])])
SVG is XML, not a raster image; ImageField rejects it by design. Use a FileField with sanitisation if you need SVG uploads, because SVG can contain scripts.
4. Protect against decompression bombs
A small file can decode to billions of pixels and exhaust memory. Pillow warns above about 89 million pixels and raises above about 179 million. Keep the protection, and set your own lower limit in validation:
from django.core.exceptions import ValidationError
from PIL import Image
def validate_image_size(f):
img = Image.open(f)
if img.width * img.height > 40_000_000:
raise ValidationError("Image is too large; maximum is 40 megapixels.")
f.seek(0)
Raising Image.MAX_IMAGE_PIXELS globally should be a deliberate choice for trusted sources only.
5. Check upload size limits
Large photos also hit size limits before Pillow sees them: Nginx returns 413 above client_max_body_size (1 MB by default), and Django streams files larger than FILE_UPLOAD_MAX_MEMORY_SIZE to a temporary directory that must be writable. See Fix Nginx 413 Request Entity Too Large.
Verification check: upload a JPEG, a PNG, and a WebP through the production form, and try an unsupported file to confirm the user sees a clear validation message rather than a 500.
Explanation
Pillow is a thin Python layer over C libraries for each image format. ImageField.to_python opens the upload with Pillow and calls verify(); anything Pillow cannot decode becomes a validation error, and missing codecs become exceptions. Because Pillow's capabilities depend on how it was built, the same code can behave differently on a laptop with a wheel and a server that compiled Pillow from source. Binary wheels make the environment predictable.
Edge cases / notes
- Thumbnails generated with
sorl-thumbnailoreasy-thumbnailsalso depend on these codecs; failures appear later, at render time. - EXIF orientation. Phone photos may appear rotated; apply
ImageOps.exif_transposewhen processing. - Memory. Processing images in web requests can spike worker memory. Move resizing to Celery for large files.
- Alpine images need musl wheels; mixing glibc builds causes import errors.
Next steps
- Harden uploads with the Django File Upload Security Checklist.
- Store originals off the server with Store Django Media Files on S3.
FAQ
Why does ImageField need Pillow?
Django uses Pillow to verify that uploads are valid images and to read their dimensions.
How do I fix decoder jpeg not available?
Reinstall Pillow from the official binary wheel, or install libjpeg development headers before building from source.
Can Django accept iPhone HEIC photos?
Yes, with the pillow-heif plugin registered at startup, or by converting on the client.
Is it safe to disable the decompression bomb check?
Only for trusted input. For user uploads, keep it and set a stricter limit.