There are some pure CSS and HTML hacks, but they use things like nested tables, or divs nested in tables, or tables nested in divs, or using spans instead of th
cells. That is, your table no longer looks like a table.
My advice: Use one of the many jQuery plugins that'll let you code your table as a table, and hide the hacks away from you. DataTables can do this, as well as many other handy features (such as sortable columns, a search field, re-orderable columns, etc.).
You can also use the FixedHeader plugin without the rest of the DataTables features, as shown in this example. This has an interesting twist: The height of the table remains dynamic, yet the column names will always display at the top of the page as you scroll the entire page. You don't have to do it that way; you can have a fixed table height with fixed header and footer, as shown in the first example I linked to.