Миграция на nginx с apache-2.4, или путь по граблям и success-story в финале.
Предыстория сводится к тому, что был почтовый сервер, на котором крутился apache-2.4 + PHP + кое-какие CGI-скрипты. При очередном апдейте системы возникло острое желание заменить индейца на nginx, плюс прикрутить к серверу rspamd и получить www-интерфейс к последнему.
В ходе этой работы я потоптался по некоторым граблям, методы борьбы с которыми я и опишу ниже.
Базовые настройки
Просто, чтобы иметь определённый фундамент для последующего описания, указываю «начальные» настройки системы.
- /etc/php/fpm-php7.0/fpm.d/mail.conf
[mail] listen = /run/php-fpm.mail.socket listen.owner = nginx listen.group = nginx listen.mode = 0660 user = nginx group = nginx pm = dynamic pm.max_children = 10 pm.start_servers = 2 pm.min_spare_servers = 2 pm.max_spare_servers = 10
- /etc/nginx/nginx.conf
user nginx nginx; worker_processes 2; error_log /var/log/nginx/error_log info; events { worker_connections 64; use epoll; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$gzip_ratio"'; client_header_timeout 10m; client_body_timeout 10m; send_timeout 10m; client_max_body_size 100m; connection_pool_size 256; client_header_buffer_size 1k; large_client_header_buffers 4 2k; request_pool_size 4k; proxy_buffers 8 64k; proxy_intercept_errors on; proxy_connect_timeout 1s; proxy_read_timeout 3s; proxy_send_timeout 3s; gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain; output_buffers 1 32k; postpone_output 1460; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 75 20; ignore_invalid_headers on; include /etc/nginx/sites/*.conf; }
- /etc/nginx/sites/mail.somedomain.ru.conf
server { server_name mx.somedomain.ru mail.somedomain.ru ; listen 80; rewrite ^ https://mail.somedomain.ru$request_uri? permanent; # enforce https } server { server_name mx.somedomain.ru mail.somedomain.ru ; listen 443 ssl http2; autoindex off; gzip off; client_max_body_size 15M; client_body_buffer_size 128k; index index.html index.htm index.php; access_log /var/log/nginx/mail.somedomain.ru-access.log; error_log /var/log/nginx/mail.somedomain.ru-error.log; set $www_root /var/www/mail.somedomain.ru/htdocs; root $www_root; ssl on; ssl_certificate /etc/ssl/wildcard/wildcard.somedomain.ru.pem; ssl_certificate_key /etc/ssl/wildcard/wildcard.somedomain.ru.key; # ssl_dhparam /etc/letsencrypt/live/mail.somedomain.ru/dhparam.pem; resolver 1.2.3.4; # ssl_stapling on; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SS ssl_prefer_server_ciphers on; location / { } location = /robots.txt { access_log off; log_not_found off; } location = /favicon.ico { access_log off; log_not_found off; } location ~ /\. { access_log off; log_not_found off; deny all; } location ~ ~$ { access_log off; log_not_found off; deny all; } location ~ \.(gif|png|ico|jpg)$ { expires 30d; } location ~ \.php$ { try_files $uri =404; include fastcgi_params; fastcgi_pass unix:/run/php-fpm.mail.socket; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_intercept_errors on; fastcgi_ignore_client_abort off; fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; } location ~ /(data|conf|bin|inc)/ { deny all; } location ~ /\.ht { deny all; } }
Интерфейс к rspamd
rspamd предоставляет www-интерфейс, который висит по адресу http://127.0.0.1:11334. С идейной стороны надо настроить nginx, чтобы он пробрасывал запросы на указанный адрес.
Настойка, которая предлагается на просторах официального сайта выглядит так:
location /rspamd/ { proxy_pass http://127.0.0.1:11334/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
Проблема в том, что логотип, расположенный по адресу /rspamd/img/rspamd_logo_navbar.png не отображается, и про него пишут 404. Творческое чтение мануала на nginx показывает, что этот эффект вызван секцией location ~ \.(gif|png|ico|jpg)$
, т.к.:
Естественное желание заменить location /rspamd/
на что-то в роде location ~ ^/rspamd/
приводит к ругани при рестарте:
mx ~ # /etc/init.d/nginx restart * Checking nginx' configuration ... nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/sites/mail.somedomain.ru.conf:44 nginx: configuration file /etc/nginx/nginx.conf test failed nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/sites/mail.somedomain.ru.conf:44 nginx: configuration file /etc/nginx/nginx.conf test failed * failed, please correct errors above [ !! ] * ERROR: nginx failed to stop
Следующей итерацией удаляем конечный слеш в директиве proxy_pass, что позволяет перезапустить nginx, но выдаёт 404ю ошибку при любом обращении к адресам /rspamd/… На этом месте я надолго застрял, пока не прочитал описание проблемы и способ решения:
- Если директива proxy_pass указана с URI, то при передаче запроса серверу часть нормализованного URI запроса, соответствующая location, заменяется на URI, указанный в директиве:
location /name/ { proxy_pass http://127.0.0.1/remote/; }
- Если директива proxy_pass указана без URI, то при обработке первоначального запроса на сервер передаётся URI запроса в том же виде, в каком его прислал клиент, а при обработке изменённого URI - нормализованный URI запроса целиком:
location /some/path/ { proxy_pass http://127.0.0.1; }
До версии 1.1.12, если proxy_pass указана без URI, в ряде случаев при изменении URI на сервер мог передаваться URI первоначального запроса вместо изменённого URI.
В ряде случаев часть URI запроса, подлежащую замене, выделить невозможно:
- Если location задан регулярным выражением.
В этом случае директиву следует указывать без URI.
- Если внутри проксируемого location с помощью директивы rewrite изменяется URI, и именно с этой конфигурацией будет обрабатываться запрос (break):
location /name/ { rewrite /name/([^/]+) /users?name=$1 break; proxy_pass http://127.0.0.1; }
В этом случае URI, указанный в директиве, игнорируется, и на сервер передаётся изменённый URI запроса целиком.
Итого, правильный конфиг будет таким:
location ~ ^/rspamd/ { rewrite /rspamd(.*) /$1 break; proxy_pass http://127.0.0.1:11334; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
CGI-скрипты
nginx не умеет работать с классическими CGI-скриптами, только FastCGI. Чтобы скрестить nginx с mailgraph ставим дополнительный софт:
emerge -avt www-misc/fcgiwrap emerge -avt www-servers/spawn-fcgi
Первая софтинка понимает FastCGI-протокол и умеет из под себя запускать классические CGI-скрипты. Вторая – запускатор первой.
cd /etc/init.d/ ln -s spawn-fcgi spawn-fcgi.mail cp /etc/conf.d/spawn-fcgi{,.mail} mcedit /etc/conf.d/spawn-fcgi.mail
Содержимое конфига:
- /etc/conf.d/spawn-fcgi.mail
FCGI_SOCKET=/run/fastcgi.mail.socket FCGI_ADDRESS= FCGI_PORT= FCGI_PROGRAM=/usr/sbin/fcgiwrap FCGI_CHILDREN=1 FCGI_CHROOT= FCGI_CHDIR= FCGI_USER=nginx FCGI_GROUP=nginx FCGI_EXTRA_OPTIONS=" -f " ALLOWED_ENV="PATH DOCUMENT_ROOT SCRIPT_NAME SCRIPT_FILENAME"
Теперь правим конфиг nginx'а и добавляем новый локейшен (уже правильная версия). spawn-fcgi при запуске потомков каждому из них создаёт отдельный сокет. Имя сокета создаётся на основе параметра FCGI_SOCKET, к которому дописывается суффикс «-N», где N – номер потомка. Такм образом, fastcgi_pass
должен сожержать значение unix:/run/fastcgi.mail.socket-1
location ~ \.cgi$ { root /var/www/mail.somedomain.ru; # error_log /var/log/nginx/mail.somedomain.ru-debug debug; include fastcgi_params; fastcgi_pass unix:/run/fastcgi.mail.socket-1 ; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param REMOTE_USER $remote_user; }