Что такое код ответа HTTP и какие бывают классы
Код ответа HTTP (status code) - трёхзначное число, которым сервер сообщает итог обработки запроса. Браузер, поисковый робот, мобильное приложение и ваш скрипт смотрят на это число первым делом, ещё до чтения содержимого страницы. Коды описаны в стандарте RFC 9110 (июнь 2022), а официальный реестр ведёт IANA.
Первая цифра определяет класс ответа:
| Класс | Смысл | Частые коды |
|---|---|---|
| 1xx | Информационные, запрос принят, обработка продолжается | 100 Continue, 101 Switching Protocols |
| 2xx | Успех | 200 OK, 201 Created, 204 No Content |
| 3xx | Перенаправление, ресурс в другом месте | 301, 302, 304, 307, 308 |
| 4xx | Ошибка клиента: неверный запрос, нет прав, нет страницы | 400, 401, 403, 404, 429 |
| 5xx | Ошибка сервера: запрос нормальный, но сервер не справился | 500, 502, 503, 504 |
Простое правило для отладки: 4xx - ищите проблему в запросе (адрес, токен, тело), 5xx - в сервере (код приложения, база, прокси, нагрузка). Если клиент получил незнакомый код, он обрабатывает его как первый код того же класса: неизвестный 4xx как 400, неизвестный 5xx как 500.
Коды 2xx: запрос выполнен
- 200 OK - стандартный успешный ответ. Страница открылась, API вернул данные.
- 201 Created - ресурс создан, обычно в ответ на POST. Хорошее API добавляет заголовок
Locationс адресом нового ресурса. - 204 No Content - всё успешно, но тела ответа нет. Типично для DELETE или сохранения настроек.
- 206 Partial Content - сервер отдал часть файла. Так работает докачка и перемотка видео.
Код 200 ещё не гарантирует, что всё хорошо. Если на несуществующий адрес сайт отвечает 200 и показывает "страница не найдена", это так называемая soft 404. Поисковики такие страницы не любят: они могут попасть в индекс как дубли с пустым содержанием.
Коды 3xx: 301, 302 и другие редиректы
Перенаправление говорит клиенту: нужный ресурс по другому адресу, он указан в заголовке Location. Браузер переходит туда автоматически, пользователь замечает только сменившийся URL.
- 301 Moved Permanently - страница переехала навсегда. Поисковики со временем заменяют в индексе старый адрес новым и передают ему накопленные сигналы. Применяйте при смене домена, переходе на HTTPS, склейке www и без www, изменении структуры URL.
- 302 Found - временный переезд. Старый адрес остаётся основным в индексе. Подходит для акций, A/B-тестов, редиректа на страницу входа.
- 304 Not Modified - "у вас уже свежая копия, берите из кеша". Экономит трафик, ошибкой не является.
- 307 и 308 - аналоги 302 и 301 с одним отличием: клиент обязан повторить запрос тем же методом и с тем же телом. При 301 и 302 браузеры исторически превращают POST в GET, и данные формы теряются.
Частая ошибка при переезде сайта - цепочки: http://site.ru ведёт на https://site.ru, оттуда на https://www.site.ru, потом на страницу со слешем. Каждый шаг - лишняя задержка. Настраивайте редирект сразу на финальный адрес. Ещё хуже цикл, когда два адреса перенаправляют друг на друга: браузер покажет ошибку "слишком много переадресаций".
Коды 4xx: ошибки на стороне клиента
400, 401, 403
400 Bad Request - сервер не может разобрать запрос: битый JSON, неверный формат параметра, слишком длинный заголовок. 401 Unauthorized - нужна аутентификация, токена нет или он истёк. 403 Forbidden - вы опознаны, но доступа нет. Запомнить разницу просто: 401 - "кто вы?", 403 - "знаю, кто вы, но нельзя".
404 и 410
404 Not Found - по адресу ничего нет. Причины: опечатка в ссылке, удалённая страница, неверный путь к файлу на сервере, неправильный маршрут в приложении. 410 Gone - ресурс удалён навсегда и не вернётся. Поисковики обычно быстрее убирают из индекса страницы с 410, чем с 404.
405, 409, 422, 429
405 Method Not Allowed - метод не поддерживается: отправили POST туда, где принимают только GET. 409 Conflict - конфликт с текущим состоянием, например пользователь с таким email уже есть. 422 Unprocessable Content - синтаксис верный, но данные не проходят проверку; так отвечает FastAPI на невалидное тело. 429 Too Many Requests - превышен лимит запросов, подождите и повторите.
Коды 5xx: 500, 502, 503, 504 и чем они отличаются
Пятисотые коды означают, что запрос был нормальным, а сломалось что-то на сервере. Для пользователя они выглядят одинаково, а для разработчика указывают на разные места поломки.
| Код | Что произошло | Где искать причину |
|---|---|---|
| 500 Internal Server Error | Приложение упало при обработке запроса | Логи приложения: исключение, ошибка в коде, недоступная база |
| 502 Bad Gateway | Прокси (nginx, балансировщик) не получил нормальный ответ от бэкенда | Запущено ли приложение, верный ли порт или сокет, лог nginx |
| 503 Service Unavailable | Сервер временно не может обслужить запрос | Перегрузка, режим обслуживания, лимиты хостинга |
| 504 Gateway Timeout | Прокси не дождался ответа бэкенда | Медленные запросы к базе, долгие внешние вызовы, таймауты прокси |
Ключевое отличие 500 от 502: при 500 приложение запущено и само сообщило об ошибке, при 502 прокси вообще не смог нормально поговорить с приложением. 502 от 504 отличается так: при 502 бэкенд ответил мусором или оборвал соединение, при 504 не ответил вовремя.
Для 503 полезно отдавать заголовок Retry-After. Поисковые роботы понимают 503 как временную проблему и приходят позже, а не удаляют страницы из индекса. Поэтому на время технических работ отдавайте именно 503, а не 200 с заглушкой и не 404.
Ошибка 502 Bad Gateway: пошаговая диагностика
Самый частый сценарий у начинающих: nginx стоит перед приложением на Python или Node.js, и после деплоя сайт отдаёт 502. Порядок проверки на Linux-сервере:
- Убедитесь, что приложение запущено:
systemctl status myappилиdocker ps, если оно в контейнере. - Проверьте, слушает ли оно нужный порт:
ss -tlnp | grep 8000. - Откройте лог nginx:
sudo tail -n 50 /var/log/nginx/error.log. Строкаconnect() failed (111: Connection refused) while connecting to upstreamзначит, что по указанному адресу никто не слушает. - Сравните адрес в
proxy_passс тем, где реально работает приложение. Типичная ошибка: приложение слушает127.0.0.1:8000, а в конфиге указано8080. - Если бэкенд в Docker, слушать он должен
0.0.0.0, а не127.0.0.1внутри контейнера, иначе снаружи до него не достучаться. - Посмотрите логи самого приложения: оно могло упасть при старте из-за отсутствующей переменной окружения.
Команды из списка подробнее разобраны в шпаргалке по командам Linux, а запуск приложений в контейнерах - в гайде Docker для начинающих.
Как проверить код ответа сервера
В браузере. Откройте DevTools клавишей F12, вкладка Network (Сеть), обновите страницу. В колонке Status виден код каждого запроса. Включите галочку Preserve log, чтобы не терять редиректы.
Через curl. Показать только заголовки ответа:
curl -I https://example.com
# пройти по всем редиректам и показать каждый шаг
curl -sIL https://example.com | grep -iE "^(HTTP|location)"
# вывести только код
curl -s -o /dev/null -w "%{http_code}\n" https://example.com
В Python код лежит в атрибуте status_code, а история редиректов - в history:
import requests
r = requests.get("http://example.com", timeout=10)
print(r.status_code)
for step in r.history:
print(step.status_code, step.url)
Для SEO в Яндекс Вебмастере есть инструмент "Проверка ответа сервера": он показывает код так, как его видит робот Яндекса. Это полезно, если сервер по-разному отвечает браузеру и боту.
Метод HEAD (который отправляет curl -I) некоторые приложения обрабатывают иначе, чем GET, и возвращают 405 или 404, хотя страница работает. Если результат выглядит странно, повторите обычным GET: curl -s -o /dev/null -w "%{http_code}" URL.
Какие коды возвращать в своём API
Когда вы пишете бэкенд, правильный код ответа - часть контракта с клиентом. Фронтенд и мобильное приложение принимают решения по коду, а не по тексту ошибки. Пример на Flask:
from flask import Flask, jsonify, request
app = Flask(__name__)
USERS = {1: {"id": 1, "name": "Анна"}}
@app.get("/users/<int:uid>")
def get_user(uid):
user = USERS.get(uid)
if user is None:
return jsonify(error="user not found"), 404
return jsonify(user), 200
@app.post("/users")
def create_user():
data = request.get_json(silent=True)
if not data or "name" not in data:
return jsonify(error="name is required"), 400
uid = max(USERS) + 1
USERS[uid] = {"id": uid, "name": data["name"]}
return jsonify(USERS[uid]), 201
Чего не делать:
- отвечать 200 с
{"error": "..."}в теле - клиент решит, что всё хорошо; - отдавать 500 на ошибки валидации - это проблема запроса, значит 400 или 422;
- показывать пользователю трассировку стека в теле 500 - так утекает информация о сервере.
Как устроены запросы и ответы в целом, объясняет статья что такое API простыми словами. Другие материалы по серверной части - в разделе Linux.
