The error most likely occurred at one of the endpoints.
As you say, both TCP data and IP/TCP headers are protected by checksums. While these are not foolproof, the chance of a datagram with a random error generating the same checksum as the uncorrupted version of that same datagram is very slim indeed.
However, bit errors occurring before the web server served the data, or after the data was received by the client, could still go unnoticed. This would include:
- errors generated by the hard drive(s) on the server (not terribly likely as the data are checksummed, but it has been known to happen)
- errors on the SATA bus or SATA chipset (the latter is not all that uncommon, while the former is usually caught by parity checking)
- faulty RAM on the server, especially if non-ECC RAM is used
- faulty server NIC (especially if the NIC supports TCP checksum offloading)
- NIC/PCIe bus/RAM/SATA chipset/hard drive errors on the client
- and last but not least: bugs in the web browser, especially with regards to cache handling
Considering the vast number of components involved in such a seemingly simple transaction, it's really remarkable that we don't see errors like that more often.