Перейти к содержанию

Как сделать, чтобы файл скачивался, а не открывался?

Выкладываешь на сайт файл для скачивания — .reg, .bat, .txt, JSON или PDF, — а браузер вместо скачивания открывает его как страницу. Читателю приходится жать «Сохранить как…», а половина просто уходит.

Лечится это двумя способами: атрибутом у ссылки или заголовком, который отдаёт сервер. Разберу оба, плюс как проверить результат одной командой и где я сам наступил на грабли.

Коротко: файл лежит на том же сайте, что и страница, — добавь ссылке атрибут download. Файл на другом домене или скачиваться должно всё из папки — настрой на сервере заголовок Content-Disposition: attachment.

Почему браузер открывает файл

Отдавая файл, сервер сообщает его тип в заголовке Content-Type. Если тип браузеру знаком и он умеет его показать — текст, картинка, PDF, JSON, — он показывает. Незнакомое скачивает. Файл .reg сервер часто отдаёт как text/plain, и браузер честно выводит его текстом.

Решать можно с двух сторон: попросить браузер скачать (атрибут ссылки) или приказать ему со стороны сервера (заголовок).

Способ 1: атрибут download

<a href="/up/antirebut.reg" download>Скачать antirebut.reg</a>

Можно сразу задать имя, под которым файл сохранится:

<a href="/files/report-2026-10.csv" download="Отчёт за октябрь.csv">Скачать отчёт</a>
Ограничение: download работает только для файлов с того же домена, что и страница. Для ссылки на другой сайт браузер атрибут молча игнорирует и открывает файл как обычно. Тогда — способ 2.

Способ 2: заголовок Content-Disposition

Заголовок Content-Disposition: attachment говорит браузеру: «это вложение, сохрани его». Работает для любой ссылки на файл, откуда бы по ней ни пришли.

Apache — файл .htaccess в папке с файлами:

Options -Indexes

ForceType application/octet-stream

<IfModule mod_headers.c>
	Header set Content-Disposition attachment
</IfModule>

Если скачиваться должны только некоторые типы файлов, оберни это в <FilesMatch "\.(reg|bat|ps1|csv)$"> … </FilesMatch>.

nginx — в конфиге сайта:

location /up/ {
    types { }
    default_type application/octet-stream;
    add_header Content-Disposition attachment;
}

PHP — когда файл отдаёт скрипт (например, после проверки доступа или если файл собирается на лету):

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="report.csv"');
readfile($path);
Файлы .php так не скачать. В комментариях к первой версии поста писали, что способ сработал для jpg, zip, pdf, txt и даже html, но не для PHP. Так и есть: сервер выполняет PHP-файл раньше, чем дело доходит до заголовков, и отдаёт результат работы скрипта, а не его текст. Исходники на PHP выкладывайте в архиве или с расширением .txt.

Как проверить

Браузер кэширует ответы, поэтому проверять удобнее не им, а командой curl — она есть и в Windows 10/11, и в Linux:

curl -I https://www.lexium.ru/up/antirebut.reg

Ключ -I показывает только заголовки. Должно быть так:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment

Ту же команду удобно вставить в скрипт, который после выкладки проверяет, что файлы отдаются правильно.

Грабли: мой рецепт 2012 года

Этот пост я написал в 2012 году, когда выложил antirebut.reg к инструкции об отключении перезагрузки после обновления Windows, и браузер стал показывать его как страницу. Тогда я положил в папку .htaccess с такой строкой:

RewriteRule ^(.*)$ $1 [type='application/x']

«У меня работает», — написал я. И правда работало. Но через 14 лет я проверил ответ сервера через curl -I и увидел:

Content-Type: 'application/x'

Кавычки попали в заголовок как есть. Такого типа не существует, и файл скачивался только потому, что браузер не узнавал, что ему прислали. Работало на честном слове. Теперь в папке лежит вариант из способа 2, с нормальным типом и Content-Disposition.

Урок шире этой задачи: «работает» и «работает правильно» — разные вещи. Одна команда curl -I показывает, что на самом деле отдаёт сервер, и экономит часы гаданий.

С вами был доктор Лексиум.

6 комментариев

  1. Рад, что пригодилось. Думаю, можно php заставить скачиваться, но придётся попрыгать с бубном. Некоторые панели управления позволяют отключать php для поддоменов. Вот самый простой вариант, утащить на него скачиваемые PHP файлы и пр. Ну или поиграться с ремувхендлером.

  2. Вот прям РЕСПЕКТ — РЕСПЕКТИЩЕ!!!! Уже который год искал простое решение (не скрипты, не плагин и т.д.) нигде нет, а здесь ЕСТЬ — автору ЛАЙК:) Прокатило для жпег, зип, пэдэф, тхт и даже для хтмл, не помогает только для PHP — их видать сама CMSка интерпретирует только как код.

  3. Весь интернет облазил в поисках решения задачи, ответ нашел только здесь. Выражаю автору статьи огромную благодарность.

Написать комментарий

Адрес почты не публикуется. Обязательные поля отмечены звёздочкой.

Возможно, вы не читали