How to fix VTT MIME type for HTML5 video
TL;DR — Fix WebVTT captions that fail in HTML5 video by checking the VTT file, track element, and server content type for .vtt files.
Related tool
WebVTT Validator Online
Sometimes a VTT file is valid, but the browser still refuses to load it correctly. The most common cause is an incorrect MIME type - the server is sending the file with the wrong Content-Type header. This guide walks you through diagnosing and fixing MIME type issues for WebVTT captions.
Quick answer
Validate the VTT file first. Then make sure the server serves .vtt files with the text/vtt content type. Most servers default to text/plain or application/octet-stream for unknown extensions, which causes browsers to refuse to parse the file as WebVTT.
Use the HTML5 Video Subtitle Converter if your source file needs to be converted into clean WebVTT.
Why MIME type matters for WebVTT
Browsers use the Content-Type HTTP header to decide how to interpret a file. For WebVTT captions, the spec requires text/vtt. If the server sends a different type:
- Chrome and Edge silently refuse to load the track
- Firefox logs a console warning and disables the track
- Safari may load the file but display captions inconsistently
- Mobile browsers often fail with no visible error
Even if the VTT file content is perfect, the wrong MIME type makes captions invisible. This is one of the most frustrating subtitle bugs because the file looks correct, the <track> element looks correct, but captions never appear.
What to check
For HTML5 video captions, check each of these in order:
1. The file starts with WEBVTT
The very first line must be WEBVTT (uppercase, no BOM). Anything else - even a blank line above it - causes the parser to reject the file.
2. Timestamps use dots, not commas
WebVTT uses 00:00:01.000 (with a dot). SRT uses 00:00:01,000 (with a comma). Mixing these breaks the file.
3. The file URL returns the VTT file
Open the URL directly in a browser tab. You should see the raw VTT text. If you get a 404, redirect, or HTML error page, the URL is wrong.
4. The server content type is text/vtt
Use browser DevTools (Network tab) to inspect the response headers. The Content-Type should be exactly text/vtt (or text/vtt; charset=utf-8).
5. The track element points to the correct URL
The src attribute must resolve to the same URL you tested in step 3.
6. CORS headers are correct (cross-origin)
If the VTT file is on a different domain than the video page, the server must send Access-Control-Allow-Origin headers, and the <track> element needs crossorigin="anonymous".
Example track element
<video controls crossorigin="anonymous">
<source src="video.mp4" type="video/mp4" />
<track kind="subtitles" src="captions.en.vtt" srclang="en" label="English" default />
</video>
The VTT file must be reachable from the browser, not only present in the project folder. If you’re testing locally, serve files through a local web server (not file://) - browsers block VTT loading from file:// URLs.
Step-by-step workflow
1. Validate the VTT file
Before fixing the server, make sure the file is actually valid WebVTT.
- Open the WebVTT Validator
- Upload your
.vttfile - Fix any reported errors (missing header, wrong timestamp format, etc.)
- Re-validate until the report is clean
A malformed VTT file will fail even with the right MIME type.
2. Open the VTT URL directly in a browser
Copy the exact URL from the <track src="..."> attribute and paste it into a new browser tab.
Expected: The browser displays the raw VTT text, starting with WEBVTT.
Common failures:
- 404 Not Found → file is missing or the path is wrong
- Redirect to login → the file is behind authentication
- HTML error page → the path resolves to a server-rendered page, not the file
- Download prompt → the server sent the wrong MIME type (often
application/octet-stream)
3. Check the response headers for content-type
Open browser DevTools (F12), go to the Network tab, reload the video page, and click the .vtt request.
Look at the Response Headers section:
content-type: text/vtt; charset=utf-8 ✅ Correct
content-type: text/plain ❌ Wrong - browsers refuse to parse
content-type: application/octet-stream ❌ Wrong - triggers download
content-type: text/html ❌ Wrong - usually means a 404 page
You can also check from the command line:
curl -I https://example.com/captions.en.vtt
4. Configure the server to serve .vtt as text/vtt
The fix depends on your server:
Apache (.htaccess or virtual host config):
AddType text/vtt .vtt
Nginx (in mime.types or server block):
types {
text/vtt vtt;
}
Or directly in a location block:
location ~ \.vtt$ {
add_header Content-Type text/vtt;
}
IIS (web.config):
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".vtt" mimeType="text/vtt" />
</staticContent>
</system.webServer>
</configuration>
Express.js (Node):
express.static('public', {
setHeaders: (res, path) => {
if (path.endsWith('.vtt')) {
res.setHeader('Content-Type', 'text/vtt');
}
}
});
Netlify (_headers file):
/*.vtt
Content-Type: text/vtt; charset=utf-8
Vercel (vercel.json):
{
"headers": [{
"source": "/(.*).vtt",
"headers": [{ "key": "Content-Type", "value": "text/vtt; charset=utf-8" }]
}]
}
Cloudflare Pages (_headers file):
/*.vtt
Content-Type: text/vtt; charset=utf-8
Access-Control-Allow-Origin: *
S3 + CloudFront: Set Content-Type: text/vtt as object metadata when uploading. For existing files, copy the object back to itself with new metadata or use the AWS CLI:
aws s3 cp s3://bucket/captions.vtt s3://bucket/captions.vtt \
--content-type "text/vtt" --metadata-directive REPLACE
5. Reload the video page and test captions
After updating server config:
- Restart the server (or wait for the config to apply)
- Hard refresh the video page (Ctrl+Shift+R / Cmd+Shift+R)
- Re-check the
Content-Typein DevTools - it should now showtext/vtt - Click the CC button in the video player
- Verify captions appear at the correct times
If captions still don’t show, see the troubleshooting section below.
Common mistakes
Fixing MIME type before validating the file
A wrong content type is only one possible issue. A malformed VTT file will still fail after the server is fixed. Always validate the file first.
Order of operations:
- Validate the file (catch syntax errors)
- Verify the URL returns the file (catch 404s and routing issues)
- Check the MIME type (catch server config issues)
- Test in the browser (catch CORS or browser-specific issues)
Using a local file path in production
The track src must point to a URL the browser can fetch from the deployed site. Common mistakes:
src="C:\Users\me\captions.vtt"→ only works on the dev machinesrc="file:///path/to/captions.vtt"→ blocked by browser securitysrc="../captions/en.vtt"→ may resolve incorrectly after deployment
Use absolute or root-relative URLs:
<track src="/captions/en.vtt" srclang="en" />
<track src="https://cdn.example.com/captions/en.vtt" srclang="en" />
Renaming SRT to VTT
Renaming captions.srt to captions.vtt does not convert the file. SRT and WebVTT use different formats:
| Difference | SRT | WebVTT |
|---|---|---|
| Header | None | WEBVTT required |
| Timestamps | 00:00:01,000 | 00:00:01.000 |
| Cue numbers | Required | Optional |
| Styling | Limited | CSS-like cue settings |
Convert SRT to VTT properly using the SRT to VTT Converter so the header and timestamp format are correct.
Forgetting CORS headers for cross-origin VTT files
If your video page is at example.com and the VTT file is at cdn.example.com, the browser blocks the request unless the server sends:
Access-Control-Allow-Origin: https://example.com
And the <track> element includes:
<video crossorigin="anonymous">
<track src="https://cdn.example.com/captions.vtt" />
</video>
Without both, captions silently fail to load.
BOM (Byte Order Mark) at the start of the file
Some text editors (Notepad on Windows, older versions of Excel) save UTF-8 files with a BOM - 3 invisible bytes at the start. The WebVTT parser sees this as garbage before WEBVTT and rejects the file.
Fix: Re-save the file as “UTF-8 without BOM” in your editor:
- VS Code: Click encoding in bottom-right → “Save with Encoding” → “UTF-8”
- Notepad++: Encoding menu → “Convert to UTF-8 (without BOM)”
- Sublime Text: File → Save with Encoding → “UTF-8”
Troubleshooting scenarios
Scenario 1: VTT works locally but fails after deployment
Likely cause: Production server has different MIME type config than dev server.
Fix: Apply the server-specific config from step 4 above. Many static hosts (Netlify, Vercel, Cloudflare) need an explicit _headers file to override defaults.
Scenario 2: Works in Chrome but not Firefox
Likely cause: Firefox is stricter about MIME types and CORS than Chrome.
Fix:
- Confirm
Content-Type: text/vtt(nottext/plain) - If cross-origin, ensure
Access-Control-Allow-Originis set - Check the Firefox console for specific error messages
Scenario 3: Works on desktop but not mobile
Likely cause: Mobile browsers (especially iOS Safari) are stricter about HTTPS, CORS, and MIME types.
Fix:
- Serve all assets (video and VTT) over HTTPS
- Verify CORS headers
- Test on actual devices, not just desktop emulators
Scenario 4: Captions show briefly then disappear
Likely cause: Track loads but the cue parser fails partway through the file.
Fix: Re-validate the VTT file. Look for malformed cues in the middle of the file - a single bad timestamp can break everything that follows.
Scenario 5: Console shows “Cross-origin track must specify crossorigin”
Fix: Add crossorigin="anonymous" to the <video> element AND ensure the VTT file’s server sends Access-Control-Allow-Origin.
Frequently asked questions
What’s the correct MIME type for WebVTT?
text/vtt is the official MIME type defined in the WebVTT specification. Some older docs mention text/vtt; charset=utf-8 - both work, but always use UTF-8 encoding.
Can I use text/plain for VTT files?
No. Browsers specifically check for text/vtt and refuse to parse files with other types. This is a security and correctness measure - it prevents arbitrary text files from being interpreted as captions.
How do I check the MIME type without DevTools?
Use curl from the command line:
curl -I https://example.com/captions.vtt
Or use an online HTTP header checker. Look for the Content-Type line.
Why does the file download instead of displaying?
The server is sending Content-Type: application/octet-stream (the default “unknown binary file” type). This tells the browser to download rather than display. Configure the server to send text/vtt instead.
Do I need to set the MIME type for .srt files too?
If you serve SRT files for download (rather than embedding as <track>), the MIME type is application/x-subrip or text/plain. But HTML5 <track> requires WebVTT, so SRT files shouldn’t be referenced from <track> directly.
Will fixing the MIME type fix all caption issues?
Only MIME-type-related issues. If the file is malformed, the URL is wrong, or CORS is misconfigured, captions still fail. Always validate the file and check the network request as well.
How do I know if my CDN supports custom MIME types?
Most CDNs (CloudFront, Cloudflare, Fastly, Akamai) honor the origin server’s Content-Type header by default. If your origin sends text/vtt, the CDN forwards it. Some CDNs let you override headers in their config.
Related guides
- Why VTT captions are not loading
- How to validate WebVTT files
- Why subtitles do not show in HTML5 video
Related tools
Use the WebVTT Validator Online
Validate WebVTT captions online and check missing WEBVTT headers, timestamp syntax, cue order, and HTML5 caption issues. No signup, no upload, and everything runs locally in the browser.
Open VTT validator