Выберите продукт

X‑Request‑ID сквозь стек: корреляция запросов в Nginx, PHP‑FPM и приложении

Разберём, как пронести X‑Request‑ID от входного запроса до логов Nginx, PHP‑FPM и вашего PHP‑кода, чтобы быстро связывать события в одну трассу. Будут рабочие конфиги, нормализация и валидация заголовка, JSON‑форматы логов, проверки и типовые нюансы продакшна.
X‑Request‑ID сквозь стек: корреляция запросов в Nginx, PHP‑FPM и приложении

Если в продакшне «горит», без быстрой корреляции логов вы теряете часы. Единый идентификатор запроса X‑Request‑ID решает задачу: один и тот же ID проходит через Nginx, PHP‑FPM и приложение, а потом попадает в разные логи. По этому ключу вы мгновенно связываете access‑лог Nginx, slowlog PHP‑FPM, ошибки приложения, SQL‑трассы и даже события фоновых джобов.

Зачем X‑Request‑ID и откуда его брать

Цель простая: присвоить каждому HTTP‑запросу уникальный идентификатор и пронести его сквозь весь стек. Идеальный вариант — «рождение» ID на границе (Nginx), затем проброс вверх по цепочке. Если клиент прислал свой X‑Request‑ID, мы либо доверяем ему по списку доверенных прокси, либо валидируем и, при сомнении, генерируем новый. На собственном VDS вы полностью контролируете Nginx и PHP‑FPM, а значит, можете реализовать строгую нормализацию и логирование без компромиссов. Если выбираете панель для управления сервером, пригодится обзор «Сравнение панелей для VDS 2025»: какую панель выбрать для VDS.

Правило: в логах всех компонент должен оказаться один и тот же ID. Иначе корреляция ломается.

Схема на высоком уровне

  • Генерируем или нормализуем X‑Request‑ID в Nginx.
  • Возвращаем его клиенту в ответе (заголовок X‑Request‑ID).
  • Пишем этот ID в access‑лог Nginx (лучше в JSON).
  • Пробрасываем в PHP‑FPM через fastcgi_param как HTTP_X_REQUEST_ID.
  • Логируем ID в access‑лог и slowlog PHP‑FPM (через формат с переменными окружения).
  • В приложении читаем $_SERVER['HTTP_X_REQUEST_ID'] и пишем в свои логи, метрики, SQL‑комментарии.

Наглядная схема нормализации и логирования X‑Request‑ID в Nginx

Nginx: генерация, нормализация и логирование

В современных сборках Nginx есть переменная $request_id, которая генерируется на каждый запрос. Лучший приём — использовать её как надёжный источник, а заголовок клиента принимать только если он проходит валидацию.

Нормализация X‑Request‑ID

http {
    map $http_x_request_id $req_id {
        ~^[A-Za-z0-9._-]{16,64}$  $http_x_request_id;  # валидный пользовательский ID
        default                    $request_id;        # иначе генерируем свой
    }

    # Возвращаем клиенту для отладки и корреляции
    add_header X-Request-ID $req_id always;

    # Логирование: обычный формат
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$req_id"';

    # Логирование: JSON для парсеров/агрегаторов
    log_format app_json escape=json '{"time":"$time_iso8601",'
        '"rid":"$req_id","remote":"$remote_addr","method":"$request_method",'
        '"uri":"$request_uri","status":$status,"bytes":$body_bytes_sent, '
        '"rt":$request_time,"urt":"$upstream_response_time"}';

    access_log /var/log/nginx/access.log main;
    # альтернативно
    # access_log /var/log/nginx/access.jsonl app_json;

    server {
        listen 80;
        server_name example.test;

        root /var/www/html;
        index index.php index.html;

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php-fpm.sock;

            # Пробрасываем ID в PHP-FPM как переменную окружения HTTP_X_REQUEST_ID
            fastcgi_param HTTP_X_REQUEST_ID $req_id;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
    }
}

Валидация регуляркой (~^[A-Za-z0-9._-]{16,64}$) защищает от мусора/инъекций и стабилизирует формат. Длину подберите под свои нужды. Если хотите принимать клиентский ID только от доверенных прокси, добавьте условие через geo или map по $remote_addr, подменяя $req_id на $request_id для недоверенных источников.

