Utilo

HTML MIME Type – Free, Online, Private

text/html is the type for an HTML document. Send charset=utf-8, and do not use this type for XHTML that you want parsed as XML.

What .html is

An HTML document is text/html. Browsers parse it with the HTML parser, which is not an XML parser. A <br> with no close is fine. A script element runs. The file often starts with <!DOCTYPE html>. The encoding comes from the Content-Type charset parameter, from a BOM, or from a meta charset element, in that order of authority for HTTP. If you send no charset, browsers guess, and a guess of windows-1252 is still something you will see on pages that are actually UTF-8. Send text/html; charset=utf-8. .htm is the same type if you still serve that extension.

IANA registers text/html. The HTML specification requires a character encoding, and charset=utf-8 is the one to send. The media type is text/html. Extensions that use it on this page: .html. A client compares the type, not the filename, once the response has arrived. The filename still matters on disk and in Content-Disposition, which is why a wrong extension and a wrong header are two different bugs.

What sends text/html

What sends text/html: Send text/html; charset=utf-8 for every HTML document, including error pages. A JSON API that fails over to an HTML error page trains clients to parse the wrong language. If the handler cannot produce JSON, send the error status with a small JSON body or with text/plain. For documents, set the header on the response that has the body. A 304 has no body and does not need a fresh copy of the document. Cache HTML that is personalized with Cache-Control: private or no-store. The type does not make a response public, and a missing cache header might. The file on disk is still .html. Once the response exists, a client trusts text/html, not the filename. Fragments and query strings do not change the type. A page that emits HTML or a fragment based on an Accept header of text/html versus application/json must send Vary: Accept. Without it, a cache serves the HTML to an API client.

Content-Type for .html

Send text/html; charset=utf-8. The charset parameter names the encoding of the body. Leaving it off forces the client to guess, and for several text types the registered default is not UTF-8. Do not send a charset on a redirect. The header belongs on the response that contains the bytes.

Content-TypeContent-Type: text/html; charset=utf-8

nginx, Apache, and Express

These snippets map .html to text/html. nginx reads the types block when it picks a type from the URL's extension. A location that sets default_type, or an application that sets the header itself, overrides that map. Apache AddType applies in the directory context where you put it. The Express static example writes the header for files ending in .html so a stale mime database in the framework cannot disagree with the type on this page.

nginx# /etc/nginx/mime.types, inside the types { } block
text/html                   html;
charset utf-8;
charset_types text/html;
ApacheAddType text/html .html
AddCharset UTF-8 .html
Expressimport express from "express";

const app = express();
app.use(express.static("public", {
  setHeaders(res, filePath) {
    if (filePath.endsWith(".html")) {
      res.setHeader("Content-Type", "text/html; charset=utf-8");
    }
  },
}));

Pitfalls for text/html

Pitfalls that show up for text/html, and not for a neighboring type with a similar extension: MIME sniffing turns a text/plain or a mislabeled image into HTML if the body starts with a tag and nosniff is absent. Send X-Content-Type-Options: nosniff on responses users can influence. Another trap is serving a downloaded HTML file as an attachment from your origin. Opening it still runs script in some flows. Prefer a separate host for untrusted HTML. A third is application/octet-stream on a .html file, which downloads a page the user expected to read. A fourth is a charset in the header that disagrees with meta charset. Make them match.

  • application/xhtml+xml is a different parse. Do not send it for a document that is merely HTML with an XML declaration.
  • .htm and .html should share one types entry.
  • A service worker that rewrites navigation responses can replace the type. The browser will believe the worker.
  • Content-Security-Policy is not a media type. You still want it on HTML. The type does not set it.

Frequently asked questions

Which charset should HTML use?

UTF-8. Send text/html; charset=utf-8 and put the same encoding in meta charset.

Is text/html appropriate for XHTML?

Only if you want the HTML parser. XML parsing uses application/xhtml+xml and rejects ill-formed markup.

Why send nosniff?

So a browser does not treat a user-controlled response of another type as HTML and run it as a page.

Does a 304 include this type?

The cached response does. The 304 itself has no document body to type.