
Введение:
При разработке сложной системы приходится сталкиваться с необходимостью использования nginx в качестве reverse proxy. Один из частых сценариев использования это роутинг, список правил, регулирующих путь запроса во внутренние системы или путь между внутренними подсистемами. Зачастую быстро развивающиеся сервисы обрастают правилами, назначение которых не очевидно или имеет недокументированные особенности.
Проверенный способ рефакторинга систем с недокументированным поведением:
зафиксировать и вылечить упростить. Фиксировать будем тестами.

Совсем кратко о себе: разрабатываю высоконагруженные сервисы, веду канал о разработке в стартапах, где делюсь своим опытом. Буду рад видеть каждого! Заходите!
Способы тестирования конфигурации Nginx:
1) Ручное тестирование
Cложно не сказать про ручной вариант. Правки конфигурации, запуск, несколько cUrl и так по кругу насколько хватит терпения и трудолюбия. Иногда вполне практичный выбор, но увлекаться не стоит.
Полезных опции:
команда nginx -t - проверяет синтаксис конфигурационных файлов Nginx, не запуская и не перезапуская сам сервер.
директива rewrite_log on; - включает подробное логирование работы rewrite-правил в Nginx.
2) Проект "nginx-location-match-finder" на python
Утилита определяет, какой именно директивой location будет обрабатываться заданный URI. Скорее вспомогательная утилита нежели полноценный способ тестирования.
Минусы: последняя поддерживаемая версия nginx - 1.18.0.
3) Test::Nginx
Это Perl-библиотека для тестирования конфигов nginx и поведения location, rewrite, proxy_pass и т.д
Возможности Test::Nginx :
в тесте описать мини-конфиг nginx;
поднять nginx на заданном порту;
отправить HTTP-запросы;
проверить статус/заголовки/тело ответа/логи.
Структура ��еста Test::Nginx :
use Test::Nginx::Socket -Base;
plan tests => 1;
run_tests();
__DATA__
=== TEST <Название теста>
--- http_config
Содержимое попадает в секцию http { ... } в тестовом nginx.conf.
--- config
Содержимое попадает внутрь server { ... } тестового nginx.
То есть здесь — тестируемый location, rewrite, proxy_pass и т.п.
Это именно тот участок конфига, который мы проверяем.
--- request
HTTP-запрос, который Test::Nginx отправит к запущенному nginx.
--- more_headers
Дополнительные заголовки HTTP-запроса.
--- error_code:
Ожидаемый HTTP-код ответа.
--- response_body_like
Регулярное выражение, которое должно совпасть с телом ответа.Пример теста на Test::Nginx можно посмотреть тут или тут
Плюсы Test::Nginx:
Готовый dsl для тестирования nginx
Test::Nginx это нативный тестовый фреймворк для nginx, что несет много плюсов само по себе.
Минусы Test::Nginx:
Test::Nginx для запуска требует установленного в системе perl и nginx
Придется освоить основы perl, +1 к зоопарку технологий в проекте
Нужно иметь в окружении perl и nginx либо запускать их в docker(с perl и nginx) например как тут. Во втором варианте придется дорабатывать ci скрипты и\или Dockerfile, для корректного возврата результата и списка упавших тестов в ci джобу.
4) Nginx_в _Docker + эхо_сервер + TestContainers + тесты на любом ЯП
Потребуется несколько шагов:
4.1) Упаковать nginx вместе с вашей конфигурацией в docker. Во многих сервисах такой способ поставки уже используется, поэтому данный шаг может быть уже фактически выполнен, как вот тут.
Пример Nginx location c rewrite:
set $myserver ${MY_SERVER};
location /api/v1/old-query {
rewrite ^/api/v1/old-query/(.*)$ /api/v2/$1 break;
proxy_set_header Host $host;
proxy_set_header X-Orig-Request-URI $request_uri;
proxy_set_header X-Uri $uri;
proxy_pass $myserver;
}4.2) Установить или написать на любом удобном ЯП простой эхо сервис.
Код эхо сервиса на python:
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class H(BaseHTTPRequestHandler):
def do_GET(self):
body = {
"path": self.path,
"headers": {k: v for k, v in self.headers.items()}
}
self.send_response(200)
self.send_header("Content-Type","application/json")
self.end_headers()
self.wfile.write(json.dumps(body).encode())
HTTPServer(("0.0.0.0", 9000), H).serve_forever()4.3) Написать сами тесты.
Пример самого упрощенного теста на python:
import pytest, requests
CASES = [
("/api/v1/old-query/items?id=42", "/api/v2/items?id=42"),
("/api/v1/old-query/users/alice", "/api/v2/users/alice"),
]
@pytest.mark.parametrize("inp,expected", CASES)
def test_rewrite(inp, expected):
r = requests.get("http://localhost:8080"+inp, timeout=2)
assert r.status_code == 200
assert r.text.strip() == expected
4.4) Запуск
Для демонстрации концепции можно запускать эхо сервис и nginx в docker-compose и ваши тесты непосредственно из ide или консоли.
Для продвинутого варианта отлично подойдет библиотека TestContainers, в тесте можно поднять все необходимые контейнеры (эхо сервис и nginx), провести необходимые проверки и выключить поднятые ранее контейнеры по завершению. Этот способ отлично подходит для CI.
Плюсы Nginx_в _Docker + эхо_сервер + TestContainers + тесты на любом ЯП:
полноценный тест на боевой сборке вашего nginx, без дополнительных правок исключительно под тесты, +1 к доверию такому тестированию.
тесты легко встраиваются в CI
не добавляет дополнительных технологий в проект
Минусы Nginx_в _Docker + эхо_сервер + TestContainers + тесты на любом ЯП:
придется самостоятельно создавать свое подобие фреймворка для тестирования nginx на вашем ЯП
5) Проверка информационной безоп��сности с помощью Gixy
Gixy — инструмент для статического анализа конфигураций Nginx, предназначенный для поиска ошибок и уязвимостей. Он проверяет проблемы с alias, try_files, небезопасные регулярки, SSRF-риски, неправильные заголовки и другие конфигурационные ловушки.
Оригинальный проект от яндекса достаточно давно не развивается тк последний релиз был 2 года назад. Зато есть форк со свежим релизом от октября 2025 года.
К примеру для такого nginx-конфига:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log notice;
pid /tmp/nginx.pid;
events { worker_connections 1024; }
set $myserver ${MY_SERVER};
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
upstream echo_upstream { server python_echo:9000; }
server {
listen 8080;
server_name _;
# Удобная диагностика переписываний
rewrite_log on;
location /api/v1/old-query {
rewrite ^/api/v1/old-query/(.*)$ /api/v2/$1 break;
proxy_set_header Host $host;
proxy_set_header X-Orig-Request-URI $request_uri;
proxy_set_header X-Uri $uri;
proxy_pass $myserver;
}
# Явный fallback, чтобы видеть, что локация не сработала
location / { return 404 "no test location matched\n"; }
}
}
Запуск утилиты Gixy:
gixy .\nginx.confВыдает следующие уязвимости:
==================== Results ===================
>> Problem: [http_splitting] Possible HTTP-Splitting vulnerability.
Description: Using variables that can contain "\n" or "\r" may lead to http injection.
Additional info: https://github.com/dvershinin/gixy/blob/master/docs/en/plugins/httpsplitting.md
Reason: At least variable "$uri" can contain "\n"
Pseudo config:
server {
server_name _;
location /api/v1/old-query {
proxy_set_header X-Uri $uri;
}
}
------------------------------------------------
>> Problem: [version_disclosure] Do not enable server_tokens on or server_tokens build
Description: Using server_tokens on; or server_tokens build; allows an attacker to learn the version of NGINX you are running, which can be used to exploit known vulnerabilities.
Additional info: https://gixy.getpagespeed.com/en/plugins/version_disclosure/
Reason: Missing server_tokens directive - defaults to 'on' which promotes information disclosure
Pseudo config:
server {
server_name _;
}
------------------------------------------------
>> Problem: [worker_rlimit_nofile_vs_connections] The worker_rlimit_nofile should be at least twice than worker_connections.
Description: The worker_rlimit_nofile should be at least twice than worker_connections.
Additional info: https://gixy.getpagespeed.com/en/plugins/worker_rlimit_nofile_vs_connections/
Reason: Missing worker_rlimit_nofile with at least twice the value of worker_connections
Pseudo config:
events {
worker_connections 1024;
}
==================== Summary ===================
Total issues:
Unspecified: 0
Low: 0
Medium: 1
High: 2Итоговые выводы:
Ручное тестирование и nginx-location-match-finder полезны при диагностике проблем
Для тестов можно использовать Test::Nginx и\или "Nginx_в _Docker + эхо_сервер + TestContainers + тесты на любом ЯП"
Если вам подходит Perl в вашем стеке проекта, то выбор однозначное в пользу Test::Nginx благодаря богатому dsl и нативности для nginx
Если Perl для вас не подходит, то вариант с эхо сервисом. Он может быть реализован на любом языке и максимально приближен к продовой конфигурации
Gixy для ИБ и поиска уязвимостей
Всем корректности, безопасности и добра!
