Рассмотрим самый простой пример. Буду использовать свой технический домен site.ru в этом и последующих примерах. Допустим, у нас есть сайт test.site.ru. В DNS создана A запись, указывающая на ip адрес сервера, где установлен nginx – nginx_srv. Мы будем проксировать все запросы с этого сервера на другой сервер в локальной сети test_srv, где реально размещается сайт.
Рисуем конфиг для секции server:
server {
listen 80;
server_name test.site.ru;
access_log /var/log/nginx/test.site.ru-access.log;
error_log /var/log/nginx/test.site.ru-error.log;
location / {
proxy_pass http://x.x.x.x; # ip локального сервера
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
Заходим по адресу http://test.site.ru. Мы должны попасть на test_srv, где тоже должен работать какой-то веб сервер. В моем случае это будет тоже nginx. У вас должно открыться содержимое, аналогичное тому, что вы увидите, набрав http://x.x.x.x в локальной сети. Если что-то не работает, то проверьте сначала, что по адресу директивы proxy_pass у вас все корректно работает.
Директива php REMOTE_ADDR не будет возвращать настоящий ip адрес клиента. А он очень часто бывает нужен. Мы это дальше исправим.
Передача реального ip (real ip) адреса клиента в nginx при proxy_pass
В предыдущем примере мы на самом деле передаем реальный ip адрес клиента с помощью директивы proxy_set_header, которая добавляет в заголовок X-Real-IP настоящий ip адрес клиента. Теперь нам нужно на принимающей стороне, то есть test_srv сделать обратную замену – заменить информацию об адресе отправителя на ту, что указана в заголовке X-Real-IP. Добавляем в секцию server следующие параметры:
set_real_ip_from (внешний IP);
real_ip_header X-Real-IP;
Полностью секция server на test_srv в самом простом варианте получается следующей:
server {
listen 80 default_server;
server_name test.site.ru;
root /usr/share/nginx/html;
set_real_ip_from (внешний IP);
real_ip_header X-Real-IP;
location / {
index index.php index.html index.htm;
try_files $uri $uri/ =404;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_intercept_errors on;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_ignore_client_abort off;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
Сохраняем конфиг, перечитываем его и снова проверяем. В логах вы должны увидеть свой реальный ip адрес. Его же вы увидите в логе web сервера на test_srv.
Дальше рассмотрим более сложные конфигурации.
Передача https через nginx с помощью proxy pass
Если у вас сайт работает по https, то достаточно настроить ssl только на nginx_srv, если вы не беспокоитесь за передачу информации от nginx_srv к test_srv. Она может осуществляться по незащищенному протоколу. Рассмотрю пример с бесплатным сертификатом let’s encrypt. Это как раз один из кейсов, когда я использую proxy_pass. Очень удобно настроить на одном сервере автоматическое получение всех необходимых сертификатов. Подробно настройку let’s encrypt я рассматривал отдельно. Сейчас будем считать, что у вас стоит certbot и все готово для нового сертификата, который потом будет автоматически обновляться.
Для этого нам надо на nginx_srv добавить еще один location – /.well-known/acme-challenge/. Полная секция server нашего тестового сайта на момент получения сертификата будет выглядеть вот так:
server {
listen 80;
server_name test.site.ru;
access_log /var/log/nginx/test.site.ru-access.log;
error_log /var/log/nginx/test.site.ru-error.log;
location /.well-known/acme-challenge/ {
root /web/sites/test.site.ru/www/;
}
location / {
proxy_pass http://x.x.x.x;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
Перечитывайте конфиг nginx и получайте сертификат. После этого конфиг меняется на следующий:
server {
listen 80;
server_name test.site.ru;
access_log /var/log/nginx/test.site.ru-access.log;
error_log /var/log/nginx/test.site.ru-error.log;
return 301 https://$server_name$request_uri; # редирект обычных запросов на https
}
server {
listen 443 ssl http2;
server_name test.site.ru;
access_log /var/log/nginx/test.site.ru-ssl-access.log;
error_log /var/log/nginx/test.site.ru-ssl-error.log;
ssl on;
ssl_certificate /etc/letsencrypt/live/test.site.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test.site.ru/privkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
location /.well-known/acme-challenge/ {
root /web/sites/test.site.ru/www/;
}
location / {
proxy_pass http://x.x.x.x;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
Проверяем, что получилось. Видим успешное применение SSL ввиде зеленого замка.
Наш сайт работает по https, при том, что мы вообще не трогали сервер, где этот сайт располагается. Конкретно с web сайтом это может быть не так актуально, но если вы проксируете запросы не на обычный сайт, а на какой-то нестандартный сервис, который трудно перевести на https, это может быть хорошим решением.
Проксирование определенной директории или файлов
Рассмотрим еще один пример. Допустим, у вас форум живет в директории http://test.site.ru/forum/, вы хотите вынести форум на отдельный web сервер для увеличения быстродействия. Для этого к предыдущему конфигу добавьте еще один location.
location /forum/ {
proxy_pass http://x.x.x.x;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect default;
}
Еще одно популярное решение. Вы можете отдавать картинки с одного сервера, а все остальное с другого. В моем примере, картинки будут жить на том же сервере, где nginx, а остальной сайт на другом сервере. Тогда у нас должна быть примерно такая конфигурация локаций.
location / {
proxy_pass http://x.x.x.x;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
location ~ \.(gif|jpg|png)$ {
root /web/sites/test.site.ru/www/images;
}
Чтобы все это работало корректно, необходимо, чтобы сам сайт умел правильно размещать свои изображения. Вариантов это организовать множество. Можно как на сервере монтировать сетевые паки различными способами, так и программистам изменять код для управления размещением изображений. В любом случае, это комплексный подход к работе с сайтом.
Существует очень много директив для управления прокси-соединениями. Все они описаны в соответствующей документации nginx. Я не большой специалист по настройке nginx. В основном использую свои готовые конфиги, зачастую даже не вникая в суть, если получается сразу решить задачу. Подсматриваю что-то у других, записываю к себе, стараюсь разобраться.
Особое внимание следует уделить директивам кэширования proxy_cache, если в этом есть потребность. Можно существенно увеличить отклик веб сайта, если подходящим образом настроить отдачу кэша. Но это тонкий момент и нужно настраивать в каждом конкретном случае отдельно. Готовых рецептов тут не бывает.
Более подробно о комплексной настройке nginx читайте в отдельной большой статье с моими личными примерами.
Часто задаваемые вопросы по теме статьи (FAQ)
При использовании https нужно ли в режиме proxy_pass настраивать https и на бэкенде?
В общем случае не обязательно. Но есть некоторый софт, который не может корректно работать в таком режиме. Он не понимает, как корректно обрабатывать такую ситуацию. Может создавать ссылки вида http://site.ru:443, которые будут являться ошибочными. В таком случае необходимо настроить обмен между nginx и бэк сервером тоже https соединение.
Как наиболее простым спосбом передавать сертификат let’s encrypt с nginx на backend? Ведь обновдление и генерация сертификата происходят только на nginx.
Я в таких случаях использую 2 способа, в зависимости от ситуации. Самый простой – на сервере с nginx настроить nfs сервер, а на бэкенде подмонтировать по nfs к себе директорию /etc/letsencrypt и спокойно использовать сертификаты, как-будто они лежат локально. Второй вариант – использовать простой bash скрипт для копирования сертификатов к себе на сервер по scp. В обоих случаях надо не забыть на бэкенде перезапускать службы, которые использую сертификат, после его обновления.
При обращении с бэкенда по адресу сайта, запрос уходит на nginx proxy, так как в dns прописан его ip адрес. Из-за этого иногда не работают встроенные скрипты или проверки некоторых движков, так как они ожидают, что запрос вернется с того же сервера, с которого он был сделан. Но на практике он уходит на proxy и приходит оттуда.
В такой ситуации может помочь правка файла /etc/hosts на самом бэкенде. Сделайте там статическую запись с именем сайта и локальным ip адресом. Тогда запросы с самого сервера будут приходить на него же локально, а не на nginx proxy.
Что лучше использовать для проксирования http запросов nginx или haproxy?
Однозначного ответа на этот вопрос не может быть. В чем-то это схожий софт, но есть и существенные отличия. В общем случае, описанном в статье, принципиальной разницы нет. У haproxy в бесплатной версии есть функционал, который присутсвует только в nginx plus. Так что нужно смотреть по конкретным задачам и решать, какой продукт подойдет лучше.
Добавляет ли nginx в режиме proxy_pass дополнительные сетевые задержки?
Конечно да. Но на практике они очень малы, если nginx и целевой сервер находятся в общей локальной сети. С учетом задержек при передачи пакетов по сети интернет, этими локальными задержками можно пренебречь. Они будут ничтожно малы. Если же вы проксируете запросы через интернет, то нужно внимательно смотреть величину задержек между nginx и бэкендом. Желательно их делать минимальными, располагая сервера поближе друг к другу.
Свежие комментарии