Настройка Nginx Reverse Proxy #
Nginx часто используется как reverse proxy — промежуточный веб-сервер, который принимает HTTP/HTTPS-запросы от клиентов и перенаправляет их на внутренние приложения. При этом все приложения могут находиться на разных серверах или слушать разные локальные порты, а снаружи будут доступны через обычные DNS-имена:
https://api.example.com
https://app.example.com
https://monitoring.example.com
Reverse Proxy и Redirect #
Перед настройкой nginx важно понимать разницу между proxy_pass и обычным HTTP-редиректом. При использовании proxy_pass клиент обращается к nginx, nginx сам отправляет запрос на backend-сервер и возвращает клиенту его ответ, при этом адрес в браузере не меняется: пользователь открывает https://api.example.com, nginx может получить данные с http://10.40.1.20:5000, но клиент об этом backend-адресе не знает.
location / {
proxy_pass http://10.40.1.20:5000;
}
Схематично это выглядит так:
Client -> https://api.example.com -> Nginx -> http://10.40.1.20:5000
Client <- response <- Nginx <- response
Redirect работает иначе: nginx не обращается к другому серверу вместо клиента, а возвращает клиенту ответ 301 или 302 с новым адресом, после чего уже браузер самостоятельно выполняет новый запрос и адрес в адресной строке меняется.
return 301 https://api.example.com$request_uri;
Например:
Client -> http://api.example.com
|
v
301 Redirect
|
v
Client -> https://api.example.com
Поэтому типичная конфигурация reverse proxy использует оба механизма одновременно: HTTP на 80 порту перенаправляется через 301 на HTTPS, а HTTPS-запрос на 443 порту уже проксируется через proxy_pass во внутреннее приложение.
Установка Nginx #
На Ubuntu установка выполняется через apt:
sudo apt update
sudo apt install -y nginx
Запускаем nginx и добавляем его в автозагрузку:
sudo systemctl enable --now nginx
Проверяем состояние:
systemctl status nginx
Версию nginx можно посмотреть командой:
nginx -v
Структура конфигурации #
Основной конфигурационный файл nginx:
/etc/nginx/nginx.conf
На Ubuntu внутри блока http {} обычно подключаются дополнительные конфигурационные файлы:
include /etc/nginx/conf.d/*.conf;
Благодаря этому нет необходимости хранить конфигурацию всех сайтов внутри одного большого nginx.conf.
Для каждого сайта удобно использовать отдельный файл:
/etc/nginx/conf.d/
├── 00-default.conf
├── api.example.com.conf
├── app.example.com.conf
├── monitoring.example.com.conf
└── stage.example.com.conf
Так конфигурацию намного проще читать, изменять и переносить между серверами.
Отключение стандартного сайта Ubuntu #
После установки nginx на Ubuntu обычно существует стандартная конфигурация:
/etc/nginx/sites-enabled/default
Если все виртуальные сайты будут храниться в /etc/nginx/conf.d/, стандартный сайт можно удалить:
sudo rm /etc/nginx/sites-enabled/default
После изменения конфигурации необходимо выполнить проверку:
sudo nginx -t
Если ошибок нет:
sudo systemctl reload nginx
Общие параметры SSL #
Чтобы не повторять одинаковые TLS-настройки в каждом виртуальном сайте, их удобно вынести в отдельный файл:
/etc/nginx/snippets/ssl-common.conf
Создадим каталог:
sudo mkdir -p /etc/nginx/snippets
Создадим файл:
sudo nano /etc/nginx/snippets/ssl-common.conf
Пример:
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
Теперь в каждом сайте достаточно написать:
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
include /etc/nginx/snippets/ssl-common.conf;
Создание default server #
Если пользователь обращается к nginx непосредственно по IP-адресу:
https://52.50.21.40/
nginx всё равно должен выбрать какой-либо server {}.
Если явно не указать default_server, nginx использует первый подходящий виртуальный хост для данного адреса и порта.
Например:
server {
listen 443 ssl http2;
server_name app.example.com;
}
может случайно стать виртуальным сервером по умолчанию.
В результате запрос по IP может попасть в настоящий сайт.
Чтобы этого не происходило, создадим отдельную конфигурацию:
/etc/nginx/conf.d/00-default.conf
Создать её можно одной командой:
sudo tee /etc/nginx/conf.d/00-default.conf > /dev/null <<'EOF'
###### Default WebSites ######
server {
listen 80 default_server;
server_name _;
return 444;
}
server {
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
include /etc/nginx/snippets/ssl-common.conf;
return 444;
}
EOF
После этого:
sudo nginx -t
sudo systemctl reload nginx
Теперь запросы к неизвестным доменам или непосредственно по IP не будут попадать в рабочие виртуальные сайты.
Что означает return 444 #
Код:
return 444;
является специальным поведением nginx.
Nginx закрывает TCP-соединение без отправки нормального HTTP-ответа.
Например:
curl http://52.50.21.40/
может вернуть:
Empty reply from server
В браузере обычно отображается ошибка вроде:
ERR_EMPTY_RESPONSE
или сообщение о том, что соединение было сброшено.
Это удобно для default virtual host, поскольку nginx вообще не раскрывает содержимое какого-либо сайта.
Как nginx выбирает виртуальный сайт #
Рассмотрим:
listen 443 ssl http2;
server_name app.example.com;
Первая строка:
listen 443 ssl http2;
означает:
- слушать TCP-порт
443; - использовать TLS;
- разрешить HTTP/2.
Вторая строка:
server_name app.example.com;
определяет имя виртуального сайта.
Например клиент открывает:
https://app.example.com/
DNS преобразует имя в IP-адрес сервера:
app.example.com
|
v
52.50.21.40
После чего клиент подключается:
52.50.21.40:443
На одном IP и одном порту могут находиться десятки сайтов:
server {
listen 443 ssl http2;
server_name app.example.com;
}
server {
listen 443 ssl http2;
server_name api.example.com;
}
server {
listen 443 ssl http2;
server_name monitoring.example.com;
}
Nginx выбирает необходимый виртуальный сервер по имени сайта.
Если подходящего имени нет, используется default_server.
Хранение сертификатов #
Для сертификатов можно создать отдельный каталог:
sudo mkdir -p /etc/nginx/ssl
sudo chmod 700 /etc/nginx/ssl
Например:
/etc/nginx/ssl/example.com.crt
/etc/nginx/ssl/example.com.key
Закрытый ключ желательно ограничить правами:
sudo chown root:root /etc/nginx/ssl/*
sudo chmod 600 /etc/nginx/ssl/*.key
Создание Reverse Proxy #
Допустим, приложение работает на внутреннем сервере:
10.40.1.20:5000
и должно быть доступно как:
https://api.example.com
Создаём:
sudo nano /etc/nginx/conf.d/api.example.com.conf
Конфигурация:
###### WebSite api.example.com ######
server {
listen 80;
server_name api.example.com;
access_log /var/log/nginx/api.example.com-access.log;
error_log /var/log/nginx/api.example.com-error.log;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
access_log /var/log/nginx/api.example.com-ssl-access.log;
error_log /var/log/nginx/api.example.com-ssl-error.log;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
include /etc/nginx/snippets/ssl-common.conf;
location / {
proxy_pass http://10.40.1.20:5000;
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-запрос:
http://api.example.com
будет перенаправлен:
https://api.example.com
А HTTPS-запрос nginx отправит backend-серверу:
10.40.1.20:5000
proxy_pass #
Главная директива reverse proxy:
proxy_pass http://10.40.1.20:5000;
Она определяет, куда nginx должен отправить запрос.
Backend может находиться локально:
proxy_pass http://localhost:5000;
на другом сервере:
proxy_pass http://10.40.1.20:8080;
или использовать HTTPS:
proxy_pass https://10.40.1.30:443;
Передача HTTP-заголовков #
Обычно вместе с proxy_pass необходимо передавать несколько заголовков.
proxy_set_header Host $host;
Передаёт backend-серверу исходное имя сайта.
Например:
Host: api.example.com
Директива:
proxy_set_header X-Real-IP $remote_addr;
передаёт реальный IP клиента.
Например:
X-Real-IP: 203.0.113.25
Директива:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
передаёт цепочку IP-адресов proxy-серверов и клиента.
Это необходимо, чтобы backend-приложение могло определить реальный адрес пользователя.
Проверка конфигурации #
Перед каждым применением изменений необходимо выполнять:
nginx -t
При корректной конфигурации:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
После этого применяем изменения:
systemctl reload nginx
reload предпочтительнее restart, поскольку nginx перечитывает конфигурацию без жёсткого разрыва текущих соединений.
Автоматическое создание виртуальных сайтов #
Чтобы каждый раз вручную не создавать однотипные конфигурационные файлы, можно написать небольшую утилиту:
/usr/local/sbin/nginx-add-site
Она будет принимать два параметра:
DOMAIN
BACKEND
Например:
nginx-add-site api.example.com http://10.40.1.20:5000
Скрипт:
#!/bin/bash
set -e
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <domain> <backend>"
echo
echo "Examples:"
echo " $0 app.example.com http://localhost:8080"
echo " $0 api.example.com https://10.40.1.20:8443"
exit 1
fi
DOMAIN="$1"
BACKEND="$2"
CONF_DIR="/etc/nginx/conf.d"
CONF_FILE="${CONF_DIR}/${DOMAIN}.conf"
# ----------------------------
# Validate domain
# ----------------------------
if ! [[ "$DOMAIN" =~ ^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$ ]]; then
echo "ERROR: invalid domain:"
echo " $DOMAIN"
exit 1
fi
# ----------------------------
# Validate backend
# ----------------------------
if ! [[ "$BACKEND" =~ ^https?:// ]]; then
echo "ERROR: backend must start with http:// or https://"
echo
echo "Examples:"
echo " http://localhost:8080"
echo " https://10.40.1.20:8443"
exit 1
fi
# ----------------------------
# Select SSL certificate
# ----------------------------
case "$DOMAIN" in
*.example.com)
SSL_CERT="/etc/nginx/ssl/example.com.crt"
SSL_KEY="/etc/nginx/ssl/example.com.key"
;;
*.example.net)
SSL_CERT="/etc/nginx/ssl/example.net.crt"
SSL_KEY="/etc/nginx/ssl/example.net.key"
;;
*)
echo "ERROR: no certificate mapping for:"
echo " $DOMAIN"
exit 1
;;
esac
# ----------------------------
# Check files
# ----------------------------
if [ -f "$CONF_FILE" ]; then
echo "ERROR: nginx config already exists:"
echo " $CONF_FILE"
exit 1
fi
if [ ! -f "$SSL_CERT" ]; then
echo "ERROR: certificate not found:"
echo " $SSL_CERT"
exit 1
fi
if [ ! -f "$SSL_KEY" ]; then
echo "ERROR: certificate key not found:"
echo " $SSL_KEY"
exit 1
fi
# ----------------------------
# Create nginx config
# ----------------------------
cat > "$CONF_FILE" <<EOF
###### WebSite ${DOMAIN} ######
server {
listen 80;
server_name ${DOMAIN};
access_log /var/log/nginx/${DOMAIN}-access.log;
error_log /var/log/nginx/${DOMAIN}-error.log;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name ${DOMAIN};
access_log /var/log/nginx/${DOMAIN}-ssl-access.log;
error_log /var/log/nginx/${DOMAIN}-ssl-error.log;
ssl_certificate ${SSL_CERT};
ssl_certificate_key ${SSL_KEY};
include /etc/nginx/snippets/ssl-common.conf;
location / {
proxy_pass ${BACKEND};
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOF
echo
echo "Created:"
echo " $CONF_FILE"
echo
echo "Domain:"
echo " $DOMAIN"
echo
echo "Backend:"
echo " $BACKEND"
echo
echo "Certificate:"
echo " $SSL_CERT"
# ----------------------------
# Test nginx
# ----------------------------
echo
echo "Checking nginx configuration..."
if nginx -t; then
echo
echo "Configuration OK."
echo "Reloading nginx..."
systemctl reload nginx
echo
echo "Site added successfully."
else
echo
echo "ERROR: nginx configuration test failed."
echo "Rolling back..."
rm -f "$CONF_FILE"
echo
echo "Removed:"
echo " $CONF_FILE"
nginx -t
exit 1
fi
Устанавливаем скрипт:
sudo mv nginx-add-site /usr/local/sbin/nginx-add-site
Выставляем права:
sudo chown root:root /usr/local/sbin/nginx-add-site
sudo chmod 755 /usr/local/sbin/nginx-add-site
Теперь новый reverse proxy можно создать одной командой:
sudo nginx-add-site api.example.com http://10.40.1.20:5000
Скрипт:
- проверит имя домена;
- проверит формат backend;
- определит сертификат;
- проверит наличие сертификата и ключа;
- создаст
/etc/nginx/conf.d/api.example.com.conf; - выполнит
nginx -t; - при успешной проверке выполнит
systemctl reload nginx; - при ошибке удалит созданный конфигурационный файл.
Просмотр всей конфигурации #
Команда:
nginx -T
проверяет конфигурацию аналогично nginx -t, но дополнительно выводит всю итоговую конфигурацию nginx вместе со всеми include.
Это особенно удобно при использовании большого количества отдельных файлов.
Просмотр всех виртуальных сайтов #
Чтобы быстро увидеть все server_name:
nginx -T 2>/dev/null | grep -n "server_name"
Чтобы посмотреть используемые порты:
nginx -T 2>/dev/null | grep -n "listen"
Для reverse proxy удобно сразу вывести основные параметры:
nginx -T 2>/dev/null | grep -n -E 'server_name|listen|proxy_pass'
Получается своего рода карта nginx:
listen 443 ssl http2;
server_name api.example.com;
proxy_pass http://10.40.1.20:5000;
listen 443 ssl http2;
server_name app.example.com;
proxy_pass http://10.40.1.30:8080;
Проверка Reverse Proxy через curl #
Проверить HTTP:
curl -I http://api.example.com/
При включённом редиректе ожидаем:
HTTP/1.1 301 Moved Permanently
Location: https://api.example.com/
HTTPS:
curl -I https://api.example.com/
Для подробной диагностики:
curl -Iv https://api.example.com/
Если сертификат временно не доверенный:
curl -kIv https://api.example.com/
Логи #
Основные логи nginx:
/var/log/nginx/access.log
/var/log/nginx/error.log
Но для каждого сайта лучше использовать отдельные файлы:
access_log /var/log/nginx/api.example.com-access.log;
error_log /var/log/nginx/api.example.com-error.log;
Просмотр в реальном времени:
tail -f /var/log/nginx/api.example.com-access.log
Ошибки:
tail -f /var/log/nginx/api.example.com-error.log
Можно одновременно смотреть несколько файлов:
tail -f /var/log/nginx/*error.log
Итоговая структура #
В результате конфигурация сервера может выглядеть следующим образом:
/etc/nginx/
├── nginx.conf
├── conf.d/
│ ├── 00-default.conf
│ ├── api.example.com.conf
│ ├── app.example.com.conf
│ └── monitoring.example.com.conf
├── snippets/
│ └── ssl-common.conf
└── ssl/
├── example.com.crt
└── example.com.key
├── example.net.crt
└── example.net.key
А добавление нового сайта сводится к одной команде:
sudo nginx-add-site api.example.com http://10.40.1.20:5000
После чего скрипт автоматически создаёт virtual host, проверяет конфигурацию и выполняет безопасный reload nginx.
Таким образом nginx становится единой точкой входа для HTTP/HTTPS-трафика, а внутренние приложения можно размещать на отдельных серверах и портах, не публикуя их напрямую в интернет.