Шаблоны конфигурации в репозитории написаны под SWAG, но библиотеке всё равно, кто стоит перед ней. Важно только, чтобы фронтенд умел две вещи: отдавать статику из тома и говорить с php-fpm по FastCGI. Эта статья — о том, как это сделать с тремя другими популярными reverse proxy.

Что нужно библиотеке от фронтенда

  1. Статика из тома. Контейнер библиотеки при старте выкладывает содержимое application/public (JS-читалки, CSS, картинки, шрифты) в именованный том flibusta_public_files. Веб-сервер монтирует этот том и отдаёт файлы сам; PHP-файлы в томе намеренно пустые — они там только для того, чтобы try_files находил путь.

  2. FastCGI к flibusta-fpm:9000 для всего, что оканчивается на .php и для всего, что не найдено как файл (переписывается на index.php). SCRIPT_FILENAME должен указывать на путь внутри контейнера библиотеки: /application/public/<имя>.php.

  3. Параметры FastCGI, от которых зависит поведение приложения:

    Параметр Зачем
    REMOTE_ADDR адрес клиента: FLIBUSTA_TRUSTED_NET, лог попыток входа, блокировки, fail2ban
    HTTP_X_FORWARDED_PROTO (или HTTPS) определение HTTPS для административных разделов
    HTTP_HOST, REQUEST_URI построение ссылок, адрес OPDS в «Справке»
    HTTP_USER_AGENT привязка сессии к браузеру
    HTTP_AUTHORIZATION, PHP_AUTH_USER HTTP Basic для OPDS и прямых ссылок на файлы
  4. Заголовок Content-Security-Policy: worker-src * blob: на ответах PHP — без него pdf.js не сможет запустить web worker и pdf-книги не откроются в браузере.

  5. Лимит размера тела запроса не меньше 500 МБ, если планируете загружать книги через «Добавить» (client_max_body_size в nginx и аналоги).

  6. HTTPS — административные разделы без него не работают.

Всё это есть в шаблонах flibusta.subdomain.conf и flibusta.subfolder.conf в docs_and_configs/external_services_config/; их стоит держать перед глазами как эталон.

Универсальный путь: nginx-сайдкар

Если ваш reverse proxy не умеет FastCGI (Traefik) или умеет, но неудобно (Nginx Proxy Manager), самое надёжное — поставить рядом с библиотекой маленький контейнер nginx:alpine, который делает всё из списка выше, и проксировать на него обычным HTTP. В репозитории есть готовый конфиг для такого сервера — phpdocker/nginx/nginx.conf (тот самый, что используется в устаревшей all-in-one конфигурации). Его нужно чуть доработать, потому что теперь перед ним стоит ещё один прокси:

server {
    listen 80;
    client_max_body_size 508M;

    # адрес клиента и схема приходят от внешнего прокси
    set_real_ip_from 172.16.0.0/12;      # подсети Docker, где живёт внешний прокси
    real_ip_header   X-Forwarded-For;
    real_ip_recursive on;

    root  /application/public;
    index index.php;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        add_header 'Content-Security-Policy' 'worker-src * blob:';
        include fastcgi_params;
        fastcgi_pass  flibusta-fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;

        fastcgi_param REMOTE_ADDR            $remote_addr;
        fastcgi_param REQUEST_URI            $request_uri;
        fastcgi_param HTTP_HOST              $http_host;
        fastcgi_param HTTP_X_REAL_IP         $remote_addr;
        fastcgi_param HTTP_X_FORWARDED_FOR   $proxy_add_x_forwarded_for;
        fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;   # не $scheme!
        fastcgi_param HTTP_USER_AGENT        $http_user_agent;
        fastcgi_param PHP_AUTH_USER          $remote_user;
        fastcgi_param HTTP_AUTHORIZATION     $http_authorization;
    }
}

Два отличия от оригинала: блок real_ip, чтобы $remote_addr стал адресом настоящего клиента, а не внешнего прокси, и HTTP_X_FORWARDED_PROTO из заголовка, а не из $scheme (иначе библиотека решит, что соединение по HTTP, и закроет административные разделы). Подсеть в set_real_ip_from должна включать адрес внешнего прокси в Docker-сети.

Сервис в docker-compose библиотеки:

  flibusta-web:
    image: nginx:alpine
    container_name: flibusta-web
    restart: unless-stopped
    volumes:
      - flibusta_public_files:/application/public:ro
      - ./nginx-sidecar.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - flibusta_net       # к flibusta-fpm
      - proxy_net          # к внешнему reverse proxy

Наружу порт не публикуется; внешний прокси ходит на flibusta-web:80 по общей сети. Для subfolder-схемы (/flibusta) подмените location / и fastcgi_split_path_info по образцу flibusta.subfolder.conf.

Дальше — как подключить flibusta-web (или напрямую flibusta-fpm, где это возможно) к каждому из прокси.

Nginx Proxy Manager

