Why does a cross-origin HEAD request need a preflight check?
The primary intent of preflighting is to ensure that servers aren't suddenly sent cross-origin browser-based requests that they could have never received before the CORS spec was implemented.
Before the CORS spec, it was impossible to send any browser-based cross-origin requests other than GET or POST. The browser simply would not allow you to fire up an XHR instance, set the method to PUT (for example) and send it off to an endpoint on a different origin. You couldn't send GET or POST cross-origin requests via XHR either, but you COULD send a cross-origin GET or POST via a form submit, or a cross-origin GET via an <img>
or <script>
tag, for example (which made JSONP the only option pre-CORS). Once browsers implemented the CORS spec, this changed. Now it IS possible to send any cross-origin ajax request, provided the server opts-in.
The CORS spec defines "simple" methods (GET and POST) along with "simple" request headers. These correspond to the types of cross-origin requests that you could already send from the browser pre-CORS spec. Non-simple cross-origin requests, such as PUT or POST/GET requests with an X-header (for example) could not be sent from a browser pre-CORS spec. So, for these types of requests, the concept of preflighting was written into the spec to ensure servers do not receive these types of non-simple cross-origin browser-based requests without explicitly opting in. In other words, if you don't want to allow these types of requests, you don't have to change your server at all. The preflight will fail, and the browser will never send the underlying request.
Directly addressing your question: HEAD requests do not normally result in a preflight. HEAD is considered a simple request method according to the CORS spec. As you know, HEAD requests are just GETs without a response payload. This is the most likely reason why HEAD and GET requests are treated the same, even though you could not send a cross-origin HEAD request pre-CORS from the browser. If your HEAD contains non-simple headers, it will be preflighted though, just like GET.