19 апреля 2012 в 08:17

Nginx как Reverse Proxy для сайта, использующего SSL

Введение


Как настроить nginx в качестве frontend к apache и зачем это нужно — написано неоднократно, в том числе и на Хабре. Мой случай немного отличается от классического. Начиналось все как обычно, проект на apache, увеличение количества посетителей и, связанная с ним, недостаточность ресурсов сервера. Но проект использовал SSL для защиты обмена данными с клиентами. С чем я столкнулся и как решил проблемы я расскажу под катом.

Проблемы


Поскольку непосредственный прием запросов клиентов перешел на плечи nginx то и работу с SSL надо переносить на него же. Apache, который теперь висел но loopback 127.0.0.1:8080, в поддержке SSL не нуждался. Первая проблемка заключалась в том, что сертификат был от Thawte, а они требовали использования промежуточного сертификата. Nginx для работы с такими сертификатами не имеет специальной директивы. В конфиге apache было так:

<VirtualHost 1.2.3.4:443>
  ...
  SSLEngine on

  SSLCertificateFile /usr/local/ssl/www.blabla.ru/public.crt
  SSLCertificateKeyFile /usr/local/ssl/www.blabla.ru/private.key
  SSLCACertificateFile /usr/local/ssl/www.blabla.ru/intermediate.crt
  ...
</VirtualHost>

Как быть с SSLCACertificateFile в nginx? Оказалось надо просто «срастить» два файла, поместив их содержимое в один (публичный сертификат, следом промежуточный):

cd /usr/local/ssl/www.blabla.ru
cp public.crt public_concat.crt
cat intermediate.crt >> public_concat.crt

Все, теперь в nginx просто пишем:

    server {
        listen                  1.2.3.4:443;
        server_name             www.blabla.ru;

        ssl                     on;
        ssl_certificate         /usr/local/ssl/www.blabla.ru/public_concat.crt;
        ssl_certificate_key     /usr/local/ssl/www.blabla.ru/private.key;
        ...
    }

Редирект с незащищенного сайта на защищенный сложностей в настройке не вызвал:

    server {
        listen          1.2.3.4:80;
        server_name     www.blabla.ru blabla.ru;
        # rewrite ^(.*) https://www.blabla.ru$1 permanent;
        return 301 https://www.blabla.ru$request_uri;
    }

Сложности начались при работе ajax. Асинхронные запросы к серверу выполнялись с префиксом http:// что вызывало губительный для ajax редирект 301. Появилась необходимость передавать переменные окружения, в частности имя протокола, из nginx в apache, сделано это было так:
в nginx:

    server {
        ...
        location / {
            ...
            proxy_set_header            X-Forwarded-Proto $scheme;
            ...
        }
        ...
    }

Самое главное в apache надо в определении виртуального хоста прописать:

<VirtualHost 127.0.0.1:8080>
  ServerName www.blabla.ru
  ...
  SetEnvIf X-Forwarded-Proto https HTTPS=on
  ...
</VirtualHost>

Теперь все ajax запросы идут на https:// что и требовалось. Для этого трюка модуль apache setenvif_module должен быть установлен и включен в конфиге:

LoadModule setenvif_module libexec/apache22/mod_setenvif.so

Также стоит иметь ввиду что некоторые приложения могут сами определять свой путь по переменным окружения. Так например phpMyAdmin, так нужный разработчикам ресурса, в случае если у него в конфиге не указан полный путь будет формировать его сам с указанием порта. Так получалось у меня нечто вида:

https://www.blabla.ru:80/myadmin

Вот это самое :80 сильно все портило, phpMyAdmin не открывался с руганью что ошибка защиты SSL. Вылечилось, как указано выше, прямым указанием пути в конфиге phpMyAdmin-а:

$cfg['PmaAbsoluteUri'] = 'https://www.blabla.ru/myadmin';


Еще не стоит забывать о передаче реальных IP адресов посетителей в apache для сохранения имеющейся схемы ведения логов. Это расписано неоднократно, копайте в сторону модуля apache rpaf_module.

PS: Все делалось под FreeBSD 8.2, Apache 2.2.22_5, nginx-1.0.14,1. Полные тексты конфигов не выкладывал чтобы не раздувать заметку. По этой же причине на заострялся на передаче реальных IP клиентов и модуле rpaf_module. Если потребуется — выложу.
Игорь Щербин @ischerbin
карма
41,0
рейтинг 3,2
*nix админ, увлекающийся программированием
Самое читаемое Администрирование

Комментарии (12)

  • +3
    >проект использовал SSL для защиты обмена данными с клиентами
    >Асинхронные запросы к серверу выполнялись с префиксом http://

    По-моему у вас проблемы вовсе не в настройке nginx.
    • –1
      Apache в моем случае работает по http, без SSL. То что асинхронные запросы надо выполнять по https надо ему сообщить передачей информации и протоколе. (proxy_set_header X-Forwarded-Proto $scheme;). По крайней мере такие мероприятия проблему решили. Есть другие варианты?
      • 0
        То что асинхронные запросы надо выполнять по https, нужно сообщить в первую очередь браузеру. У вас же, насколько я понял, протокол жестко прописан в скриптах. Поэтому данные в запросе передаются все-таки незащищенными. Или я чего-то не понял?
        • 0
          Разработчиком проекта являюсь не я, но в процессе поиска решения я смотрел логи сервера и копался в исходниках. Вот как формируются у них адреса асинхронных запросов:

          $.post("<? echo $GLOBALS['mosConfig_live_site']; ?>/templates/region_select.php", 
          

          То есть проект сделан на Joomla, JS код встроен в тело выдаваемой страницы, при формировании этого кода используются вставки на php. Но главное что запрос идет по https, то есть шифруется если я не ошибаюсь.
  • +4
    Оказалось надо просто «срастить» два файла, дописав промежуточный сертификат в конец публичного


    Конец.
    • 0
      Смешно, согласен, поправил.
  • +2
    Это плохо:
    rewrite ^(.*) https://www.blabla.ru$1 permanent;
    

    должно быть так:
    return 301 https://www.blabla.ru$request_uri;
    
    • 0
      В документации написано «Можно возвращать следующие коды: 204, 400, 402 — 406, 408, 410, 411, 413, 416 и 500 — 504».
      Можете объяснить чем плоха
      rewrite ^(.*) https://www.blabla.ru$1 permanent;
      
      и в чем разница в результате?
      • 0
        Значительная часть документации находится в устаревшем состоянии. Работа над актуализацией ведется, но рук на всё не хватает.

        А плоха тем, что требует интерпретации регулярного выражения (пустая трата ресурсов в данном случае).
        • 0
          Спасибо за информацию, возьму на вооружение!
          В данном же случае запрос, приводящий к редиректу (80-й порт), происходит один раз (первый) в рамках сеанса работы пользователя с системой, остальные запросы уже идут на 443-й порт. Значит падением производительности на этот раз можно пренебречь.
          • +1
            В данном конкретном случае вы даете пример другим пользователям, и, как показывает опыт, он будет растиражирован копипастой на тысячи конфигов, а, кроме того, ещё попадет в несколько других туториалов.
            • +1
              Исправил.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.