NPM — это nginx с веб-панелью, так что FastCGI он умеет, но панель заточена под proxy_pass. Каждый «Proxy Host» генерирует свой location /, и попытка объявить ещё один location / во вкладке Advanced закончится ошибкой конфигурации. Практичных варианта два.

С сайдкаром (рекомендуется). Создайте Proxy Host: домен flibusta.example.org, схема http, Forward Host flibusta-web, порт 80, включите SSL с Let’s Encrypt и Force SSL. NPM сам передаст X-Forwarded-For и X-Forwarded-Proto. Контейнер NPM должен быть в той же Docker-сети, что и flibusta-web (proxy_net в примере выше). Во вкладке Advanced добавьте только лимит на загрузку:

client_max_body_size 508M;

Без сайдкара, subfolder-схема. Смонтируйте том статики в контейнер NPM (flibusta_public_files:/srv/flibusta:ro), подключите NPM к flibusta_net, создайте Proxy Host для основного домена (Forward Host — что угодно живое, он будет обслуживать /), и во вкладке Advanced вставьте содержимое flibusta.subfolder.conf — блок location ^~ /flibusta { … } с другим префиксом не конфликтует с location /. В библиотеке задайте FLIBUSTA_WEBROOT=/flibusta. Пути к логам в шаблоне (/config/log/nginx/…) замените на /data/logs/… или уберите.

Caddy

Caddy умеет FastCGI «из коробки» и сам получает сертификаты, поэтому сайдкар не нужен. Caddyfile для subdomain-схемы:

flibusta.example.org {
    root * /srv/flibusta          # том flibusta_public_files
    encode gzip

    request_body {
        max_size 512MB
    }

    header Content-Security-Policy "worker-src * blob:"

    php_fastcgi flibusta-fpm:9000 {
        # SCRIPT_FILENAME должен указывать на путь внутри контейнера библиотеки
        root /application/public
    }
    file_server
}

php_fastcgi сам делает try_files с откатом на index.php, передаёт REMOTE_ADDR с адресом клиента, ставит HTTPS=on при TLS и прокидывает заголовки запроса как HTTP_*, включая HTTP_AUTHORIZATION для OPDS. Поддиректива root внутри php_fastcgi переопределяет корень только для SCRIPT_FILENAME — именно то, что нужно, когда файлы у Caddy лежат в одном месте, а у php-fpm в другом.

Docker-compose для Caddy: том flibusta_public_files:/srv/flibusta:ro, сеть flibusta_net, порты 80 и 443, том для сертификатов (caddy_data).

Subfolder-схему в Caddy сделать можно (handle_path /flibusta/* плюс php_fastcgi с теми же параметрами), но try_files для вложенного пути требует аккуратности; если есть выбор — берите subdomain.

Конфигурация выше составлена по документации Caddy, а не снята с рабочей установки; после запуска проверьте три вещи: открывается ли pdf-книга (CSP), показывается ли в разделе «Пользователи» предупреждение о HTTP (HTTPS определяется корректно) и правильный ли адрес в «Активности пользователей» после входа (REMOTE_ADDR).

Traefik

Traefik не говорит по FastCGI вообще, поэтому сайдкар обязателен. Traefik подключается к нему через labels на сервисе flibusta-web:

  flibusta-web:
    image: nginx:alpine
    # ... тома и конфиг как в разделе про сайдкар
    networks:
      - flibusta_net
      - traefik_net
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik_net
      - traefik.http.routers.flibusta.rule=Host(`flibusta.example.org`)
      - traefik.http.routers.flibusta.entrypoints=websecure
      - traefik.http.routers.flibusta.tls.certresolver=letsencrypt
      - traefik.http.services.flibusta.loadbalancer.server.port=80

Traefik по умолчанию добавляет X-Forwarded-For и X-Forwarded-Proto, а set_real_ip_from в сайдкаре должен включать подсеть traefik_net. Если Traefik сам стоит за ещё одним прокси (Cloudflare), не забудьте forwardedHeaders.trustedIPs на entrypoint, иначе в X-Forwarded-For будет адрес Cloudflare.

Лимит на размер тела запроса Traefik не накладывает (есть middleware buffering, но по умолчанию он выключен), так что 500 МБ упрутся только в client_max_body_size сайдкара.

Как проверить, что всё подключено правильно

Один проход по чек-листу закрывает почти все типичные ошибки интеграции:

  1. Главная страница открывается, есть стили и картинки — статика из тома работает.
  2. Открывается pdf-книга — CSP на месте.
  3. В «Пользователи → Сессии» ваш IP — реальный, а не 172.x контейнера — REMOTE_ADDR передаётся.
  4. В «Пользователях» нет красного предупреждения об HTTP — схема определена верно.
  5. Читалка подключается к /opds с паролем — HTTP_AUTHORIZATION доходит.
  6. Загрузка книги на 100+ МБ в «Добавить» проходит — лимиты тела запроса подняты.

Если что-то из этого не так — сверьтесь с таблицей параметров в начале статьи и с шаблоном для SWAG.