Выкладываешь на сайт файл для скачивания — .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);
.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 показывает, что на самом деле отдаёт сервер, и экономит часы гаданий.
С вами был доктор Лексиум.
Рад, что пригодилось. Думаю, можно php заставить скачиваться, но придётся попрыгать с бубном. Некоторые панели управления позволяют отключать php для поддоменов. Вот самый простой вариант, утащить на него скачиваемые PHP файлы и пр. Ну или поиграться с ремувхендлером.
Вот прям РЕСПЕКТ — РЕСПЕКТИЩЕ!!!! Уже который год искал простое решение (не скрипты, не плагин и т.д.) нигде нет, а здесь ЕСТЬ — автору ЛАЙК:) Прокатило для жпег, зип, пэдэф, тхт и даже для хтмл, не помогает только для PHP — их видать сама CMSка интерпретирует только как код.
Спасибо. Надеюсь помогло. К сожалению попадаются сервера, где через .htaccess не получается настроить.
Весь интернет облазил в поисках решения задачи, ответ нашел только здесь. Выражаю автору статьи огромную благодарность.
Добавил в код пару строк. Попробуйте такой вариант.
Сделал как вы сказали не помогло?