Forbidden characters
Windows rejects these outright. Linux allows almost anything.
<>:"/\|?*Which of the 9 each system rejects
Trap: report:2024.txt is legal on Linux, impossible on Windows: sync skips it.
Learning & reference
A practical guide to how Windows, macOS and Linux store and restrict filenames, and what happens at the moment a file crosses from one to another. Read the background first, or scan the matrix and dig into any row.
None of this is arbitrary, and none of it is a bug. Each system inherited its rules from a different decade and a different set of constraints, and then kept them for compatibility.
Windows still carries MS-DOS with it. Drive letters, the backslash separator and the reserved
device names CON, NUL, COM1 and the rest were decisions made in
1981, when a filename was eight characters and a three-letter extension. Every one of them is still
enforced today, because breaking them would break software that has shipped for forty years.
Linux comes from Unix, where a filename is simply a sequence of bytes. Exactly two values are forbidden: the forward slash, because it separates path components, and the null byte, because it ends a C string. Everything else is legal, including newlines, quotes and a leading dash. The system does not interpret your filename at all, which is both the freedom and the hazard.
macOS is Unix underneath with a different history above it. It has always been case-insensitive by default, and older Macs rewrote filenames into a decomposed Unicode form on disk, so a name typed one way came back with different bytes. That single design choice is still the most common source of duplicate files when work moves between a Mac and anything else.
Underneath the differences, every system is answering the same five questions. Once you know which answer each one gives, the table below stops being a list of exceptions and becomes predictable.
README and readme are two
files; on another they are the same file, and the second one overwrites the first.A file today rarely stays where it was made. It gets committed to a repository on a laptop and checked out in a Linux container, uploaded through a browser and written to object storage, zipped on one machine and extracted on another, or synced through a service that has its own rules on top. Every one of those boundaries is a translation, and translations lose things.
The failures are rarely loud. A name comes back subtly altered, two files collapse into one, an extraction stops part-way through, or a filename matches in testing and not in production. The table below is a map of where those translations go wrong, and each row links to a real file you can download and push through your own code to see which behaviour you actually have.
Scroll the table sideways for more columns
| Rule | Windows | macOS | Linux |
|---|---|---|---|
Special chars : * ? " < > | |
✗forbidden | !avoid : |
✓allowed |
Backslash \ in a name |
✗path separator | ✓ordinary char | ✓ordinary char |
Reserved names CON NUL |
✗forbidden | ✓allowed | ✓allowed |
| Case-sensitive names | ✗merges | ✗merges (default) | ✓distinct |
| Unicode NFC = NFD | !bytes as-is | !stores NFD | !bytes as-is |
Trailing . or space |
✗stripped | ✓kept | ✓kept |
| Path length | !260 default | ✓1024 | ✓4096 |
| Zero-byte file | ✓"0 KB" | ✓"Zero bytes" | ✓0 |
| Text line endings | !CRLF | !LF | !LF |
Windows rejects these outright. Linux allows almost anything.
<>:"/\|?*Which of the 9 each system rejects
Trap: report:2024.txt is legal on Linux, impossible on Windows: sync skips it.
/ vs \The single biggest cross-platform path bug.
Which separator each system uses
Trap: on Linux, folder\file is one filename containing a backslash. Move it to Windows and it becomes a folder ➜ file path, or fails. Web URLs are always /.
Try: backslash\name.txt
Windows keeps legacy device names, even with an extension.
CONPRNAUXNULCOM1–9LPT1–9Whether these names can be used at all
Trap: CON.txt and nul.log can't be created or extracted on Windows.
Are File.txt and file.txt the same file?
What happens to README.txt and readme.txt side by side
Trap: a repo with README.md + Readme.md loses one file when cloned on Windows/Mac.
café can be 4 code points (NFC) or 5 (NFD). Same look, different bytes.
Trap: a Mac-saved café.txt won't match the same name typed on Windows: "file not found" for a name you can see.
Try: nfc-café.txt vs nfd-café.txt
Windows silently strips a trailing . or space.
What happens to a name ending in a dot or a space
Trap: report. becomes report: two files collide into one.
Try: trailing-dot.txt.
Per-name and full-path caps.
Names: 255 units (Win/Mac) or 255 bytes (Linux, so ~63 emoji). *Past 260 only via the \\?\ prefix, or LongPathsEnabled plus an app that opts in.
Try: a 204-char name
A file with no content is valid everywhere, but shown and handled differently.
Trap: upload validators that check MIME but not size > 0 accept empty files; some sync clients skip them; Git needs an explicit add. Used deliberately as markers (.gitkeep).
Try: 0b.bin (exactly 0 bytes)
Same text, different bytes per platform.
The line ending each system writes by default
\r\n CRLF\n LF\n LFTrap: breaks diffs, checksums and line-by-line parsers. Classic Mac used bare \r (CR).
OneDrive, Dropbox and Drive apply the strictest (Windows-style) rules on every platform, plus extra reserved names and path caps.
" * : < > ? / \ |, .lock, CON…LPT9, ~$…, leading/trailing spaces, ~400-char paths/ \ < > : " | ? *, .dropbox, desktop.ini; case-insensitive; no trailing spaces/dotsSafe-everywhere rule: use only A–Z a–z 0–9 . _ -, no leading/trailing dots or spaces, avoid Windows reserved names, keep paths under ~255 chars, normalize Unicode to NFC.
Every rule above has a matching test file in the catalog. Upload one and confirm your app fails gracefully, not silently.
→ Browse the filename & encoding test files
Guides and references for test data, file handling and AI evals. All free, no sign-up. See the full hub.