This answer will be used as a placeholder for the not fully supported position: sticky and will be updated over time. It is currently advised to not use the native implementation of this in a production environment.
See this for the current support: https://caniuse.com/#feat=css-sticky
Use of position: sticky
An alternative answer would be using position: sticky. As described by W3C:
A stickily positioned box is positioned similarly to a relatively positioned box, but the offset is computed with reference to the nearest ancestor with a scrolling box, or the viewport if no ancestor has a scrolling box.
This described exactly the behavior of a relative static header. It would be easy to assign this to the <thead> or the first <tr> HTML-tag, as this should be supported according to W3C. However, both Chrome, IE and Edge have problems assigning a sticky position property to these tags. There also seems to be no priority in solving this at the moment.
What does seem to work for a table element is assigning the sticky property to a table-cell. In this case the <th> cells.
Because a table is not a block-element that respects the static size you assign to it, it is best to use a wrapper element to define the scroll-overflow.
The code
div {
  display: inline-block;
  height: 150px;
  overflow: auto
}
table th {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
}
/* == Just general styling, not relevant :) == */
table {
  border-collapse: collapse;
}
th {
  background-color: #1976D2;
  color: #fff;
}
th,
td {
  padding: 1em .5em;
}
table tr {
  color: #212121;
}
table tr:nth-child(odd) {
  background-color: #BBDEFB;
}
<div>
  <table border="0">
    <thead>
      <tr>
        <th>head1</th>
        <th>head2</th>
        <th>head3</th>
        <th>head4</th>
      </tr>
    </thead>
    <tr>
      <td>row 1, cell 1</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
    <tr>
      <td>row 2, cell 1</td>
      <td>row 2, cell 2</td>
      <td>row 1, cell 2</td>
      <td>row 1, cell 2</td>
    </tr>
  </table>
</div>
 
 
In this example I use a simple <div> wrapper to define the scroll-overflow done with a static height of 150px. This can of course be any size. Now that the scrolling box has been defined, the sticky <th> elements will corespondent "to the nearest ancestor with a scrolling box", which is the div-wrapper.
Use of a position: sticky polyfill
Non-supported devices can make use of a polyfill, which implements the behavior through code. An example is stickybits, which resembles the same behavior as the browser's implemented position: sticky.
Example with polyfill: http://jsfiddle.net/7UZA4/6957/