Фолбэк, если нет $request_id

Редко, но может пригодиться: собрать устойчивый surrogate‑ID из доступных переменных.

map "$pid-$connection-$msec-$request_length" $req_id { default $value; }

Это хуже встроенного генератора, но лучше, чем никакой корреляции.

PHP‑FPM: проброс и логирование ID

PHP‑FPM может вести собственный access‑лог с кастомным форматом. Мы добавим туда X‑Request‑ID, взятый из окружения. Параллельно настроим slowlog.

; /etc/php-fpm.d/www.conf (или соответствующий пул)
[www]
; Ловим stdout/stderr, чтобы сообщения не терялись
catch_workers_output = yes

; Включаем access.log для пула
access.log = /var/log/php-fpm/www-access.log
access.format = %R - %u %t "%m %r%Q%q" %s %{HTTP_X_REQUEST_ID}e

; Slowlog: стек вызовов для медленных запросов
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log

; При необходимости жёсткий таймаут
; request_terminate_timeout = 60s

Ключевой фрагмент — %{HTTP_X_REQUEST_ID}e: это значение переменной окружения, которую мы передали из Nginx директивой fastcgi_param HTTP_X_REQUEST_ID $req_id. Теперь FPM‑access, slowlog и логи приложения можно связать по одному идентификатору.

Привязка X‑Request‑ID в access‑лог и slowlog PHP‑FPM и передача в приложение

Приложение: как безопасно использовать X‑Request‑ID

В PHP ID доступен как $_SERVER['HTTP_X_REQUEST_ID']. Если Nginx всегда нормализует и добавляет заголовок, ваше приложение может безоговорочно доверять этому значению. Если же вы работаете без нормализации — валидируйте поле в коде.

<?php
$rid = $_SERVER['HTTP_X_REQUEST_ID'] ?? null;
if (!$rid || !preg_match('/^[A-Za-z0-9._-]{16,64}$/', $rid)) {
    // Нежелательно генерировать новый ID в приложении, лучше чинить Nginx.
    // Но как страховку можно сделать так:
    $rid = bin2hex(random_bytes(16));
    if (!headers_sent()) {
        header('X-Request-ID: ' . $rid);
    }
}

