Базовый ультимативный гайд по Nginx
Published: 2023-12-30 17:24:34

Ниже — две части.
1. База — её можно быстро пролистать.
2. Боевая имплементация — там то, что мы хотим на практике.

> Важно учесть, что настройка Nginx — не только для "наших" задач.
> Все наши фреймворки, их middleware и т.д. — тоже рассчитывают на достоверные заголовки. Для корректной работы их `autoban` и т.д.

## База. Зачем нужен Nginx. Логика `X-Forwarded-For`.

Тестировать будем на starlette.

Создадим простой `main.py`, который просто рисует http-мету:
```python
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route

async def home(request):
    return HTMLResponse(f"""<pre>
        url: {str(request.url)}
        client:
            host: {request.client.host}
            port: {request.client.port}
        headers:
            {('<br>' + '&nbsp' * 12).join(k + ': ' + v for k, v in request.headers.items())}
    </pre>""")

app = Starlette(debug=True, routes=[Route('/', home),])
```

Его можно запустить на сервере:
```
uvicorn main:app --host=0.0.0.0 --port=2100
```

И, если DNS настроены:
1. Его уже можно открыть в браузере по http://myproject.org:2100
2. Он уже будет знать:
	- IP-пользователя в `request.client.host` вида 'xxx.xxx.xxx.xxx'.
	- Host-Cервера в `request.headers.host` вида 'myproject.org:2100'.
3. Даже ssl/https можно прикрутить.

И если обращаться к серверу прям так — http://myproject.org:2100 — без прокси, без VPN-ов — как минимум с заголовками — всё будет нормально.

Но:
1. Возможно — нам бы хотелось иметь возможность как-то знать о прокси/VPN-ах.
2. Возможно — нам также хотелось бы — мапить/перемапливать урлы (чтобы по 'myproject.org/api/' открывалось 'api.org/take/some/api/dude/'); эффективней обслуживать статику (не приложением); контролировать — таймауты, размеры файловых аплоудов, размеры текстовых аплоудов; иметь возможность показать пользователю хоть что-то, пока сервер лежит/перезапускается; клёвое логирование и т.д. и т.п.

А главное что нам хотелось бы — делать всё это через одну привычную технологию, на чём бы мы не писали наше приложение.

### Nginx

Сэмулируем проксирование самих себя (может это мы сами для наших нужд):
```nginx
server {
    listen 2200;
    location / {
        proxy_pass http://myproject.org:2100;
	}
}
```

Открываем браузер по `http://myproject.org:2200`.
В `request.client.host` лежит уже не IP-Пользователя, а IP-Cервера, который к нам обратился.
Скажем сразу — если доступа к этому конфигу у нас нет — многого мы не сделаем.

Но, можем отловить "чуть более верхнего инициатора запроса" добавив "подконтрольный" нам конфиг:
```nginx
server {
    listen 2100;
    location / {
        proxy_pass http://myproject.org:2000;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
```

Запускаемся (но уже с портом 2000):
```
uvicorn main:app --host=0.0.0.0 --port=2000
```

Открываем браузер также по `http://myproject.org:2200`.
Теперь наш сервис дополнительно отображает нам некий `request.headers['x-real-ip']` с IP отличающимся от `request.client.host`.
И если в `request.client.host` у нас IP-Сервера, то в `request.headers['x-real-ip']` похоже IP-Роутера-Сервера. (Но это не важно).

А вот если бы тот первый конфиг был нам подконтролен (или просто настроен хорошо), то мы могли бы в обоих случаях добавить:

```nginx
server {
    listen 2200;
    location / {
        proxy_pass http://myproject.org:2100;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # +
        proxy_set_header Host $http_host;  # +
    }
}

server {
    listen 2100;
    location / {
        proxy_pass http://myproject.org:2000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # +
        proxy_set_header Host $http_host;  # +
    }
}
```

И теперь, в `http://myproject.org:2200` мы получим:
1. В `request.headers.host` лежит всё как надо — 'myproject.com:2200'.
2. В `request.client.host` всё также лежит IP-Сервера.
3. В `request.headers['x-real-ip']` лежит всё тот же IP-Роутера-Сервера.
4. Но, появился ещё `request.headers['x-forwarded-for']`, и это — цепочка IP-шников! Потерянный IP-Пользователя и IP-Роутера-Сервера.

Т.е., самый достоверный IP-пользователя — это всегда — `request.headers['x-forwarded-for'][0]`.

## Боевая имплементация

Нам конечно не нужны такие двойные конфиги.
Просто только так можно было понять 'X-Forwarded-For'.
Что и в какой степени достоверно.

И вот перед нами просто "голый" конфиг:
```nginx
server {
    listen 2200;
    location / {
        proxy_pass http://headhall.com:2000;
    }
}
```

По `http://myproject.org:2200` мы получим:
Который показывает:
1. В `request.headers.host` лежит 'myproject.org:2000', хотя браузер открыт с ':2200'.
2. В `request.client.host` — IP-Сервера, а не IP-Пользователя.
3. `request.headers['x-real-ip']` вообще нет.
3. `request.headers['x-forwarded-for']` также нет.

Приводим конфиг в боевое состояние:
```nginx
server {
    listen 2200;
    location / {
    	# Чтобы показывал :2200
    	proxy_set_header Host $http_host;

    	# Пытаемся получить IP-Пользователя (и получим, если без прокси)
    	proxy_set_header X-Real-IP $remote_addr;

		# Ещё пытаемся получить IP-Пользователя (и получим, если прокси наше или "нормальное")
    	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_pass http://headhall.com:2000;
    }
}
```

(Вместо `$http_host` конечно можно — и `$host` (без порта), и `$server_name` (если хочется) и т.д. Просто `$http_host` самый полный и универсальный.).
(К слову, все эти `proxy_*` можно указать и на уровне `server`).

Закрепим, что мы получим в `http://myproject.org:2200`:
1. В `request.headers.host` лежит гарантированный 'myproject.org:2200'.
2. В `request.client.host` — IP-Сервера, а не IP-Пользователя, как можно было ожидать — всегда НЕ надёжный ориентир.
3. `request.headers['x-real-ip']` — IP-Пользователя, но, всё же — тоже НЕ надёжный ориентир.
4. `request.headers['x-forwarded-for'][0]` — IP-Пользователя, как — наиболее надёжный ориентир.

А вот теперь можно уже к — таймаутам, лимиту аплоудов и т.д.

В помощь по всему что выше:
Alphabetical index of variables: https://nginx.org/en/docs/varindex.html
Злой, но золотой комментарий на SO: https://stackoverflow.com/a/72586833/4117781

Удачи, авантюрист!