Traefik для самых маленьких.

Идейным вдохновителем стал наш уважаемый админ, видео которого про Netbird дало начало процессу. За первую конфигурацию Traefik благодарность stilliсho2011 aka ProHomelab.

Предистория

Сразу скажу, что у меня есть белый статический ip, но подключение устройств к домашней сети с помощью Netbird дает дополнительные возможности.

История началась в новогодние праздники когда я был неприятно удивлен, что не могу обновить свою коллекцию книг в зелёном магазине с помощью прекрасного приложения от hufrea. При этом смотреть видео на красно-белом видеохостинге стало даже проще с помощью короткого параметра “-o“ для тех кто понимает. Без видео я прожить могу, а вот без книг - нет. Это подтолкнуло к изысканиям на тему удаленного от пределов нашего богоспасаемого Отечества сервера. Для тестов был взят

Хостера намеренно не указываю, т.к. показала практика сервис у него мягко говоря плохой. Например, поставил я docker и Nginx Proxy Manager в нем. Пол дня работает нормально, а потом перестает проксировать мои сервисы. При этом в админке всё хорошо, да и зайдя по ssh видно что docker контейнер с NPM работает.

Короче, сервер настолько плохой, что это стало даже хорошо. И вот тут начинается самое интересное. У себя дома в docker на Synology я поднимаю self-hosted Netbird. И понимаю, что моего дубового reverse proxy от Synology недостаточно, он просто не умеет ничего кроме простого проксирования.

И тут на сцену выходит Traefik. Функциональный, гибкий и многообещающий. Но требующий аккуратного прописывания конфигурации. И тут во мне столкнулись лень и любопытство. Лень послала в/на deepseek. Любопытство задало ИИ вопрос: “А можно ли в конфигурации Traefik задавать переменные?“. Был получен положительный ответ, мои ладошки вспотели и всё завертелось. Была получена универсальная конфигурация под мои задачи. Смысл в том, что логическая часть остается неизменной, а сервисы задаются с помощью массива именованных значений. Сначала был реализован простой прокси, потом редирект и статика. Но самое главное, я получил простую авторизацию по ключу ?a=….

Зачем мне такая авторизация. У меня есть сервисы, например библиотека с книгами (copsfb и inpx-web), которые могут вызвать вопросы у правообладателей в нашем Отечестве. Т.е. мне нужна авторизация для RU сегмента и прямой вход для NoneRu сегмента. Таким образом в моей универсальной конфигурации появился плагин geoblock.

Возвращаемся к удаленному серверу и Netbird. С помощью Netbird подключаю удаленный сервер в мою домашнюю сеть 192.168.1.0/24. Т.к. docker и NPM на удаленном сервере можно считать нерабочими, я через файлы nginx проксирую свои доменные имена привязанные к удаленному серверу на 192.168.1.1:80 или :443 где у меня развернут Traefik. И уже с помощью своего домашнего Traefik я окончательно проксирую на бэкэнд сервисы. При этом я могу дополнительно навешивать авторизацию, геоблок, редирект и статику. И всё это, по факту, в одном месте. Т.е. от удаленного сервера требуется его наличие и простые конфиги nginx - всё. Переехать на другой сервер дело 2-х минут.

Возвращаемся к Traefik и уже готовой универсальной конфигурации. Напомню, каждый сервис можно задать строкой вида:

“device” “ds218+” “name” “traefik” “subservice” “dashboard” “domain” “domen1.synology.me” “ip” “stub” “port” 443 “pathPrefix” “/” “protocol” “https” “serviceType” “proxy” “access” “internal” “geoblock” false “protectedRU” false “protectedNoneRU” false “staticRoot” “stub” “redirectTarget” “stub” “certResolver” “le-http”

Гораздо лучше стандартного конфига Traefik, но тем не менее хочется лучше.

И тут на сцену выходит OnlyOffice (или Excel). И вышеуказанная строка попадает в умную таблицу, где все наглядно, с быстрой сортировкой по всему и вся, подсветкой через условное форматирование и получением готового конфига одним нажатием на кнопку мыши.

Как можно расширять функционал конфига. Скармливаем конфиг и инструкцию к нему ИИ. И просим его сделать, то что вам нужно. Строго следим, чтобы ИИ не уходил в частности и следовал критерию универсальности конфига. Или сами пишем логику с помощью Go-шаблонов (сразу оговорюсь, самостоятельно я не написал ни одной строчки кода).

Мой опыт взаимодействия с ИИ и его ответ на мой вопрос “Почему мы так долго не могли понять друг друга?“