// Пример унифицированного лог-формата
function app_log(string $level, string $message, array $ctx = []) use ($rid): void {
    $line = json_encode([
        'time' => date('c'),
        'level' => $level,
        'rid' => $rid,
        'msg' => $message,
        'ctx' => $ctx,
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    error_log($line);
}

app_log('info', 'Request started', [
    'uri' => $_SERVER['REQUEST_URI'] ?? '-',
    'method' => $_SERVER['REQUEST_METHOD'] ?? '-',
]);

В проектах на PSR‑3 (Monolog) достаточно добавлять rid в контекст логгера на уровне middleware. Для популярных фреймворков разумно продублировать X‑Request‑ID в контексте трейсинга и в метриках.

SQL‑корреляция

Чтобы соотнести медленные SQL‑запросы с HTTP‑запросом, можно добавлять комментарий с ID. Большинство СУБД сохраняют комментарии в логах.

$rid = $_SERVER['HTTP_X_REQUEST_ID'] ?? '-';
$sql = '/* rid=' . substr($rid, 0, 64) . ' */ SELECT * FROM users WHERE id = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);

Проверка и отладка цепочки

Пошаговая проверка после деплоя:

  1. Сделайте HTTP‑запрос и убедитесь, что ответ содержит заголовок X-Request-ID.
  2. Проверьте access‑лог Nginx: ищите свой ID.
  3. Проверьте www-access.log PHP‑FPM: ID должен совпадать.
  4. Инициируйте медленный запрос и убедитесь, что www-slow.log появился с тем же ID (через access‑лог/логи приложения вы увидите связку).
  5. Проверьте логи приложения: строки с тем же rid.
# Примеры проверки
curl -s -D - http://example.test/ -o /dev/null | grep -i X-Request-ID

# Ищем ID в логах Nginx
RID="abc123..."
grep -F "$RID" /var/log/nginx/access.log

# Ищем ID в логах FPM
grep -F "$RID" /var/log/php-fpm/www-access.log

# Ищем ID в логах приложения
grep -F "$RID" /var/www/app/storage/logs/app.log

Безопасность: доверие, валидация и приватность

  • Не доверяйте клиентскому заголовку без валидации: ограничьте алфавит и длину, как в примере map.
  • При наличии внешнего прокси/CDN внедряйте «белый список» источников, от которых принимаете X-Request-ID, для остальных — генерируйте свой.
  • Не размещайте в ID персональные данные или бизнес‑смыслы. Это технический ключ корреляции.
  • В ответ добавляйте X-Request-ID — это удобно и для поддержки, и для клиента.

Производительность и хранение логов

  • JSON‑лог формата app_json удобен для парсеров. Если у вас высокий трафик, рассмотрите семплирование или раздельные access‑логи по локациям.
  • Включайте сжатие на уровне ротации и удерживайте ровно столько, сколько нужно по SLO и требованиям комплаенса.
  • Старайтесь не штамповать несколько ID в разных местах. Источник истины — Nginx.
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Расширение: фоновые задачи и очереди

Если в HTTP‑обработчике вы ставите задачу в очередь, положите X‑Request‑ID в payload. Исполнитель сможет прокинуть его в свой лог и метрики. Для cron‑задач, инициирующих внешние HTTP‑запросы, стартуйте новый ID и также соблюдайте валидацию/проброс.

Типовые ошибки

  • Забыли fastcgi_param HTTP_X_REQUEST_ID $req_id — в PHP‑FPM и приложении ID будет пустым.
  • Оставили доверие клиентскому заголовку без валидации — возможны мусорные значения и проблемы парсинга логов.
  • Пишете в разные форматы логов без ID в одном из них — корреляция частично теряется.
  • Дублируете генерацию ID в приложении, тогда как Nginx уже сгенерировал — появляется «рассинхрон».

Чек‑лист внедрения

  1. Nginx: map с валидацией, переменная $req_id, add_header, формат access‑лога с ID.
  2. Nginx → PHP‑FPM: fastcgi_param HTTP_X_REQUEST_ID $req_id.
  3. PHP‑FPM: access.log с %{HTTP_X_REQUEST_ID}e, настроенный slowlog.
  4. Приложение: чтение $_SERVER['HTTP_X_REQUEST_ID'], единый формат логов, внедрение в SQL‑комментарии и события очередей.
  5. Проверка: запрос → заголовок в ответе → три лога с одинаковым ID.

Итоги

Один короткий идентификатор даёт мощную экономию времени при расследовании инцидентов. Схема «генерируем и нормализуем в Nginx → пробрасываем в PHP‑FPM → логируем в приложении» детерминирована, проста в поддержке и не требует тяжёлой APM‑системы. Настройте её однажды — и каждый инцидент будет раскручиваться в разы быстрее.

Поделиться статьей

Вам будет интересно

systemd-networkd на VDS: пропал интернет после reboot — DHCP, cloud-init и метрики маршрутов OpenAI Статья написана AI (GPT 5)

systemd-networkd на VDS: пропал интернет после reboot — DHCP, cloud-init и метрики маршрутов

Если после reboot на VDS «отвалилась» сеть, чаще всего виноваты cloud-init/netplan, конкурирующие DHCP-клиенты или неверная метрик ...
CPU throttling на VDS в Linux: TDP/thermal лимиты, частоты и квоты cgroups v2 (cpu.max) OpenAI Статья написана AI (GPT 5)

CPU throttling на VDS в Linux: TDP/thermal лимиты, частоты и квоты cgroups v2 (cpu.max)

Если на VDS растёт load average и задержки, а CPU «не на 100%», причина часто в throttling: тепловые/мощностные лимиты, steal time ...
Let’s Encrypt через DNS-01: wildcard, автоматизация продления и безопасные deploy-хуки OpenAI Статья написана AI (GPT 5)

Let’s Encrypt через DNS-01: wildcard, автоматизация продления и безопасные deploy-хуки

DNS-01 удобен для wildcard и закрытых сетей: владение доменом подтверждается TXT-записью в DNS. Разбираем выбор certbot/lego/acme. ...