Они немного отличаются - у ETag нет никакой информации, которую клиент может использовать, чтобы определить, следует ли в будущем снова делать запрос на этот файл. Если ETag - это все, что у него есть, он всегда должен будет сделать запрос. Однако, когда сервер считывает ETag из запроса клиента, сервер может затем определить, следует ли отправить файл (HTTP 200) или сказать клиенту просто использовать свою локальную копию (HTTP 304). ETag - это просто контрольная сумма для файла, который семантически изменяется при изменении содержимого файла.
Заголовок Expires используется клиентом (и прокси-серверами / кэшами), чтобы определить, нужно ли ему вообще отправлять запрос серверу. Чем ближе вы находитесь к дате истечения срока действия, тем больше вероятность того, что клиент (или прокси-сервер) отправит HTTP-запрос на этот файл с сервера.
Поэтому на самом деле вы хотите использовать ОБА заголовки - установите для заголовка Expires разумное значение, основанное на частоте изменения содержимого. Затем настройте отправку ETag так, чтобы, когда клиенты отправляли запрос на сервер, он мог легче определить, следует ли отправлять файл обратно.
Последнее замечание об ETag - если вы используете настройку сервера с балансировкой нагрузки на нескольких машинах под управлением Apache, вы, вероятно, захотите отключить генерацию ETag. Это связано с тем, что inode используются как часть алгоритма хеширования ETag, который будет отличаться для разных серверов. Вы можете настроить Apache так, чтобы он не использовал inode в качестве части вычисления, но затем вы должны убедиться, что временные метки в файлах совпадают, чтобы обеспечить одинаковый ETag для всех серверов.