Мы с вами прошли долгий путь отладки конфигурации Traefik. Ваш последний вопрос очень важен: “Почему мы так долго не могли понять друг друга?”. Давайте честно разберем причины, чтобы в будущем наши взаимодействия были эффективнее.

  1. Разные контексты и предположения
    Я как ИИ работаю с текстовыми описаниями и не имею доступа к вашей реальной системе. Вы же видите конкретные ошибки, логи, поведение сервисов. Часто я делал предположения, основанные на общих знаниях о Traefik и Go-шаблонах, но ваша среда (OPNsense, плагин, особенности версий) вносила свои коррективы. Например, когда я предлагал использовать {{/* … */}} для комментариев, я исходил из стандартного поведения Go-шаблонов, но в вашей среде это вызывало ошибки. Вы же видели конкретную ошибку unexpected “{” in operand, которая указала на проблему.

  2. Недостаточная конкретика в описании проблемы
    На начальных этапах вы часто писали “не работает”, “пропускает”, не прикладывая логов или точных сообщений об ошибках. Мне приходилось гадать, что именно не так. Постепенно вы стали присылать логи, скриншоты, что кардинально улучшило диагностику. Например, когда вы показали лог с request_X-Ipcountry":“AA”, стало ясно, что плагин не определяет страну. Без этого мы могли бы долго крутиться вокруг middleware.

  3. Сложность самого конфига
    Ваш конфиг очень мощный, но и очень сложный: шаблонизация, динамическая генерация роутеров, три разных geoblock-мидлваря, условная логика. В такой системе легко допустить несоответствие между намерением и реализацией. Я несколько раз ошибался в логике сборки publicMiddlewares, вы это замечали и указывали. Это нормально для сложных систем.

  4. Эволюция задачи
    Мы начинали с одной задачи, но по ходу выявлялись новые проблемы: то геоблокировка не работала для локальных IP, то авторизация давала доступ к неправильным путям, то дашборд не интегрировался. Каждое новое требование добавляло сложности и требовало пересмотра предыдущих решений.

  5. Мои ошибки в комментариях
    Да, это моя вина. Я несколько раз вставлял комментарии внутри активных частей шаблона, хотя вы неоднократно просили этого не делать. Это происходило потому, что я старался сделать конфиг самодокументированным, но забывал о жёстких ограничениях парсера в вашей среде. Теперь я усвоил: никаких комментариев внутри {{ … }}, кроме как в специально отведённых местах (например, в описании переменных до их объявления).

  6. Разница между “должно работать” и “работает в вашей системе”
    Я часто предлагал теоретически правильные решения, но они не учитывали особенности вашего плагина Traefik на OPNsense. Например, флаг (?i) в регулярках мог не поддерживаться, из-за чего пути в разном регистре не совпадали. Только когда вы предоставили конкретные примеры из логов, мы смогли адаптировать решение.

Как нам лучше взаимодействовать в будущем
Чтобы избежать недопонимания, предлагаю следующий формат:

Всегда прикладывайте конкретные артефакты: логи (хотя бы relevant строки), скриншоты UI, фрагменты конфига, которые вызывают проблемы.

Описывайте ожидаемое поведение и фактическое.

Указывайте контекст: что вы уже пробовали, какие изменения вносили.

Чётко формулируйте запрос: “исправь X, чтобы работало Y”, а не просто “не работает”.

Я буду строже следить за синтаксисом: никаких комментариев внутри активных частей шаблона, все пояснения выносить в начало или в отдельные блоки.

Если моё предложение не работает, сразу присылайте обратную связь с логами, чтобы мы могли скорректировать.

Вы очень терпеливо вели этот диалог, и в итоге мы пришли к рабочему конфигу. Спасибо за вашу настойчивость и детальные замечания. Это помогает мне учиться и улучшать качество ответов.

Управление Traefik на OPNsense: от сложных конфигураций к простой таблице OnlyOffice (краткое руководство)

Введение

При использовании Traefik в качестве обратного прокси на OPNsense с ростом числа сервисов поддержка конфигурационных файлов вручную становится трудоёмкой и чревата ошибками. Была разработана система, позволяющая управлять конфигурацией Traefik через таблицу OnlyOffice и автоматически генерировать готовые файлы traefik.toml (динамическая часть) и traefik.yml (статическая часть). В статье подробно описываются архитектура, возможности и процесс использования.

Архитектура

В рассматриваемой инфраструктуре:

  • OPNsense с плагином Traefik выполняет роль основного обратного прокси внутри локальной сети.
  • Удалённый сервер с реальным IP (например, 185.12.45.67) терминирует SSL-соединения для внешних доменов (например, *.remote3.fbx.one) и проксирует трафик на OPNsense по HTTP или HTTPS в зависимости от настроек конкретного поддомена.
  • Внутренние сервисы работают на хостах 192.168.1.55 и 192.168.1.5.

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

Возможности конфигурации

Шаблон динамической конфигурации поддерживает:

  • Проксирование (proxy) – обычная передача трафика на бэкенд.
  • Редиректы (redirect) – перенаправление на другой URL.
  • Статические файлы (static) – раздача файлов из локальной директории через плагин statiq.
  • Геоблокировку – полная блокировка запросов из России (флаг geoblock) или требование авторизации только для российских (protectedRU) / нероссийских (protectedNoneRU) IP.
  • Авторизацию по ключу – доступ по параметру ?a=ключ с установкой сессионной cookie.
  • Корневые домены – возможность обслуживать domain без поддомена (с помощью name=“/”).
  • Гибкое управление HTTPS – выбор между certResolver: “le-http” (получение сертификата Let’s Encrypt) и certResolver: “stub” (работа через HTTP).

В статической конфигурации настраиваются глобальные параметры: entryPoints, резолверы сертификатов, плагины, логирование.

Два способа управления конфигурацией

1. Ручное редактирование

Для небольших проектов или разового использования можно править файлы напрямую. Подробная инструкция по каждому параметру и примерам приведена в полной инструкции. Однако этот способ становится неудобным при десятках сервисов.

2. Использование OnlyOffice-таблицы (рекомендуемый способ)

Был создан файл OnlyOffice (аналог Excel), который автоматизирует генерацию конфигураций. В нём три листа:

  • Справочник – содержит все допустимые значения: ключи авторизации, базовые домены, устройства, протоколы, IP-адреса, типы доступа и т.д.
  • Шаблон – служебный лист, где хранятся шаблоны с плейсхолдерами. Редактируется только при изменении логики.
  • toml – основной рабочий лист. Здесь находится таблица subdomains, в которой каждая строка – отдельный сервис. Также здесь расположены три синие кнопки-стрелки для запуска макросов и две зелёные ячейки toml_final и yml_final для готовых текстов.

В файл встроены три макроса на JavaScript:

  • Summary – заполняет столбец summary в таблице subdomains формулами, преобразующими строки в словари.
  • CopyPlainText_toml – копирует очищенный текст динамической конфигурации в ячейку toml_final.
  • CopyPlainText_yml – аналогично для статической конфигурации.

Пошаговая инструкция по работе с таблицей

Шаг 1. Заполнение справочников (лист «Справочник»)

  1. В таблице keys перечислите все ключи для входа (например, curiosity, key1). Каждый с новой строки, пустых строк быть не должно.
  2. В таблице baseDomains укажите все базовые домены, например:
  3. В таблице device перечислите названия устройств (например, ds218+, Xpenology, xorek). Они используются только для информационных целей.
  4. Проверьте остальные справочники (protocol, certResolver, access, serviceType, staticRoot, ip, protected). Они уже содержат стандартные значения, но при необходимости можно добавить свои (например, новый путь для статики).

Шаг 2. Заполнение таблицы сервисов (лист «toml», таблица «subdomains»)

Для каждого сервиса заполните строку. Доступны выпадающие списки для большинства полей – они берутся из соответствующих справочников.

Пример записи для обычного поддомена с HTTPS:

(dict “device” “xorek” “name” “ad” “subservice” “ad” “domain” “remote3.fbx.one” “ip” “192.168.1.55” “port” 5001 “pathPrefix” “/” “protocol” “https” “serviceType” “proxy” “access” “public” “geoblock” false “protectedRU” false “protectedNoneRU” false “staticRoot” “stub” “redirectTarget” “stub” “certResolver” “le-http”)

Пример корневого домена с HTTP (stub):

(dict “device” “xorek” “name” “/” “subservice” “/” “domain” “remote3.fbx.one” “ip” “192.168.1.55” “port” 8087 “pathPrefix” “/” “protocol” “http” “serviceType” “proxy” “access” “public” “geoblock” false “protectedRU” false “protectedNoneRU” false “staticRoot” “stub” “redirectTarget” “stub” “certResolver” “stub”)

Важно: все поля должны быть заполнены. Для неиспользуемых полей ставьте “stub” (строки) или 0 (числа). Логические поля – строго true или false.

Шаг 3. Запуск макроса «Summary»

Нажмите первую синюю стрелку с надписью «Нажми для заполнения или обновления summary». Макрос обработает таблицу и заполнит столбец summary формулами. В ячейке над таблицей (строка 1) появится отчёт об успешном выполнении. Если возникли ошибки, проверьте правильность заполнения строк.

Шаг 4. Запуск макроса «CopyPlainText_toml»

Нажмите вторую синюю стрелку с надписью «Нажми для .toml». Макрос возьмёт текст из диапазона toml_buf, очистит его от служебных символов и поместит в ячейку toml_final (зелёная). Выше появится подсказка: «:white_check_mark: Текст в … Нажмите F2 → Ctrl+A → Ctrl+C».

Шаг 5. Запуск макроса «CopyPlainText_yml» (если нужна статика)

Третья стрелка «Нажми для .yml» – аналогично для статической конфигурации. Запускайте только при изменении статических параметров (email, плагины, entryPoints).

Шаг 6. Копирование готового текста

  1. Кликните на зелёную ячейку toml_final.
  2. Нажмите F2 (режим редактирования).
  3. Нажмите Ctrl+A (выделить всё).
  4. Нажмите Ctrl+C (копировать).
  5. Вставьте текст в файл /usr/local/etc/traefik.toml на OPNsense (через командную строку или веб-интерфейс Services → Traefik → General).
  6. Повторите для yml_final, если нужно.

Шаг 7. Перезапуск Traefik

Traefik может автоматически отслеживать изменения файлов (режим watch). Для надёжности перезапустите его вручную:

service traefik restart

Проверьте логи:

tail -f /var/log/traefik/traefik.log

Примеры для понимания

Пример 1: прокси с HTTPS и авторизацией для России

(dict “device” “ds218+” “name” “book” “subservice” “book” “domain” “domen1.synology.me” “ip” “192.168.1.55” “port” 8099 “pathPrefix” “/” “protocol” “http” “serviceType” “proxy” “access” “public” “geoblock” false “protectedRU” true “protectedNoneRU” false “staticRoot” “stub” “redirectTarget” “stub” “certResolver” “le-http”)

Для такого сервиса Traefik создаст три роутера:

  • /lo – выход (удаление cookie).
  • -auth – для запросов с cookie (авторизованные).
  • -public – для всех остальных, с middleware geoblock-ru-only, который потребует авторизацию только от российских IP.

Пример 2: редирект

(dict “device” “ds218+” “name” “glance” “subservice” “glance” “domain” “remote1.fbx.one” “ip” “192.168.1.55” “port” 8087 “pathPrefix” “/” “protocol” “http” “serviceType” “redirect” “access” “public” “geoblock” false “protectedRU” false “protectedNoneRU” false “staticRoot” “stub” “redirectTarget” “https://glance.domen1.synology.me” “certResolver” “le-http”)

Все запросы к glance.remote1.fbx.one будут перенаправлены на https://glance.domen1.synology.me.

Пример 3: статика

(dict “device” “ds218+” “name” “auth” “subservice” “auth” “domain” “domen1.synology.me” “ip” “192.168.1.55” “port” 9000 “pathPrefix” “/” “protocol” “http” “serviceType” “static” “access” “public” “geoblock” false “protectedRU” false “protectedNoneRU” false “staticRoot” “/srv/www” “redirectTarget” “stub” “certResolver” “le-http”)

Файлы из директории /srv/www будут доступны по адресу auth.domen1.synology.me.

Настройка удалённого сервера (NGINX)

Для доменов, которые должны работать через внешний сервер, настройте NGINX следующим образом.

Для stub-доменов (certResolver=“stub”) – прокси на HTTP (порт 80):

server {
server_name ad.remote3.fbx.one;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/ad.remote3.fbx.one/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ad.remote3.fbx.one/privkey.pem;

location / {
    proxy_pass http://192.168.1.1:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

}

Для доменов с le-http – прокси на HTTPS (порт 443), с отключением проверки сертификата (если используется самоподписанный):

server {
server_name ad.remote3.fbx.one;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/ad.remote3.fbx.one/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ad.remote3.fbx.one/privkey.pem;

location / {
    proxy_pass https://192.168.1.1:443;
    proxy_ssl_verify off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

}

Советы по отладке

  • Если макросы не запускаются, проверьте, разрешено ли в OnlyOffice выполнение макросов (настройки безопасности).
  • Если в столбце summary появляются #NAME? или #REF!, значит, какая-то ячейка заполнена некорректно (пусто, недопустимый символ).
  • Для диагностики Traefik используйте логи:
    tail -f /var/log/traefik/traefik.log
    tail -f /var/log/traefik/access.log

Заключение

Предложенное решение позволяет превратить поддержку конфигурации Traefik из рутинной задачи в быстрый и удобный процесс. Достаточно заполнить таблицу сервисов и нажать три кнопки – всё остальное сделают макросы. При этом сохраняется полный контроль над всеми параметрами, и можно легко добавлять новые сервисы. Файл OnlyOffice, подробные инструкции и примеры доступны для скачивания ниже. Такой подход экономит время и избавляет от ошибок ручного редактирования.

ПОЛНАЯ ИНСТРУКЦИЯ ПО ДИНАМИЧЕСКОЙ КОНФИГУРАЦИИ TRAEFIK (OPNsense)

ПОЛНАЯ ИНСТРУКЦИЯ ПО ДИНАМИЧЕСКОЙ КОНФИГУРАЦИИ TRAEFIK (OPNsense) для скачивания
Версия: с поддержкой proxy/static/redirect, корневых доменов и гибкой схемой HTTPS/HTTP

================================================================================

  1. ВВЕДЕНИЕ

    Данная конфигурация предназначена для Traefik, установленного как плагин на
    OPNsense. Она позволяет гибко управлять входящим трафиком: проксировать запросы
    к внутренним сервисам, настраивать редиректы, раздавать статические файлы,
    ограничивать доступ по геолокации и реализовывать простую авторизацию по ключу
    (?a=…). Все настройки производятся через редактирование трёх основных
    переменных в начале файла, что обеспечивает лёгкую поддержку и масштабирование.

Конфигурация поддерживает как обычные поддомены (например, ad.remote3.fbx.one),
так и корневые домены (remote3.fbx.one) с помощью специального значения name=“/”.

================================================================================

  1. ОСНОВНЫЕ ПЕРЕМЕННЫЕ (размещены в самом начале файла)

2.1. $authKeys — список ключей для входа.
Формат: список строк, например [“curiosity”, “key1”].
Любой из этих ключей, переданный в параметре ?a=…, даёт доступ ко всем
защищённым разделам (тем, где установлены флаги protectedRU или protectedNoneRU).
После первого использования клиенту устанавливается cookie auth_session
сроком на 24 часа, и ключ больше не требуется.

2.2. $baseDomains — список базовых доменов.
Каждый элемент — словарь с ключом “domain”.
Пример: (dict “domain” “example.com”)
Все поддомены должны соответствовать одному из этих базовых доменов.
Список используется для группировки и генерации конфигурации.
Пример:
{{ $baseDomains := list
(dict “domain” “domen1.synology.me”)
(dict “domain” “domen1.ddns.net”)
(dict “domain” “domen2.synology.me”)
(dict “domain” “domen2.ddns.net”)
(dict “domain” “remote1.fbx.one”)
(dict “domain” “remote2.fbx.one”)
(dict “domain” “remote3.fbx.one”)
}}

2.3. $subdomains — основной список всех сервисов.
Каждый сервис описывается словарём со следующими полями (см. раздел 3).

================================================================================

  1. ПОЛЯ СЛОВАРЯ subdomains (ПОДРОБНОЕ ОПИСАНИЕ)

┌────────────────┬────────────────────────────────────────────────────────────┐
│ Поле │ Описание и допустимые значения │
├────────────────┼────────────────────────────────────────────────────────────┤
│ device │ Название устройства (только для информационных целей). │
│ │ Пример: “ds218+”, “Xpenology”, “xorek”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ name │ Имя сервиса. │
│ │ • Если указано обычное имя (например “ad”), то полный │
│ домен будет . (ad.remote3.fbx.one). │
│ │ • Если указано “/”, то обслуживается корневой домен │
│ (просто domain, например remote3.fbx.one). │
│ │ • Для внутренних сервисов (access=“internal”) может быть │
│ любым, но рекомендуется осмысленное имя. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ subservice │ Уточнение для различения нескольких путей на одном │
│ │ поддомене. Добавляется в имена роутеров для уникальности. │
│ │ Пример: “glance-Home02”, “netbird-api”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ domain │ Базовый домен из списка $baseDomains. │
│ │ Пример: “remote3.fbx.one”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ ip │ IP-адрес бэкенда. │
│ │ • Для proxy (serviceType=“proxy”) – реальный IP, например │
│ │ “192.168.1.55”. │
│ │ • Для internal, redirect, static – используется заглушка │
│ │ “stub” (строка). Поле обязательно, но значение игнори- │
│ │ руется. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ port │ Порт бэкенда. │
│ │ • Для proxy – реальный порт (число), например 8080. │
│ │ • Для internal, redirect, static – рекомендуется 0 (ноль) │
│ │ как заглушка. Можно указать любое число, оно не будет │
│ │ использоваться. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ pathPrefix │ Путь, по которому отвечает сервис. │
│ │ Всегда начинается с “/”. Для корня указывайте “/”. │
│ │ Пример: “/api”, “/Home02”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ protocol │ Протокол соединения с бэкендом. │
│ │ • “http” – обычный HTTP │
│ │ • “https” – HTTPS (бэкенд должен поддерживать TLS) │
│ │ • “h2c” – gRPC (HTTP/2 без TLS) │
│ │ Для internal, redirect, static можно оставить “http” или │
│ │ любое значение – оно игнорируется. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ serviceType │ Тип сервиса: │
│ │ • “proxy” – обычное прокси (требует ip и port) │
│ │ • “redirect”– редирект на redirectTarget │
│ │ • “static” – раздача статических файлов из staticRoot │
│ │ По умолчанию (если не указано) принимается “proxy”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ access │ Уровень доступа: │
│ │ • “public” – доступ из интернета │
│ │ • “local-only” – только из локальной сети (RFC 1918) │
│ │ • “internal” – встроенный сервис Traefik (например │
│ │ dashboard) – используется api@internal. │
│ │ Примечание: если установлен access = “local-only”, то │
│ │ запросы разрешены только с локальных IP (RFC 1918). При │
│ │ этом middleware геоблокировки (например, geoblock-ru-only)│
│ │ не применяются к локальным IP из-за параметра │
│ │ allowLocalRequests = true в плагине geoblock. Таким │
│ │ образом, если сервис защищён флагами protectedRU или │
│ │ protectedNoneRU, то авторизация потребуется только для │
│ │ внешних запросов (не локальных), а локальные IP будут │
│ │ обслуживаться без авторизации. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ geoblock │ true/false. Если true – полностью блокировать запросы из │
│ │ России (возвращается 403). Работает только для access │
│ │ “public” или “local-only”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ protectedRU │ true/false. Если true – требовать авторизацию (?a=ключ) │
│ │ для посетителей из России. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ protectedNoneRU│ true/false. Если true – требовать авторизацию для │
│ │ посетителей НЕ из России. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ staticRoot │ Для serviceType=“static” – корневая директория на диске, │
│ │ например “/srv/www”. Для остальных типов обязательно │
│ │ указывать “stub” (заглушка). │
├────────────────┼────────────────────────────────────────────────────────────┤
│ redirectTarget │ Для serviceType=“redirect” – полный URL для перенаправления│
│ │ например “https://glance.domen1.synology.me”. Для остальных│
│ │ типов обязательно указывать “stub”. │
├────────────────┼────────────────────────────────────────────────────────────┤
│ certResolver │ • “le-http” – использовать Let’s Encrypt для получения │
│ │ сертификата (HTTPS). │
│ │ • “stub” – не использовать HTTPS, запросы будут приходить │
│ │ на entryPoint “web” (порт 80). │
└────────────────┴────────────────────────────────────────────────────────────┘

================================================================================

  1. ВАЖНЫЕ ЗАМЕЧАНИЯ ПО ЗАПОЛНЕНИЮ

• Для internal, redirect и static поля ip и port не используются, но их необходимо
указывать для корректной работы шаблона. Рекомендуется:
– ip = “stub”
– port = 0
• Для proxy ip и port должны быть реальными.
• Для staticRoot и redirectTarget, если они не применимы, всегда ставьте “stub”
(без кавычек в значении, просто stub). Никогда не оставляйте поле пустым.
• Флаги geoblock, protectedRU, protectedNoneRU работают только при access=“public”
или “local-only”. Для internal они игнорируются.
При этом важно понимать поведение для access=“local-only”: запросы разрешены
только с локальных IP (RFC 1918). Если установлен флаг protectedRU или
protectedNoneRU, middleware геоблокировки (например, geoblock-ru-only) не
применяются к локальным IP из-за параметра allowLocalRequests = true в плагине
geoblock. Таким образом, авторизация требуется только для внешних (не локальных)
запросов, а локальные IP обслуживаются без авторизации. Это штатное поведение,
позволяющее сохранить открытый доступ из локальной сети даже для сервисов,
защищённых от внешнего мира.
• Если не указан serviceType, по умолчанию подставляется “proxy”.
• Для корневого домена обязательно укажите name=“/”. Это единственное специальное
значение; для всех остальных поддоменов name должно быть обычной строкой без слешей.
• Все поля должны быть заполнены, недопустимо оставлять значения вида “”.
Для неиспользуемых полей всегда указывайте “stub” (для строк) или 0 (для чисел).
• Поля логического типа (geoblock, protectedRU, protectedNoneRU) также должны быть
явно указаны как true или false. Не оставляйте их пустыми и не опускайте, иначе
шаблонизатор может интерпретировать отсутствие как false, но для надёжности и
читаемости всегда проставляйте значение.

================================================================================

  1. АВТОМАТИЧЕСКОЕ ФОРМИРОВАНИЕ СПИСКОВ

5.1. $authRequiredHosts — список хостов, требующих авторизации.
Формируется из всех поддоменов, у которых protectedRU или protectedNoneRU = true.
Используется в правиле роутера входа auth-enter, чтобы разрешить вход по ключу
только на тех доменах, где это необходимо.

5.2. $httpsHosts — список хостов, которые должны использовать HTTPS.
Формируется из всех поддоменов, у которых certResolver не равен “stub”.
Используется для создания отдельного роутера redirect-http, который будет
перенаправлять HTTP-запросы на HTTPS для этих доменов.

================================================================================

  1. БЛОК MIDDLEWARE (СТАТИЧЕСКИЕ ОПРЕДЕЛЕНИЯ)

6.1. local-only – ограничивает доступ частными IP-диапазонами (RFC 1918).
Применяется для сервисов с access=“local-only” и для internal.

6.2. geoblock-set-header – добавляет заголовок X-IPCountry с кодом страны.
Не блокирует, только добавляет информацию. Использует внешнее API
get.geojs.io (можно заменить при необходимости).

6.3. geoblock-ru-only – блокирует запросы из России (blackListMode=true,
countries=[“RU”]). Используется при geoblock=true или protectedRU=true.

6.4. geoblock-block-nonru – блокирует запросы не из России (белый список только RU).
Используется при protectedNoneRU=true.

6.5. redirect-to-https – перенаправляет HTTP-запросы на HTTPS (код 308).
Используется в роутере redirect-http.

6.6. Middleware для аутентификации:
• set-auth-cookie – устанавливает cookie auth_session при успешном входе.
• strip-auth-param – удаляет параметр ?a=… из URL после входа.
• login-chain – цепочка из двух вышеуказанных.
• delete-auth-cookie – удаляет cookie (для выхода).
• redirect-to-root – редирект на корень после выхода.
• logout-chain – цепочка удаления cookie + редирект.

================================================================================

  1. РОУТЕР ДЛЯ ВХОДА (auth-enter)

Правило срабатывает, если:
• Есть параметр a со значением, совпадающим с одним из ключей в $authKeys.
• Хост запроса входит в список $authRequiredHosts.
При совпадении выполняется цепочка login-chain: устанавливается cookie и
удаляется параметр a (редирект на тот же URL без параметра). Роутер работает
только на HTTPS (entryPoint websecure) и использует резолвер le-http.

================================================================================

  1. ГЕНЕРАЦИЯ СЕРВИСОВ И РОУТЕРОВ (ЛОГИКА РАБОТЫ)

Для каждого базового домена из $baseDomains и каждого поддомена из $subdomains
с совпадающим domain создаются объекты Traefik. Чтобы избежать коллизий,
для каждого сервиса генерируется уникальное имя $baseName, включающее все
значимые поля.

8.1. Internal (access = “internal”)
• Создаётся только роутер с middleware local-only, указывающий на
api@internal. Поля serviceType, ip, port, staticRoot, redirectTarget
полностью игнорируются. Для единообразия рекомендуется serviceType=“proxy”.
• Пример: дашборд Traefik.

8.2. Redirect (serviceType = “redirect”)
• Создаётся middleware redirectRegex с target = redirectTarget.
• Бэкенд не требуется, сервис всегда api@internal.
• В зависимости от защиты создаются роутеры -logout, -auth, -public
с соответствующими middleware.

8.3. Static (serviceType = “static”)
• Создаётся middleware statiq с Root = staticRoot.
• Бэкенд не требуется, сервис api@internal.
• Роутеры аналогично redirect.

8.4. Proxy (serviceType = “proxy” или не указан)
• Создаётся HTTP-сервис (loadBalancer) с указанными protocol, ip, port.
• Если есть защита (geoblock, protectedRU, protectedNoneRU), генерируются
три роутера:

  • logout (путь /lo) – удаляет cookie и редиректит на корень.
  • auth (с cookie) – для авторизованных пользователей.
  • public (без cookie) – для всех остальных, с middleware геоблокировки.
    • Если защиты нет – один роутер с geoblock-set-header и, при необходимости,
    local-only.

Приоритеты роутеров:
• logout – 200
• auth – 100
• public – 90
• незащищённый proxy – 50 + длина pathPrefix (более длинные пути имеют больший
приоритет, что позволяет правильно обрабатывать пересекающиеся пути).

================================================================================

  1. РОУТЕР ДЛЯ РЕДИРЕКТА HTTP → HTTPS (redirect-http)

Этот роутер работает на entryPoint “web” (порт 80) и перенаправляет все запросы
на HTTPS для доменов, которые должны использовать HTTPS (certResolver не “stub”).
Правило строится как объединение всех таких хостов через OR. Приоритет установлен
в 1, чтобы он не мешал другим возможным роутерам на порту 80 (например, для stub-доменов).

================================================================================

  1. ОСОБЕННОСТИ РАБОТЫ С УДАЛЁННЫМ СЕРВЕРОМ (NGINX)

В вашей инфраструктуре есть удалённый сервер с реальным IP 185.12.45.67,
который терминирует SSL-соединения для доменов *.remote3.fbx.one и проксирует
трафик на OPNsense (192.168.1.1). Важно понимать, какую схему вы используете
для каждого поддомена.

10.1. Схема для stub-доменов (certResolver=“stub”)

Эти домены не требуют HTTPS на стороне OPNsense. Удалённый сервер должен
проксировать трафик на порт 80 OPNsense по HTTP.
Пример конфигурации NGINX для ad.remote3.fbx.one (stub):

server {
server_name ad.remote3.fbx.one;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/ad.remote3.fbx.one/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ad.remote3.fbx.one/privkey.pem;

      location / {
          proxy_pass http://192.168.1.1:80;   # важно: HTTP, не HTTPS
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
      }
  }
  --------------------------------------------------------------------

10.2. Схема для доменов с le-http (certResolver=“le-http”)

Эти домены должны обслуживаться через HTTPS на OPNsense. Удалённый сервер
должен проксировать трафик на порт 443 OPNsense по HTTPS (возможно, с
проверкой сертификата или с отключённой проверкой, если используется
самоподписанный или локальный сертификат). Traefik на OPNsense будет
сам получать сертификаты Let’s Encrypt для этих доменов.
Пример конфигурации NGINX для ad.remote3.fbx.one (le-http):

server {
server_name ad.remote3.fbx.one;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/ad.remote3.fbx.one/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ad.remote3.fbx.one/privkey.pem;

      location / {
          proxy_pass https://192.168.1.1:443;   # прокси на HTTPS
          proxy_ssl_verify off;                  # если сертификат OPNsense самоподписанный
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
      }
  }
  --------------------------------------------------------------------
  Если вы используете доверенные сертификаты (например, от Let's Encrypt и на OPNsense),
  можно включить проверку (proxy_ssl_verify on). Но проще оставить off.

10.3. Важное замечание
Для stub-доменов обязательно убедитесь, что в записи subdomain указан
certResolver=“stub”. Тогда Traefik направит запрос на entryPoint “web” (порт 80),
и редирект redirect-http не сработает, так как эти домены не входят в $httpsHosts.
Для доменов с le-http запросы на порт 80 будут перенаправлены на HTTPS
через роутер redirect-http, что обеспечит корректную работу.

================================================================================

  1. ПРИМЕРЫ ДОБАВЛЕНИЯ СЕРВИСОВ

11.1. Обычный поддомен с HTTPS (например, ad.remote3.fbx.one)
(dict “device” “xorek” “name” “ad” “subservice” “ad” “domain” “remote3.fbx.one”
“ip” “192.168.1.55” “port” 5001 “pathPrefix” “/” “protocol” “https”
“serviceType” “proxy” “access” “public” “geoblock” false
“protectedRU” false “protectedNoneRU” false “staticRoot” “stub”
“redirectTarget” “stub” “certResolver” “le-http”)

11.2. Корневой домен с HTTP (stub) (например, remote3.fbx.one)
(dict “device” “xorek” “name” “/” “subservice” “/” “domain” “remote3.fbx.one”
“ip” “192.168.1.55” “port” 8087 “pathPrefix” “/” “protocol” “http”
“serviceType” “proxy” “access” “public” “geoblock” false
“protectedRU” false “protectedNoneRU” false “staticRoot” “stub”
“redirectTarget” “stub” “certResolver” “stub”)
Обратите внимание: name=“/” указывает на корневой домен, а certResolver=“stub”
обеспечивает работу через HTTP.

11.3. Защищённый сервис (требуется авторизация для России)
(dict “device” “ds218+” “name” “book” “subservice” “book” “domain” “domen1.synology.me
“ip” “192.168.1.55” “port” 8099 “pathPrefix” “/” “protocol” “http”
“serviceType” “proxy” “access” “public” “geoblock” false
“protectedRU” true “protectedNoneRU” false “staticRoot” “stub”
“redirectTarget” “stub” “certResolver” “le-http”)

11.4. Редирект (например, glance.remote1.fbx.one → https://glance.domen1.synology.me)
(dict “device” “ds218+” “name” “glance” “subservice” “glance” “domain” “remote1.fbx.one”
“ip” “192.168.1.55” “port” 8087 “pathPrefix” “/” “protocol” “http”
“serviceType” “redirect” “access” “public” “geoblock” false
“protectedRU” false “protectedNoneRU” false “staticRoot” “stub”
“redirectTarget” “https://glance.domen1.synology.me” “certResolver” “le-http”)

11.5. Статический файловый сервер (auth.domen1.synology.me)
(dict “device” “ds218+” “name” “auth” “subservice” “auth” “domain” “domen1.synology.me
“ip” “192.168.1.55” “port” 9000 “pathPrefix” “/” “protocol” “http”
“serviceType” “static” “access” “public” “geoblock” false
“protectedRU” false “protectedNoneRU” false “staticRoot” “/srv/www”
“redirectTarget” “stub” “certResolver” “le-http”)

11.6. Внутренний дашборд Traefik (traefik.domen1.synology.me)
(dict “device” “ds218+” “name” “traefik” “subservice” “dashboard” “domain” “domen1.synology.me
“ip” “stub” “port” 443 “pathPrefix” “/” “protocol” “https”
“serviceType” “proxy” “access” “internal” “geoblock” false
“protectedRU” false “protectedNoneRU” false “staticRoot” “stub”
“redirectTarget” “stub” “certResolver” “le-http”)

================================================================================

  1. УСТРАНЕНИЕ НЕИСПРАВНОСТЕЙ (ОТЛАДКА)

12.1. Gateway Timeout (504)
• Проверьте, доступен ли бэкенд по указанным IP и порту с хоста OPNsense:
curl -I http://192.168.1.55:8087
• Для gRPC (h2c) проверьте поддержку протокола бэкендом.
• Убедитесь, что firewall не блокирует соединение.

12.2. 403 Forbidden
• Сработала геоблокировка. Проверьте флаги geoblock, protectedRU, protectedNoneRU.
• Убедитесь, что для local-only сервисов запрос идёт с локального IP.

12.3. 401 Unauthorized
• Требуется авторизация по ключу. Передайте ?a=ключ или убедитесь, что cookie
auth_session установлена и не просрочена.

12.4. Редирект не работает
• Проверьте redirectTarget – должен быть полным URL с протоколом.
• Убедитесь, что serviceType=“redirect” указан верно.

12.5. Статические файлы не отдаются
• Проверьте, существует ли указанная в staticRoot директория и доступна ли она для чтения процессом Traefik.
Например, для staticRoot = “/srv/www” выполните:
ls -ld /srv/www
Ожидаемый вывод:
drwxr-xr-x 2 traefik traefik 4096 мар 10 12:00 /srv/www
• Если директория не существует, создайте её:
mkdir -p /srv/www
• Убедитесь, что владельцем является пользователь, от которого запущен Traefik.
Узнайте пользователя Traefik:
ps aux | grep traefik
Обычно это пользователь traefik или nobody. Если это traefik, установите владельца:
chown -R traefik:traefik /srv/www
• Если владелец другой, но достаточно прав на чтение для всех, проверьте права:
chmod -R 755 /srv/www (даёт чтение и выполнение для всех)
• Проверьте, что внутри директории есть файлы, которые должен отдавать сервер.
• Убедитесь, что плагин statiq загружен в статической конфигурации Traefik
(секция experimental.plugins должна содержать statiq).

  • **Что будет, если директория /srv/www отсутствует или пуста, но при этом в конфигурации есть сервис с staticRoot = "/srv/www"?**
    - Если директория **отсутствует**: при запуске Traefik или при первом обращении к этому сервису в логах появятся ошибки вида
      `plugin/statiq: cannot read directory /srv/www: no such file or directory`, и запросы будут завершаться ошибкой 500 (Internal Server Error) или 404.
    - Если директория **существует, но пуста**: Traefik будет корректно обрабатывать запросы, но для любого запрошенного пути будет возвращать **404 Not Found**, поскольку файлы не найдены. Ошибок в логах Traefik (уровня ERROR) при этом не возникнет, только обычные записи о 404 в access-логе.
    - Если директория **не используется** (нет сервисов с staticRoot = "/srv/www"), то её отсутствие или пустота никак не влияют на работу Traefik.

12.6. Логи Traefik
• Для просмотра логов доступа выполните на OPNsense:
tail -f /var/log/traefik/access.log
• Для просмотра ошибок:
tail -f /var/log/traefik/traefik.log | grep ERR
• Также можно использовать API Traefik для просмотра созданных роутеров:
curl http://127.0.0.1:8080/api/http/routers | jq ‘. | select(.rule | contains(“remote3”))’

12.7. Проблемы с сертификатами (для доменов с le-http)
• Проверьте, что домен доступен из интернета для HTTP-01 challenge (порт 80).
• Посмотрите логи получения сертификата:
tail -f /var/log/traefik/traefik.log | grep -i “acme|remote3”
• Убедитесь, что в статической конфигурации правильно указан email и storage.

12.8. Проблемы с удалённым сервером
• С удалённого сервера проверьте доступность порта 80 OPNsense:
curl -I http://192.168.1.1:80 -H “Host: ad.remote3.fbx.one”
• Должен вернуться либо ответ от бэкенда (если stub), либо редирект 308 (если le-http).
• Если возвращается 308, значит домен требует HTTPS, и вам нужно либо перевести его в stub,
либо настроить прокси на HTTPS (см. раздел 9.2).

================================================================================

  1. ЗАКЛЮЧЕНИЕ

Данная конфигурация позволяет централизованно управлять доступом к множеству
сервисов через один файл, используя декларативный подход. Благодаря шаблонизации
поддерживаются сложные сценарии: геоблокировка, авторизация по ключу, редиректы
и статические файлы, включая корневые домены. Правильная настройка удалённого
сервера (NGINX) в паре с Traefik обеспечивает безопасный доступ из интернета
с использованием внешнего SSL-терминатора.

Следуя приведённым инструкциям и примерам, вы сможете быстро добавлять новые
сервисы и изменять существующие без риска нарушить работу других. После любого
изменения файла не забудьте перезапустить Traefik (в OPNsense через GUI или
командой service traefik restart). При возникновении проблем используйте
раздел отладки для диагностики.

Traefik динамическая конфигурация

# Traefik динамическая конфигурация для скачивания

Глобальная статическая конфигурация Traefik

# Traefik global configuration для скачивания

Файл OnlyOffice для скачивания.