Загрузка файлов через PHP: что нужно знать до начала
Форма с input type=file и обработчик на PHP — задача на полчаса, если знать про лимиты в php.ini, проверку MIME и безопасное имя файла. Разбираем, что подготовить заранее, чтобы форма не отдавала пустой $_FILES.

Рабочая загрузка файла на PHP — это HTML-форма, обработчик из десяти строк и три-четыре настройки в php.ini, о которых новичок узнаёт в тот момент, когда файл на 5 МБ загружается, а на 50 МБ — молча нет. Если всё готово заранее, форма приёма документа или картинки собирается за полчаса. Ниже — что нужно иметь и проверить до того, как вы напишете первую строку обработчика. Примеры для PHP 8.x.
Что понадобится до начала
- PHP 8.0 или новее и веб-сервер (Apache, Nginx + PHP-FPM или встроенный сервер
php -Sдля локальной проверки). - Доступ к
php.iniили возможность менять настройки через.htaccess/ панель хостинга. Без этого вы не поднимете лимит размера. - Папка на сервере, куда веб-сервер имеет право писать. На большинстве хостингов это подпапка внутри вашего каталога, но не публичный корень — почему, разберём ниже.
- Понимание, что форма должна отправляться методом
POSTс атрибутомenctype="multipart/form-data". Без этого атрибута файл до сервера не доедет, а$_FILESбудет пустым.
Проверить версию и текущие лимиты можно одной командой в терминале:
php -i | grep -Ei "php version|upload_max_filesize|post_max_size|file_uploads"
Если вывода про upload_max_filesize нет или file_uploads стоит Off, загрузка отключена на уровне сервера — сначала чините это.
Четыре настройки, которые решают половину проблем
Самая частая жалоба новичка — «маленький файл грузится, большой нет, ошибки никакой». Причина почти всегда в лимитах. Их четыре, и работают они вместе.
| Директива | За что отвечает | Типичное значение по умолчанию |
|---|---|---|
file_uploads | Разрешена ли загрузка вообще | On |
upload_max_filesize | Максимальный размер одного файла | 2M |
post_max_size | Максимальный размер всего POST-запроса | 8M |
max_file_uploads | Сколько файлов за один запрос | 20 |
Главная ловушка: post_max_size должен быть больше upload_max_filesize, потому что в POST-запрос кроме файла идут и другие поля формы. Если upload_max_filesize = 10M, а post_max_size = 8M, файл на 9 МБ обрежется, и вы получите пустой $_FILES без внятной ошибки.
Пустой $_FILES при отправке большого файла — это почти всегда не баг вашего кода, а превышение post_max_size. PHP отбрасывает такой запрос до того, как ваш скрипт вообще запустится.
Менять значения — в php.ini, а на shared-хостинге чаще через .htaccess (если сервер это позволяет):
php_value upload_max_filesize 20M
php_value post_max_size 25M
После правки перезапустите PHP-FPM или веб-сервер и снова проверьте вывод php -i. На Nginx помните ещё про его собственный лимит client_max_body_size — если он меньше, запрос обрежет уже Nginx, и PHP его не увидит.
Минимальная рабочая связка: форма и обработчик
Форма:
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="document">
<button type="submit">Загрузить</button>
</form>
Обработчик upload.php. Он проверяет код ошибки, реальный тип файла и переносит его безопасным именем:
<?php
declare(strict_types=1);
$targetDir = __DIR__ . '/../uploads/'; // папка ВНЕ публичного корня
$allowedMime = ['image/jpeg', 'image/png', 'application/pdf'];
$maxBytes = 20 * 1024 * 1024; // 20 МБ, синхронно с настройками сервера
// 1. Проверяем, что файл вообще пришёл без ошибок
if (!isset($_FILES['document']) || $_FILES['document']['error'] !== UPLOAD_ERR_OK) {
$code = $_FILES['document']['error'] ?? 'нет данных';
exit('Ошибка загрузки, код: ' . $code);
}
$file = $_FILES['document'];
// 2. Проверяем размер на стороне PHP, не доверяя только настройкам
if ($file['size'] > $maxBytes) {
exit('Файл слишком большой');
}
// 3. Определяем реальный MIME по содержимому, а не по имени файла
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, $allowedMime, true)) {
exit('Недопустимый тип файла: ' . $mime);
}
// 4. Генерируем безопасное имя, отбрасывая присланное клиентом
$ext = match ($mime) {
'image/jpeg' => 'jpg',
'image/png' => 'png',
'application/pdf' => 'pdf',
};
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destination = $targetDir . $safeName;
// 5. Переносим именно через move_uploaded_file — она проверяет,
// что файл действительно пришёл через HTTP-загрузку
if (move_uploaded_file($file['tmp_name'], $destination)) {
echo 'Готово: ' . $safeName;
} else {
echo 'Не удалось сохранить файл';
}
Проверка результата: после отправки формы в браузере появится строка «Готово:» и случайное имя файла, а в папке uploads — сам файл. Если видите код ошибки — сверьте его со списком UPLOAD_ERR_* в документации PHP.
Почему нельзя доверять $_FILES['...']['type']
В массиве $_FILES есть поле type, но его присылает браузер, и подделать его тривиально. Отсюда — использование finfo, которая читает реальную сигнатуру содержимого. Полагаться на расширение имени файла тоже нельзя: evil.php можно переименовать в photo.jpg, а сервер при неверной настройке всё равно выполнит его как PHP.
Три правила безопасности, без которых форма опасна
Загрузка файлов — один из самых частых входов для атак. До запуска в бой закройте минимум это.
- Храните файлы вне публичного корня. Если папка
uploadsдоступна по прямому URL и туда попал исполняемый скрипт, злоумышленник запустит его запросом в браузере. Держите загрузки вышеpublic_htmlи отдавайте их через отдельный PHP-скрипт с проверкой прав. - Никогда не сохраняйте файл под присланным именем. В имени могут быть
../, спецсимволы или второе расширение. Генерируйте имя сами, как в примере черезrandom_bytes. - Проверяйте тип по содержимому.
finfoсFILEINFO_MIME_TYPEнадёжнее, чем расширение и чем полеtypeиз запроса.
Если не получилось
- $_FILES пустой, ошибки нет. Забыт
enctype="multipart/form-data"в форме или превышенpost_max_size. Проверьте оба. - Код ошибки
UPLOAD_ERR_INI_SIZE(значение 1). Файл большеupload_max_filesize. Поднимите лимит и перезапустите PHP. - Код
UPLOAD_ERR_NO_TMP_DIR(6) илиUPLOAD_ERR_CANT_WRITE(7). Нет временной папки или прав на запись. Проверьтеupload_tmp_dirвphp.iniи права целевой папки. - «413 Request Entity Too Large» в браузере. Запрос обрезал Nginx. Увеличьте
client_max_body_sizeв конфиге Nginx и перезагрузите его. - move_uploaded_file вернула false. Целевой папки не существует или у веб-сервера нет прав на запись в неё. Создайте папку и выдайте права процессу PHP.
Что настроить дальше
Базовая форма работает — дальше смотрите в сторону валидации на клиенте (атрибут accept у input сокращает число заведомо неверных загрузок, но не заменяет серверную проверку), ограничения на количество файлов в мультизагрузке и антивирусной проверки загруженного, если пользователи присылают документы. Отдача файлов обратно клиенту с проверкой прав — отдельная тема, здесь мы разобрали только приём. Начните с того, чтобы синхронизировать лимиты PHP, Nginx и вашу проверку $maxBytes: расхождение между ними — источник большинства «то грузится, то нет».
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: PHP Manual — Handling file uploads, PHP Manual — POST method uploads and $_FILES
Частые вопросы
У меня PHP 7.4, а не 8.x — код будет работать?
Почему маленький файл загружается, а большой — нет, и никакой ошибки?
Обязательно ли проверять MIME через finfo, если я и так ограничил расширения?
Можно ли хранить загрузки прямо в public_html, чтобы отдавать по прямой ссылке?
Как перенести файл, если move_uploaded_file возвращает false?
Как разрешить загрузку нескольких файлов сразу?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.