ExifTool "Error: File Is Empty": Causes and Fixes (2026)

How-To Guide

You ran ExifTool over an uploaded image and got this:

Error                           : File is empty

Here is the useful part: that message means one thing and nothing else. The file is exactly zero bytes. Not corrupt, not truncated, not the wrong format. Zero.

TL;DR

Error: File is empty fires at exactly 0 bytes and never at 1 byte or 20. A truncated file gets Warning: JPEG format error and exits 0, so an upload check that gates on ExifTool's exit code rejects empty files and waves half-written ones through. Fix the pipeline that produced the zero-byte file, then fix the check.

Confirm It Before You Debug It

One command, before anything else:

Check the actual size
ls -l /path/to/upload.jpg
# -rw-r--r--  1 app  app  0 Aug 23 16:04 upload.jpg
#                         ^ this is the whole diagnosis

If that number is 0, you have an upload problem, not an ExifTool problem. If it is not 0, you are looking at a different error and the rest of this page will not help.

What ExifTool Actually Reports, By File Size

These are real runs on ExifTool 12.76. The exit codes are the part worth writing down, because most upload pipelines gate on them.

InputMessageExit code
0 bytesError: File is empty1
1 bytenone, parses it as text0
20-byte truncated JPEGWarning: JPEG format error0
path does not existError: File not found1
0 bytes with -mError: File is empty1
0 bytes with -q -qsuppressed1

Reproduce it yourself in about ten seconds:

Reproduce all three cases
: > empty.jpg                        # zero bytes
printf 'x' > onebyte.jpg             # one byte
head -c 20 real.jpg > trunc.jpg      # truncated

exiftool empty.jpg;   echo "exit=$?"   # Error: File is empty, exit=1
exiftool onebyte.jpg; echo "exit=$?"   # no error,             exit=0
exiftool trunc.jpg;   echo "exit=$?"   # Warning: JPEG format error, exit=0

The truncated case is the one that should worry you. If your validation step is exiftool "$f" || reject, you reject the zero-byte upload and accept the file that was cut off partway through. Truncation is the more interesting failure: it means bytes were in flight and something interrupted them, and whatever your app does with that half-file next is now running on attacker-influenceable input.

Why a Zero-Byte File Shows Up in an Upload Pipeline

A zero-byte file is not a corrupted write. It is a file that was created and never written to. Three things produce that shape.

The stream was already consumed

This is the most common one in Node. A body-parsing middleware reads the multipart stream to build req.body, and then your handler pipes the same request to disk. The second read gets nothing, because a readable stream is consumed once. The destination file gets created by the write stream, stays at zero bytes, and ExifTool tells you so.

The tell: it happens on every upload, not intermittently. If every file is zero bytes, look at middleware ordering before you look at anything else.

The read raced the write

Your upload handler writes the file and, in the same tick, kicks off processing. If the processing step is not waiting on the write stream's finish event (or the close event, which is the one you actually want if you then read the file back), ExifTool can open the path before the first chunk lands.

The tell: it is intermittent, and it gets worse with larger files and slower disks. A race that never reproduces locally and fires constantly in production is almost always this.

The write went somewhere else

Container-mounted volumes, TMPDIR differences between local and production, a path built from a variable that was empty. The write succeeds against a path nobody reads and the reader creates a fresh empty file at the path it expected.

The tell: the file you find is zero bytes and its mtime is the time of the read, not the upload.

Serverless platforms add a fourth: on most of them only /tmp is writable, and it is not shared between invocations. Writing an upload to a relative path can fail outright, and writing to /tmp in one invocation then reading it in another finds nothing there.

Stop Gating on the Exit Code

The exit code answers "did ExifTool have any problem at all", which is not the question your validation is asking. Ask for the thing you care about instead.

Check what the file actually is, not whether ExifTool was happy
# Bad: passes on a truncated file, fails on an empty one
exiftool "$f" > /dev/null || reject "$f"

# Better: assert the file type ExifTool detected
TYPE=$(exiftool -s3 -FileType "$f" 2>/dev/null)
[ "$TYPE" = "JPEG" ] || reject "$f"

# Also catch the truncation warning, which exits 0
if exiftool -validate -warning -error "$f" 2>&1 | grep -qi 'error\|warning'; then
  reject "$f"
fi

-validate is the flag most people miss. It surfaces the warnings that would otherwise vanish behind a zero exit code, and on an empty file it reports Validate: 1 Error alongside the message.

If you are scanning a directory and just want empties out of the way, filter before the run rather than quieting ExifTool afterwards: find . -type f -size +0c -exec exiftool {} +. That keeps the exit code meaningful for the files you do care about.

The Part That Is a Security Bug

Zero-byte uploads are a symptom of ordering. If the file is empty when your processing step reads it, then your application created a record, assigned an ID, and probably returned 200 to the client before it knew whether the bytes arrived. Everything downstream of that point is validating a file the user did not necessarily send.

We see this exact shape in scans constantly: an upload endpoint that stores first and checks later, with the check wired to a truthy exit code. It rejects the obviously broken file and accepts the interesting one.

The ordering that holds is boring. Write to a quarantine path, wait for the write to finish, validate the completed file, and only then move it into the location your app serves from. Anything that reads from the serving path before validation has already lost.

Before you close this ticket

FAQ

What does ExifTool's Error: File is empty actually mean?

The file exists and is exactly zero bytes. That is the only thing that triggers it. A one-byte file does not, and a truncated JPEG does not either. If the path did not exist at all you would get a different message, Error: File not found, so the file was created and then nothing was written into it.

Why does my truncated image not trigger the error?

Because ExifTool treats truncation as a warning, not an error. A JPEG cut off mid-file returns Warning: JPEG format error and exits 0. This matters if your upload validation gates on ExifTool's exit code, since a half-written file passes the check that a zero-byte file fails.

How do I suppress the empty-file error in a batch run?

Use -q -q to silence the message, but note that the exit code stays 1. The -m flag does not suppress it, because ExifTool does not classify an empty file as a minor error. If you are scanning a directory and want to skip empties, filter them before the run with find . -size +0c rather than trying to quiet ExifTool afterwards.

Does a zero-byte upload mean I have a security problem?

Not by itself, but it points at one. A zero-byte file means your pipeline created a destination and stored a record before it confirmed the bytes arrived. Any validation you run after that point, including virus scanning and MIME sniffing, is running against a file that may not be the one the user sent.

What ExifTool version does this apply to?

The behaviour described here was verified on ExifTool 12.76. The empty-file error and the File not found error have been distinct for many major versions, but check your own version with exiftool -ver before matching exit codes against this page.

Is Your Upload Endpoint Validating Anything?

CheckYourVibe scans your deployed app for upload endpoints that accept unvalidated content, missing size limits, and files served from paths that skip processing.

How-To Guides

ExifTool "Error: File Is Empty": Causes and Fixes (2026)