Справочник · Веб и HTTP
Коды ответа HTTP
Что означают коды 1xx–5xx, чем 401 отличается от 403, 301 от 308, 502 от 504, и какой код возвращать из своего API в типичных ситуациях.
Код ответа — первое, что сообщает сервер: получилось или нет, и если нет, то по чьей вине. Первая цифра задаёт класс, остальные уточняют. Клиент, который не знает конкретный код, обязан обработать его как общий код своего класса: неизвестный 299 как 200, неизвестный 499 как 400.
| Класс | Смысл |
|---|---|
1xx | информационные, запрос принят, обработка продолжается |
2xx | успех |
3xx | перенаправление, клиенту нужно сделать ещё один шаг |
4xx | ошибка клиента: запрос неверный или недопустимый |
5xx | ошибка сервера: запрос нормальный, но сервер не справился |
1xx: информационные
| Код | Название | Когда встречается |
|---|---|---|
| 100 | Continue | клиент отправил Expect: 100-continue и ждёт разрешения, прежде чем слать большое тело |
| 101 | Switching Protocols | переход на другой протокол, например на WebSocket |
| 103 | Early Hints | ранние подсказки: браузер может начать загружать стили и скрипты, пока страница ещё готовится |
2xx: успех
| Код | Название | Когда возвращать |
|---|---|---|
| 200 | OK | обычный успешный ответ с телом |
| 201 | Created | ресурс создан; в заголовке Location адрес нового ресурса |
| 202 | Accepted | запрос принят в очередь, но ещё не выполнен |
| 204 | No Content | успех без тела, например после удаления или сохранения настроек |
| 206 | Partial Content | отдана часть файла по заголовку Range, так работает докачка и перемотка видео |
3xx: перенаправления
| Код | Название | Метод при переходе | Кешируется |
|---|---|---|---|
| 301 | Moved Permanently | браузеры меняют POST на GET | да |
| 302 | Found | браузеры меняют POST на GET | нет, если не указано иное |
| 303 | See Other | всегда GET | нет |
| 304 | Not Modified | — | ответ на условный запрос |
| 307 | Temporary Redirect | сохраняется | нет |
| 308 | Permanent Redirect | сохраняется | да |
Как выбрать:
- Сайт переехал на новый адрес навсегда, только GET-страницы — 301. Поисковики переносят вес на новый адрес.
- То же, но через адрес идут и POST-запросы API — 308, чтобы метод и тело не потерялись.
- После отправки формы перенаправить на страницу результата — 303. Это классический шаблон «POST, перенаправление, GET»: обновление страницы не отправит форму второй раз.
- Временное перенаправление, например на страницу входа, — 302 или 307, если важно сохранить метод.
304 Not Modified — не перенаправление в обычном смысле. Клиент спросил «изменился ли ресурс с прошлого раза» через заголовки If-None-Match или If-Modified-Since, а сервер ответил «нет, берите из кеша» без тела. Подробнее о заголовках кеша в соседней статье.
4xx: ошибки клиента
| Код | Название | Когда возвращать |
|---|---|---|
| 400 | Bad Request | запрос не разобрать: битый JSON, нет обязательного параметра |
| 401 | Unauthorized | нужна аутентификация, или токен неверный либо истёк |
| 403 | Forbidden | пользователь известен, но прав на действие нет |
| 404 | Not Found | ресурса нет, или его существование не хотят раскрывать |
| 405 | Method Not Allowed | метод не поддерживается для этого адреса; список разрешённых в заголовке Allow |
| 406 | Not Acceptable | сервер не может отдать ответ в формате из Accept |
| 408 | Request Timeout | клиент слишком долго отправлял запрос |
| 409 | Conflict | конфликт с текущим состоянием: такой логин занят, версия документа устарела |
| 410 | Gone | ресурс удалён навсегда и не вернётся |
| 411 | Length Required | нужен заголовок Content-Length |
| 412 | Precondition Failed | не выполнено условие из If-Match: кто-то успел изменить ресурс |
| 413 | Content Too Large | тело больше допустимого, например загружаемый файл |
| 414 | URI Too Long | слишком длинный адрес, часто из-за огромной строки запроса |
| 415 | Unsupported Media Type | тело не в том формате: ждали JSON, прислали форму |
| 416 | Range Not Satisfiable | запрошенный диапазон байтов за пределами файла |
| 418 | I'm a teapot | шутка из первоапрельского RFC 2324, в работе не используется |
| 422 | Unprocessable Content | синтаксис верный, но данные не проходят проверку |
| 425 | Too Early | сервер не хочет обрабатывать запрос, который может быть повтором |
| 428 | Precondition Required | сервер требует условный запрос, чтобы не потерять чужие изменения |
| 429 | Too Many Requests | превышен лимит запросов; когда повторить, в заголовке Retry-After |
| 431 | Request Header Fields Too Large | слишком большие заголовки, часто разросшиеся cookie |
| 451 | Unavailable For Legal Reasons | доступ закрыт по требованию закона |
401 или 403
Самая частая путаница. Название 401 сложилось исторически и вводит в заблуждение: на деле это «мы не знаем, кто вы». Вместе с ним сервер должен прислать заголовок WWW-Authenticate со способом входа. 403 значит «мы знаем, кто вы, но вам сюда нельзя», и повторная попытка с тем же пользователем не поможет.
400 или 422
400 — запрос невозможно прочитать: невалидный JSON, неверный тип поля. 422 — запрос прочитан, но данные не годятся по смыслу: дата окончания раньше даты начала, email уже зарегистрирован. Многие API используют 400 для обоих случаев, это тоже нормально. Важнее вернуть понятное тело с описанием ошибки:
{
"error": "validation_failed",
"message": "Проверьте поля формы",
"fields": {
"email": "Такой адрес уже зарегистрирован",
"age": "Должно быть число от 14 до 120"
}
}Для таких тел есть стандартный формат RFC 9457, «Problem Details»: поля type, title, status, detail, instance и тип содержимого application/problem+json.
404 вместо 403
Иногда сервер отвечает 404 на чужой ресурс, к которому нет доступа. Так не раскрывается сам факт, что ресурс с таким идентификатором существует. Например, на запрос чужого закрытого репозитория хостинги обычно отвечают именно 404.
5xx: ошибки сервера
| Код | Название | Типичная причина |
|---|---|---|
| 500 | Internal Server Error | необработанное исключение в приложении |
| 501 | Not Implemented | сервер не умеет такой метод вообще |
| 502 | Bad Gateway | веб-сервер перед приложением, например nginx, получил от него неверный ответ или не смог подключиться |
| 503 | Service Unavailable | перегрузка или плановые работы; можно указать Retry-After |
| 504 | Gateway Timeout | приложение за веб-сервером не ответило вовремя |
| 507 | Insufficient Storage | на сервере кончилось место для сохранения |
Как разбирать 502 и 504
Оба кода выдаёт не само приложение, а то, что стоит перед ним: nginx, балансировщик, облачный шлюз.
- 502 чаще всего значит, что приложение не запущено, упало во время запроса или слушает не тот порт или сокет. Смотрите журнал ошибок nginx (
/var/log/nginx/error.log), там будетconnect() failedилиupstream prematurely closed connection. - 504 значит, что приложение живо, но отвечает дольше таймаута. Причина обычно в медленном запросе к базе или во внешнем вызове. Увеличение таймаута помогает только как временная мера.
Какой код вернуть: короткая памятка для API
| Ситуация | Код |
|---|---|
| получили список или объект | 200 |
| создали объект | 201 + Location |
| приняли задачу в фоновую обработку | 202 |
| удалили, сохранили без ответа | 204 |
| битый JSON | 400 |
| нет токена, токен истёк | 401 |
| нет прав на действие | 403 |
| нет такого объекта | 404 |
| объект уже существует, конфликт версий | 409 |
| данные не прошли проверку | 422 или 400 |
| слишком много запросов | 429 + Retry-After |
| исключение в коде | 500 |
| сервис на обслуживании | 503 |
Как посмотреть код ответа
curl -I localhost:8080/health # только заголовки
curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/api/users
curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' localhost:8080/oldПоследняя команда проходит по всем перенаправлениям и показывает итоговый код и адрес. Больше приёмов в шпаргалке по curl. В браузере код виден во вкладке «Сеть» инструментов разработчика.