Задача проксирования с целью балансировки нагрузки описана на разных ресурсах 100500 раз. Однако в каждом конкретном случае есть 100500 нюансов, и когда я стал разбираться со своей проблемой, прямого ответа найти так и не удалось. Пришлось потратить несколько часов. Возможно, описание моего случая кому-то поможет сэкономить время.

Возникла задача балансировки нагрузки между основным сервером и его зеркалом. Нужно было сделать так, чтобы запросы к API разделялись между основным сервером и зеркалом по принципу: запросы на чтение — на зеркало, запросы на запись — на основной. В наличии был один важный факт: все запросы на запись к API выполняются методами POST, PUT и DELETE, а запросы на чтение — методом GET. Поэтому было решено воспользоваться этим фактом.

Исходная инфраструктура: Ubuntu 16 + nginx + uwsgi, в целом одинаковая и на основном сервере, и на зеркале, с той лишь разницей, что БД на зеркале является репликой основной БД в режиме read-only.

Что в итоге получилось — в конфигах nginx с комментариями.

Конфиг nginx на основном сервере:

# Сокет, через который nginx общается с uwsgi-приложением
upstream flask_serv {
    server unix:/tmp/flask.sock;
}

# Адрес зеркала, на котором реплика БД в read-only
upstream read_api {
    server api.server.ru;
}

# Выбираем цель на основе метода запроса
map $request_method $upstream_location {
    GET         read_api;
    default     flask_serv;
}

server {
    listen 443 ssl;
    server_name server.ru;

    root /var/www/server;

    # Включаем запрос сертификата Let`s Encrypt (подробности опускаем)
    include acme;

    # Location для общих запросов (не к API). Они должны проксироваться через сокет вне зависимости от того, какой метод у запроса.
    location / {
        if ($http_referer !~* ^($|http://|https://) ){
             return 403;
        }
        include uwsgi_params;
        uwsgi_pass flask_serv;
        uwsgi_read_timeout 300;
    }

    location /static/ {
        root /var/www/server/application;
    }


    # Location для запросов к API. Распределяем запросы в зависимости от метода.
    location /api/ {
        include uwsgi_params;
        # Если запрос на запись - обращаемся к сокету
        if ($upstream_location = "flask_serv") {
            uwsgi_pass unix:/tmp/flask.sock;
        }

        # По дефолту nginx записывает в заголовок Host домен server_name, т.е. переменную $host
        # Это засада, и если не переписать его значением хоста, к которому идёт проксирование, 
        # nginx будет отправлять запрос на IP-адрес сервера api.server.ru, указывая в 
        # заголовке Host домен server.ru, и вы получите 502.
        proxy_set_header Host api.server.ru;
        # Укажем, что мы хотим пробросить IP адрес клиента на зеркало, чтобы в логах на зеркале 
        # увидеть не IP адрес server.ru, а IP адрес клиента. Это потребует некоторых манипуляций и в
        # конфиге nginx на зеркале.
        proxy_set_header X-Real-IP $remote_addr;
        # Если запрос на чтение - проксируем на зеркало
        if ($upstream_location = "read_api") {
            proxy_pass http://$upstream_location;
        }
    }

    ssl_certificate /etc/letsencrypt/live/server.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/server.ru/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/server.ru/chain.pem;

    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers  "RC4:HIGH:!aNULL:!MD5:!kEDH";
    add_header Strict-Transport-Security 'max-age=600';
}

Теперь внесём некоторые правки в конфиг на зеркале.

server {
    listen 80;
    server_name api.server.ru;
    ...

    # Укажем реальный IP основного сервера и заменим адрес проксирующего сервера на адрес клиента
    set_real_ip_from xxx.xxx.xxx.xxx;
    real_ip_header X-Real-IP;
    ...

}

Это всё.

Если у сообщества есть замечания по этому решению — приму с благодарностью!