You rename a photo from .webp to .png and the upload still refuses it. Or a download that should open in the browser saves to disk instead. Or a CSV opens in a text editor as gibberish. All three are the same confusion: there are several independent claims about what a file is, and they disagree.
Three claims, not one
The extension. Characters after the last dot in the name. It is a hint for the operating system about which program to launch, and it is trivially wrong because anyone can rename anything.
The MIME type. A label like image/png or text/csv transmitted alongside the file: in an HTTP Content-Type header, in a multipart upload, in an email part. It describes intent and it is also just a claim.
The magic bytes. The first few bytes of the file itself, which most formats define as a fixed signature. This is the only one of the three derived from the actual contents.
When they conflict, different systems believe different ones, which is exactly why the same file behaves differently in three places.
Signatures worth recognising
PNG 89 50 4E 47 0D 0A 1A 0A .PNG....
JPEG FF D8 FF ....
GIF 47 49 46 38 GIF8
PDF 25 50 44 46 2D %PDF-
ZIP 50 4B 03 04 PK..
RIFF 52 49 46 46 .... 57 45 42 50 RIFF....WEBP
Two of those explain a lot of everyday confusion.
PK at the start of a ZIP is the initials of Phil Katz, and it appears at the start of DOCX, XLSX, PPTX, ODT, EPUB, JAR and APK files: because all of those are ZIP archives with a prescribed internal structure. That is why a ZIP extractor can open a DOCX and show you the XML inside, and why a corrupt Office file is often a corrupt ZIP.
%PDF- is ASCII, which is why a PDF header is readable in a text editor and why a truncated PDF can sometimes be diagnosed by eye.
Why the browser downloads instead of displaying
When a browser fetches a URL, what it does with the response is decided by the Content-Type header, not by the path. A PDF served as application/octet-stream downloads. The same file served as application/pdf opens in the viewer.
Two related headers finish the story. Content-Disposition: attachment forces a download regardless of type, and X-Content-Type-Options: nosniff tells the browser not to second-guess the declared type by inspecting the bytes.
That sniffing is the historical behaviour: browsers used to examine content when the declared type looked wrong, which was convenient and also a security hole, because a file uploaded as text and sniffed as HTML could execute script on the site's own origin. Hence nosniff on anything user-supplied.
Text files have no signature
Plain text formats (CSV, JSON, Markdown, YAML, SQL) have no magic bytes, which is why detection for them is guesswork and why a CSV can be opened as anything.
The one marker that does appear is a byte order mark: EF BB BF for UTF-8. It is invisible in most editors and is a recurring cause of a first column header arriving with three strange characters in front of it, or a JSON parser rejecting a file that looks perfectly valid. See mojibake and UTF-8 for why that happens and what to do about it.
Converting versus renaming
The distinction that started this: renaming changes the label, converting rewrites the bytes.
To make a WebP into a real PNG you have to decode the image and re-encode it in PNG, which an image converter does. The file that comes out has 89 50 4E 47 at the front and will pass any check. The renamed one never will, and the failure message will usually be unhelpful about why.
A short diagnostic
- Read the signature. That tells you what the file is.
- Compare it to the extension. Mismatch → rename or convert, depending on which one is wrong.
- Behaving oddly over HTTP → check
Content-TypeandContent-Disposition. - Rejected by an upload → the server almost certainly checked the bytes, not the name.
When a file opens in the wrong program or an upload is rejected, check what it actually is with the MIME checker before renaming anything: the first few bytes settle the question that the extension only claims to answer.