[{"data":1,"prerenderedAt":835},["ShallowReactive",2],{"blog-how-to/exiftool-file-is-empty":3},{"id":4,"title":5,"body":6,"category":805,"date":806,"dateModified":806,"description":807,"draft":808,"extension":809,"faq":810,"featured":808,"headerVariant":820,"image":821,"keywords":822,"meta":823,"navigation":277,"ogDescription":824,"ogTitle":821,"path":825,"readTime":826,"schemaOrg":827,"schemaType":828,"seo":829,"sitemap":830,"stem":831,"tags":832,"twitterCard":833,"__hash__":834},"blog/blog/how-to/exiftool-file-is-empty.md","ExifTool \"Error: File Is Empty\": Causes and Fixes (2026)",{"type":7,"value":8,"toc":792},"minimark",[9,13,17,28,36,53,58,61,102,105,109,112,208,211,347,360,364,371,376,383,386,390,401,404,408,415,423,436,440,443,633,643,653,657,664,667,670,695,699,757,776,788],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,16],"p",{},"You ran ExifTool over an uploaded image and got this:",[18,19,24],"pre",{"className":20,"code":22,"language":23},[21],"language-text","Error                           : File is empty\n","text",[25,26,22],"code",{"__ignoreMap":27},"",[14,29,30,31,35],{},"Here is the useful part: that message means one thing and nothing else. The file is ",[32,33,34],"strong",{},"exactly zero bytes",". Not corrupt, not truncated, not the wrong format. Zero.",[37,38,39],"tldr",{},[14,40,41,44,45,48,49,52],{},[25,42,43],{},"Error: File is empty"," fires at exactly 0 bytes and never at 1 byte or 20. A truncated file gets ",[25,46,47],{},"Warning: JPEG format error"," and exits ",[32,50,51],{},"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.",[54,55,57],"h2",{"id":56},"confirm-it-before-you-debug-it","Confirm It Before You Debug It",[14,59,60],{},"One command, before anything else:",[62,63,65],"code-block",{"label":64},"Check the actual size",[18,66,70],{"className":67,"code":68,"language":69,"meta":27,"style":27},"language-bash shiki shiki-themes github-dark","ls -l /path/to/upload.jpg\n# -rw-r--r--  1 app  app  0 Aug 23 16:04 upload.jpg\n#                         ^ this is the whole diagnosis\n","bash",[25,71,72,89,96],{"__ignoreMap":27},[73,74,77,81,85],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"svObZ","ls",[73,82,84],{"class":83},"sDLfK"," -l",[73,86,88],{"class":87},"sU2Wk"," /path/to/upload.jpg\n",[73,90,92],{"class":75,"line":91},2,[73,93,95],{"class":94},"sAwPA","# -rw-r--r--  1 app  app  0 Aug 23 16:04 upload.jpg\n",[73,97,99],{"class":75,"line":98},3,[73,100,101],{"class":94},"#                         ^ this is the whole diagnosis\n",[14,103,104],{},"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.",[54,106,108],{"id":107},"what-exiftool-actually-reports-by-file-size","What ExifTool Actually Reports, By File Size",[14,110,111],{},"These are real runs on ExifTool 12.76. The exit codes are the part worth writing down, because most upload pipelines gate on them.",[113,114,115,131],"table",{},[116,117,118],"thead",{},[119,120,121,125,128],"tr",{},[122,123,124],"th",{},"Input",[122,126,127],{},"Message",[122,129,130],{},"Exit code",[132,133,134,147,157,170,182,196],"tbody",{},[119,135,136,140,144],{},[137,138,139],"td",{},"0 bytes",[137,141,142],{},[25,143,43],{},[137,145,146],{},"1",[119,148,149,152,155],{},[137,150,151],{},"1 byte",[137,153,154],{},"none, parses it as text",[137,156,51],{},[119,158,159,162,166],{},[137,160,161],{},"20-byte truncated JPEG",[137,163,164],{},[25,165,47],{},[137,167,168],{},[32,169,51],{},[119,171,172,175,180],{},[137,173,174],{},"path does not exist",[137,176,177],{},[25,178,179],{},"Error: File not found",[137,181,146],{},[119,183,184,190,194],{},[137,185,186,187],{},"0 bytes with ",[25,188,189],{},"-m",[137,191,192],{},[25,193,43],{},[137,195,146],{},[119,197,198,203,206],{},[137,199,186,200],{},[25,201,202],{},"-q -q",[137,204,205],{},"suppressed",[137,207,146],{},[14,209,210],{},"Reproduce it yourself in about ten seconds:",[62,212,214],{"label":213},"Reproduce all three cases",[18,215,217],{"className":67,"code":216,"language":69,"meta":27,"style":27},": > empty.jpg                        # zero bytes\nprintf 'x' > onebyte.jpg             # one byte\nhead -c 20 real.jpg > trunc.jpg      # truncated\n\nexiftool empty.jpg;   echo \"exit=$?\"   # Error: File is empty, exit=1\nexiftool onebyte.jpg; echo \"exit=$?\"   # no error,             exit=0\nexiftool trunc.jpg;   echo \"exit=$?\"   # Warning: JPEG format error, exit=0\n",[25,218,219,234,250,272,279,306,327],{"__ignoreMap":27},[73,220,221,224,228,231],{"class":75,"line":76},[73,222,223],{"class":83},":",[73,225,227],{"class":226},"snl16"," >",[73,229,230],{"class":87}," empty.jpg",[73,232,233],{"class":94},"                        # zero bytes\n",[73,235,236,239,242,244,247],{"class":75,"line":91},[73,237,238],{"class":83},"printf",[73,240,241],{"class":87}," 'x'",[73,243,227],{"class":226},[73,245,246],{"class":87}," onebyte.jpg",[73,248,249],{"class":94},"             # one byte\n",[73,251,252,255,258,261,264,266,269],{"class":75,"line":98},[73,253,254],{"class":79},"head",[73,256,257],{"class":83}," -c",[73,259,260],{"class":83}," 20",[73,262,263],{"class":87}," real.jpg",[73,265,227],{"class":226},[73,267,268],{"class":87}," trunc.jpg",[73,270,271],{"class":94},"      # truncated\n",[73,273,275],{"class":75,"line":274},4,[73,276,278],{"emptyLinePlaceholder":277},true,"\n",[73,280,282,285,287,291,294,297,300,303],{"class":75,"line":281},5,[73,283,284],{"class":79},"exiftool",[73,286,230],{"class":87},[73,288,290],{"class":289},"s95oV",";   ",[73,292,293],{"class":83},"echo",[73,295,296],{"class":87}," \"exit=",[73,298,299],{"class":83},"$?",[73,301,302],{"class":87},"\"",[73,304,305],{"class":94},"   # Error: File is empty, exit=1\n",[73,307,309,311,313,316,318,320,322,324],{"class":75,"line":308},6,[73,310,284],{"class":79},[73,312,246],{"class":87},[73,314,315],{"class":289},"; ",[73,317,293],{"class":83},[73,319,296],{"class":87},[73,321,299],{"class":83},[73,323,302],{"class":87},[73,325,326],{"class":94},"   # no error,             exit=0\n",[73,328,330,332,334,336,338,340,342,344],{"class":75,"line":329},7,[73,331,284],{"class":79},[73,333,268],{"class":87},[73,335,290],{"class":289},[73,337,293],{"class":83},[73,339,296],{"class":87},[73,341,299],{"class":83},[73,343,302],{"class":87},[73,345,346],{"class":94},"   # Warning: JPEG format error, exit=0\n",[348,349,350],"danger-box",{},[14,351,352,355,356,359],{},[32,353,354],{},"The truncated case is the one that should worry you."," If your validation step is ",[25,357,358],{},"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.",[54,361,363],{"id":362},"why-a-zero-byte-file-shows-up-in-an-upload-pipeline","Why a Zero-Byte File Shows Up in an Upload Pipeline",[14,365,366,367,370],{},"A zero-byte file is not a corrupted write. It is a ",[32,368,369],{},"file that was created and never written to",". Three things produce that shape.",[372,373,375],"h3",{"id":374},"the-stream-was-already-consumed","The stream was already consumed",[14,377,378,379,382],{},"This is the most common one in Node. A body-parsing middleware reads the multipart stream to build ",[25,380,381],{},"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.",[14,384,385],{},"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.",[372,387,389],{"id":388},"the-read-raced-the-write","The read raced the write",[14,391,392,393,396,397,400],{},"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 ",[25,394,395],{},"finish"," event (or the ",[25,398,399],{},"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.",[14,402,403],{},"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.",[372,405,407],{"id":406},"the-write-went-somewhere-else","The write went somewhere else",[14,409,410,411,414],{},"Container-mounted volumes, ",[25,412,413],{},"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.",[14,416,417,418,422],{},"The tell: the file you find is zero bytes and its mtime is the time of the ",[419,420,421],"em",{},"read",", not the upload.",[424,425,426],"warning-box",{},[14,427,428,429,432,433,435],{},"Serverless platforms add a fourth: on most of them only ",[25,430,431],{},"/tmp"," is writable, and it is not shared between invocations. Writing an upload to a relative path can fail outright, and writing to ",[25,434,431],{}," in one invocation then reading it in another finds nothing there.",[54,437,439],{"id":438},"stop-gating-on-the-exit-code","Stop Gating on the Exit Code",[14,441,442],{},"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.",[62,444,446],{"label":445},"Check what the file actually is, not whether ExifTool was happy",[18,447,449],{"className":67,"code":448,"language":69,"meta":27,"style":27},"# Bad: passes on a truncated file, fails on an empty one\nexiftool \"$f\" > /dev/null || reject \"$f\"\n\n# Better: assert the file type ExifTool detected\nTYPE=$(exiftool -s3 -FileType \"$f\" 2>/dev/null)\n[ \"$TYPE\" = \"JPEG\" ] || reject \"$f\"\n\n# Also catch the truncation warning, which exits 0\nif exiftool -validate -warning -error \"$f\" 2>&1 | grep -qi 'error\\|warning'; then\n  reject \"$f\"\nfi\n",[25,450,451,456,486,490,495,529,561,565,571,615,627],{"__ignoreMap":27},[73,452,453],{"class":75,"line":76},[73,454,455],{"class":94},"# Bad: passes on a truncated file, fails on an empty one\n",[73,457,458,460,463,466,468,470,473,476,479,481,483],{"class":75,"line":91},[73,459,284],{"class":79},[73,461,462],{"class":87}," \"",[73,464,465],{"class":289},"$f",[73,467,302],{"class":87},[73,469,227],{"class":226},[73,471,472],{"class":87}," /dev/null",[73,474,475],{"class":226}," ||",[73,477,478],{"class":79}," reject",[73,480,462],{"class":87},[73,482,465],{"class":289},[73,484,485],{"class":87},"\"\n",[73,487,488],{"class":75,"line":98},[73,489,278],{"emptyLinePlaceholder":277},[73,491,492],{"class":75,"line":274},[73,493,494],{"class":94},"# Better: assert the file type ExifTool detected\n",[73,496,497,500,503,506,508,511,514,516,518,520,523,526],{"class":75,"line":281},[73,498,499],{"class":289},"TYPE",[73,501,502],{"class":226},"=",[73,504,505],{"class":289},"$(",[73,507,284],{"class":79},[73,509,510],{"class":83}," -s3",[73,512,513],{"class":83}," -FileType",[73,515,462],{"class":87},[73,517,465],{"class":289},[73,519,302],{"class":87},[73,521,522],{"class":226}," 2>",[73,524,525],{"class":87},"/dev/null",[73,527,528],{"class":289},")\n",[73,530,531,534,536,539,541,544,547,550,553,555,557,559],{"class":75,"line":308},[73,532,533],{"class":289},"[ ",[73,535,302],{"class":87},[73,537,538],{"class":289},"$TYPE",[73,540,302],{"class":87},[73,542,543],{"class":226}," =",[73,545,546],{"class":87}," \"JPEG\"",[73,548,549],{"class":289}," ] ",[73,551,552],{"class":226},"||",[73,554,478],{"class":79},[73,556,462],{"class":87},[73,558,465],{"class":289},[73,560,485],{"class":87},[73,562,563],{"class":75,"line":329},[73,564,278],{"emptyLinePlaceholder":277},[73,566,568],{"class":75,"line":567},8,[73,569,570],{"class":94},"# Also catch the truncation warning, which exits 0\n",[73,572,574,577,580,583,586,589,591,593,595,598,601,604,607,610,612],{"class":75,"line":573},9,[73,575,576],{"class":226},"if",[73,578,579],{"class":79}," exiftool",[73,581,582],{"class":83}," -validate",[73,584,585],{"class":83}," -warning",[73,587,588],{"class":83}," -error",[73,590,462],{"class":87},[73,592,465],{"class":289},[73,594,302],{"class":87},[73,596,597],{"class":226}," 2>&1",[73,599,600],{"class":226}," |",[73,602,603],{"class":79}," grep",[73,605,606],{"class":83}," -qi",[73,608,609],{"class":87}," 'error\\|warning'",[73,611,315],{"class":289},[73,613,614],{"class":226},"then\n",[73,616,618,621,623,625],{"class":75,"line":617},10,[73,619,620],{"class":79},"  reject",[73,622,462],{"class":87},[73,624,465],{"class":289},[73,626,485],{"class":87},[73,628,630],{"class":75,"line":629},11,[73,631,632],{"class":226},"fi\n",[14,634,635,638,639,642],{},[25,636,637],{},"-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 ",[25,640,641],{},"Validate: 1 Error"," alongside the message.",[644,645,646],"tip-box",{},[14,647,648,649,652],{},"If you are scanning a directory and just want empties out of the way, filter before the run rather than quieting ExifTool afterwards: ",[25,650,651],{},"find . -type f -size +0c -exec exiftool {} +",". That keeps the exit code meaningful for the files you do care about.",[54,654,656],{"id":655},"the-part-that-is-a-security-bug","The Part That Is a Security Bug",[14,658,659,660,663],{},"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 ",[32,661,662],{},"before it knew whether the bytes arrived",". Everything downstream of that point is validating a file the user did not necessarily send.",[14,665,666],{},"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.",[14,668,669],{},"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.",[671,672,674,679,683,687,691],"checklist-section",{"title":673},"Before you close this ticket",[675,676],"checklist-item",{"description":677,"label":678},"ls -l on the exact path ExifTool was given. If it is not 0, this is a different error.","The file really is 0 bytes",[675,680],{"description":681,"label":682},"Every upload (consumed stream), intermittent (write race), or wrong location (path). Guessing here means it comes back.","You know which of the three causes it was",[675,684],{"description":685,"label":686},"Test with head -c 20 on a real image. If your pipeline accepts it, your check is reading the exit code.","Truncated files are rejected too",[675,688],{"description":689,"label":690},"A record created before the bytes are confirmed is a record pointing at unvalidated content.","Validation runs before the file is servable",[675,692],{"description":693,"label":694},"Almost every pipeline caps the maximum. Very few reject a zero-length body, which is why this reaches ExifTool at all.","The upload endpoint has a size floor, not just a ceiling",[54,696,698],{"id":697},"faq","FAQ",[700,701,702,712,721,737,743],"faq-section",{},[703,704,706],"faq-item",{"question":705},"What does ExifTool's Error: File is empty actually mean?",[14,707,708,709,711],{},"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, ",[25,710,179],{},", so the file was created and then nothing was written into it.",[703,713,715],{"question":714},"Why does my truncated image not trigger the error?",[14,716,717,718,720],{},"Because ExifTool treats truncation as a warning, not an error. A JPEG cut off mid-file returns ",[25,719,47],{}," 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.",[703,722,724],{"question":723},"How do I suppress the empty-file error in a batch run?",[14,725,726,727,729,730,732,733,736],{},"Use ",[25,728,202],{}," to silence the message, but note that the exit code stays 1. The ",[25,731,189],{}," 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 ",[25,734,735],{},"find . -size +0c"," rather than trying to quiet ExifTool afterwards.",[703,738,740],{"question":739},"Does a zero-byte upload mean I have a security problem?",[14,741,742],{},"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.",[703,744,746],{"question":745},"What ExifTool version does this apply to?",[14,747,748,749,752,753,756],{},"The behaviour described here was verified on ExifTool 12.76. The empty-file error and the ",[25,750,751],{},"File not found"," error have been distinct for many major versions, but check your own version with ",[25,754,755],{},"exiftool -ver"," before matching exit codes against this page.",[758,759,760,766,771],"related-articles",{},[761,762],"related-card",{"description":763,"href":764,"title":765},"Validation, resizing, and stripping EXIF metadata before an image reaches your storage bucket.","/blog/how-to/image-upload-security","How to Secure Image Uploads",[761,767],{"description":768,"href":769,"title":770},"14 items covering type validation, size limits, storage, and malware scanning.","/blog/checklists/file-upload-checklist","File Upload Security Checklist",[761,772],{"description":773,"href":774,"title":775},"The general version: type checks, size caps, storage isolation, and keeping uploads non-executable.","/blog/how-to/file-upload-security","How to Secure File Uploads",[777,778,781,785],"cta-box",{"href":779,"label":780},"/","Start Free Scan",[54,782,784],{"id":783},"is-your-upload-endpoint-validating-anything","Is Your Upload Endpoint Validating Anything?",[14,786,787],{},"CheckYourVibe scans your deployed app for upload endpoints that accept unvalidated content, missing size limits, and files served from paths that skip processing.",[789,790,791],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",{"title":27,"searchDepth":91,"depth":91,"links":793},[794,795,796,801,802,803,804],{"id":56,"depth":91,"text":57},{"id":107,"depth":91,"text":108},{"id":362,"depth":91,"text":363,"children":797},[798,799,800],{"id":374,"depth":98,"text":375},{"id":388,"depth":98,"text":389},{"id":406,"depth":98,"text":407},{"id":438,"depth":91,"text":439},{"id":655,"depth":91,"text":656},{"id":697,"depth":91,"text":698},{"id":783,"depth":91,"text":784},"how-to","2026-08-23","ExifTool prints this only when a file is exactly zero bytes. What produces a zero-byte upload, why a truncated file passes instead, and how to gate on it safely.",false,"md",[811,813,815,817,818],{"question":705,"answer":812},"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.",{"question":714,"answer":814},"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.",{"question":723,"answer":816},"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.",{"question":739,"answer":742},{"question":745,"answer":819},"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.","yellow",null,"exiftool error file is empty, exiftool file is empty, exiftool empty file, exiftool exit code, zero byte upload, file upload validation, exiftool truncated file",{},"ExifTool says \"File is empty\" only at exactly zero bytes. What causes that in an upload pipeline, and why a truncated file quietly exits 0 instead.","/blog/how-to/exiftool-file-is-empty","7 min read","[object Object]","HowTo",{"title":5,"description":807},{"loc":825},"blog/how-to/exiftool-file-is-empty",[],"summary_large_image","e-HPtVEONhdaVsGNFXxJ3jOi3mNsG2VZAADY06aDjaE",1787602606790]