MIME Type: Why Files Aren't Served Correctly

If the browser downloads a CSS file or executes an image, the problem is the MIME type. Here you'll see which header is wrong and how to fix it.

6 min Updated 23 Sep 2026

You open a CSS file and instead of rendering it, the browser shows a download dialog. Or worse: you've uploaded an .svg file and Chrome executes it as HTML. These two symptoms point to one thing: the Content-Type header the server sends doesn't match the file's actual content. The problem isn't your code, it's the MIME type.

What exactly does the MIME type determine

Every time the browser fetches a resource, the server sends a header in the response like this:

Content-Type: text/css; charset=UTF-8

The first part is the MIME type, the second is the charset parameter. Based on this string, the browser decides whether to render, execute, or download the file. If the header is missing or wrong, the browser falls back to its default behavior, and that behavior is almost never what you want.

A MIME type is made of two parts: type/subtype. For example text/html, image/webp, application/json. Its reference standard is IANA, and the official list is updated regularly, but browsers in practice recognize a subset of it.

Common types you'll actually run into

ExtensionCorrect MIME typeWhat happens if it's wrong
.csstext/cssDownloaded instead of rendered; site without styles
.jsapplication/javascriptMIME error in console, script doesn't execute
.svgimage/svg+xmlSVG executed as HTML; XSS risk
.woff2font/woff2Font doesn't load, text falls back to default font
.jsonapplication/jsonfetch in JavaScript throws an error
.webpimage/webpImage isn't displayed

The error Refused to execute script because its MIME type ('text/html') is not executable in the Chrome console means the server returned an HTML page instead of the JS file. This almost always means the file path was a 404 and the server sent the HTML error page with a 200 code. The problem isn't the MIME type, it's the path.

Adding a custom type in Apache

If you have an extension Apache doesn't recognize, you have two options. The first is from inside .htaccess:

AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType image/avif .avif

Put these lines in the domain root. If you serve files from a CDN or subdirectory, the .htaccess must be in the same path the file is served from, not just the root.

The second way is at the server level. In Apache's mime.types or in the Nginx http block:

types {
    application/wasm wasm;
    font/woff2 woff2;
}

An important difference: .htaccess takes effect without a server restart and works on shared hosting too. Changing mime.types requires root access and a restart, but affects all domains on the server. If you only have one site, .htaccess is enough.

Where people get it wrong

The most common mistake I see is this: someone uploads a .woff2 font, the font doesn't load, and they go digging into CSS and @font-face and load order. Meanwhile, in the browser's Network tab, the font response is a 200 with the header Content-Type: application/octet-stream. The browser doesn't recognize the font and silently skips it. You see no error in the console. The text just falls back to the default font and you spend hours hunting for a CSS bug.

The tell is this: in the Network tab, look at the Type column. If it says octet-stream for the font, the problem is the MIME type, not the CSS.

How the wrong type causes unwanted downloads

When the server sends application/octet-stream, the browser has no way to render it and downloads the file. This behavior is safe for unknown files, but it's a disaster for CSS, JS, and fonts.

The more dangerous case is the opposite: a file that should be downloaded is served as HTML. If you return a user-uploaded file with Content-Type: text/html, the browser executes it on your domain. That means stored XSS. This is why, in a correct configuration, the upload folder should either have restricted MIME types or be served with Content-Disposition: attachment.

How to figure out which layer sets the type

First, check the actual header with curl, not what the browser shows from cache:

curl -I https://example.com/assets/style.css

The output shows the Content-Type. If it's correct but the browser still has a problem, it's cache. If it's wrong, go to the layer that sets it:

  1. Apache or Nginx: .htaccess and mime.types
  2. PHP: the header() function in the script serving the file
  3. CDN: some CDNs copy the type from the origin, some set it themselves
  4. Browser: the file extension on your system, not on the server

If you serve a file with PHP and forget header('Content-Type: ...'), PHP sends text/html by default. This is one of the most frequent reasons PDFs and images get downloaded in hand-rolled upload scripts.

MIME type and browser cache

A point that's rarely mentioned: the browser also stores the MIME type in cache. If you've fixed the header and the user still gets the old file with the wrong type, it's a cache issue. For static files, versioning in the filename (style.a3f9.css) solves this completely and you don't need to ask users to clear their cache.

Also take the X-Content-Type-Options: nosniff header seriously. This header tells the browser to trust the declared type and not guess on its own. Without it, some browsers read a file's content and guess the type from the content, which produces unpredictable results.

Where to apply these settings

On shared hosting, your access to the server's mime.types is closed and your only tool is .htaccess. If you have a WordPress site and a plugin generates .webp or .avif files, first make sure their MIME type is declared correctly, then move on to optimization. For a quick check of headers and DNS records, use the free webmaster tools, and if needed, check the domain status with DNS and network lookup.

If the number of static files and domains has grown and you keep having to tweak .htaccess, it's time to have full control over the web server configuration. A dedicated server lets you set mime.types correctly once and have it apply to all sites. For smaller sites, Linux hosting with .htaccess access is enough for most of these cases.

Before any change, back up the configuration file. One wrong line in .htaccess takes the whole site down with a 500 error, and if you don't have the previous version, you'll have to rewrite it from scratch.

Frequently asked questions

Why does my CSS file download instead of opening in the browser?

Because the server sends the wrong MIME type for it, usually application/octet-stream or text/plain. The browser doesn't know this file should be rendered and downloads it. Check the Content-Type header with curl -I, and if it's wrong, add the line AddType text/css .css to .htaccess.

What's the difference between a MIME type and a file extension?

A file extension is just part of the filename and the server can ignore it. A MIME type is what the server declares in the Content-Type header, and the browser decides based on that. A file with a .jpg extension can have a MIME type of text/html, and the browser will execute it as HTML.

How do I check a file's MIME type on the server?

The simplest way is curl -I https://example.com/file.ext, which shows the response headers. In the browser, the Network tab and the Type column give the same information, but if caching is active you may see the old value. To be sure, make the request with a random parameter like ?v=123 to bypass the cache.

Does MIME type affect SEO?

Not directly, but indirectly yes. If a CSS or JS file is served with the wrong type, the page renders incompletely and Core Web Vitals suffer. If an image is served with the wrong type, Google may not index it and it won't appear in image results. These affect ranking, though the Content-Type header itself isn't a ranking factor.

Was this page helpful?