diff --git a/.gitattributes b/.gitattributes
index b20336a..b5dbc7b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -7,6 +7,8 @@ zabbix-allowed text eol=lf
syno-allowed text eol=lf
rds-allowed-static text eol=lf
scripts/*.sh text eol=lf
+scripts/security/*.sh text eol=lf
+scripts/security/*.example text eol=lf
scripts/*.cron text eol=lf
scripts/haproxy_ru_ipdeny_lib.sh text eol=lf
.gitignore text eol=lf
diff --git a/README.md b/README.md
index d8bf7f6..70c611a 100644
--- a/README.md
+++ b/README.md
@@ -38,3 +38,4 @@ DNS: **A** для `ext`, `autodiscover`, `rds`, `erp`, `git`, `sac`, **`sac-api`
- [Работа с Git: clone, pull, push, ключи и PAT](docs/git.md)
- [Политика: Gitea — основной, GitHub — резерв без секретов](docs/repo-policy.md)
- [Let's Encrypt / ACME через HAProxy (порт 80)](docs/letsencrypt-acme.md)
+- [Безопасность: UFW, бэкенды, план внедрения](docs/security/README.md)
diff --git a/docs/haproxy-install.md b/docs/haproxy-install.md
index 220b0d6..0518140 100644
--- a/docs/haproxy-install.md
+++ b/docs/haproxy-install.md
@@ -65,6 +65,8 @@ sudo haproxy -c -f /etc/haproxy/haproxy.cfg
На хосте с HAProxy с интернета обычно открывают только то, что слушает конфиг: **25**, **80** (Zabbix HTTP), **443** (и при использовании — **8404** для stats). Остальной трафик — по политике безопасности.
+**Полный план:** [docs/security/README.md](security/README.md) — UFW, бэкенды, чеклист, скрипты `scripts/security/`.
+
Порт **80** используется для **`http://zabbix.kalinamall.ru`** (allowlist `zabbix-allowed`). Для ACME на других именах см. [letsencrypt-acme.md](letsencrypt-acme.md) — потребуется расширить `fe_http_zabbix` маршрутизацией по Host/пути.
## SAC и Seaca (sac-api)
diff --git a/docs/security/README.md b/docs/security/README.md
new file mode 100644
index 0000000..ebf99e3
--- /dev/null
+++ b/docs/security/README.md
@@ -0,0 +1,54 @@
+# Безопасность периметра HAProxy (kalinamall.ru)
+
+План защиты хоста **haproxy.kalinamall.ru** и **бэкендов** за ним. HAProxy слушает **25**, **80**, **443**; фильтрация по SNI/Host и allowlist уже в `haproxy.cfg`, но **UFW на хосте inactive** — ниже поэтапное включение **defense in depth**.
+
+## Модель угроз (кратко)
+
+| Уровень | Что закрывает |
+|---------|----------------|
+| **MikroTik / провайдер** | DDoS, geo, лишние порты до DMZ |
+| **UFW на HAProxy** | Вход только 25/80/443 + SSH с доверенных IP |
+| **HAProxy ACL** | Неизвестный SNI, allowlist по сервисам |
+| **UFW на бэкендах** | Сервисы доступны **только с IP HAProxy** (`192.168.160.117`) |
+| **Приложение** | Auth OWA, RD Gateway, Gitea, SAC, 1С |
+
+Обход HAProxy (VPN в `192.168.160.0/24`, прямой RDP) — отдельная политика; см. [backend-hosts.md](backend-hosts.md).
+
+## Документы
+
+| Файл | Содержание |
+|------|------------|
+| [architecture.md](architecture.md) | Схема потоков, зоны, принципы |
+| [haproxy-host.md](haproxy-host.md) | Hardening Ubuntu, UFW, SSH, fail2ban, HAProxy |
+| [backend-hosts.md](backend-hosts.md) | Правила для каждого бэкенда |
+| [deployment-plan.md](deployment-plan.md) | **Пошаговый план** внедрения (фазы 0–5) |
+| [checklist.md](checklist.md) | Проверки после внедрения |
+
+## Скрипты
+
+| Скрипт | Назначение |
+|--------|------------|
+| [scripts/security/haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh) | UFW на HAProxy (dry-run по умолчанию) |
+| [scripts/security/haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example) | Пример env для HAProxy |
+| [scripts/security/backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh) | UFW на бэкенде: порт только с HAProxy |
+| [scripts/security/backend-ufw.env.example](../../scripts/security/backend-ufw.env.example) | Пример env для бэкенда |
+
+## Быстрый старт (после чтения deployment-plan)
+
+```bash
+# на HAProxy — сначала dry-run
+sudo cp ~/reverse-proxy/scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env
+sudo nano /etc/haproxy/security/haproxy-ufw.env # SSH_ALLOWED, при необходимости порты
+sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh
+sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply
+```
+
+На каждом бэкенде — [backend-hosts.md](backend-hosts.md) и `backend-ufw-from-haproxy.sh`.
+
+## Связь с конфигом HAProxy
+
+Allowlist-файлы в `/etc/haproxy/` (**не** заменяют UFW):
+
+- `git-sac-allowed`, `rds-allowed`, `1c-allowed`, `zabbix-allowed`, `syno-allowed`
+
+Сервисы **без** allowlist на 443: **Exchange** (`ext`, `autodiscover`), **sac-api**, **SMTP 25** — усилить на уровне приложения и мониторинга; при необходимости добавить allowlist или rate-limit (см. [haproxy-host.md](haproxy-host.md)).
diff --git a/docs/security/architecture.md b/docs/security/architecture.md
new file mode 100644
index 0000000..e55e58e
--- /dev/null
+++ b/docs/security/architecture.md
@@ -0,0 +1,68 @@
+# Архитектура защиты
+
+## Поток трафика
+
+```mermaid
+flowchart TB
+ Internet[Интернет]
+ MT[MikroTik / периметр]
+ HAP[HAProxy 192.168.160.117
25 80 443]
+ ACL[HAProxy ACL
SNI Host allowlist]
+ BE[Бэкенды 192.168.160.0/24]
+
+ Internet --> MT
+ MT -->|DNAT публичный IP| HAP
+ HAP --> ACL
+ ACL --> BE
+```
+
+## Два контура
+
+### 1. Публичный вход (через HAProxy)
+
+Клиент → публичный IP → **HAProxy** → (проверки) → один бэкенд.
+
+На HAProxy уже есть:
+
+- **443/TCP**: отклонение неизвестного SNI; allowlist для git, sac-web, rds, erp, syno.
+- **80/HTTP**: только `Host: zabbix.kalinamall.ru` + `zabbix-allowed`.
+- **25/SMTP**: passthrough на KSMG без IP-фильтра на frontend.
+
+### 2. Внутренняя сеть (LAN / VPN)
+
+Трафик **мимо HAProxy** возможен, если:
+
+- есть VPN в `192.168.160.0/24`;
+- бэкенд слушает на LAN без host firewall;
+- RDP/RDP Gateway по внутренним адресам.
+
+Защита: **UFW на бэкендах** + политика «сервисный порт только с `.117`», админ-доступ — отдельные правила.
+
+## IP HAProxy (источник для бэкендов)
+
+| Имя | IP |
+|-----|-----|
+| haproxy.kalinamall.ru | **192.168.160.117** |
+
+Health-check HAProxy и прокси идут **с этого IP**. Правила на бэкендах должны разрешать его явно.
+
+## Матрица: кто фильтрует что
+
+| Угроза | MikroTik | UFW HAProxy | HAProxy cfg | UFW бэкенд | Приложение |
+|--------|----------|-------------|-------------|------------|------------|
+| Сканирование портов | ✓ | ✓ | — | ✓ | — |
+| Неверный SNI / Host | — | — | ✓ | — | — |
+| Доступ не из allowlist | — | — | ✓ | — | ✓ |
+| Прямой удар в бэкенд | ✓ | — | — | ✓ | — |
+| Brute SSH на HAProxy | ✓ | ✓ | — | — | fail2ban |
+| DDoS L4 | ✓ | частично | conn limits* | — | — |
+
+\* Опционально: `maxconn`, rate-limit на frontend (Enterprise / custom); на 2.8 — sysctl, MikroTik, внешний scrubbing.
+
+## Принципы
+
+1. **Default deny** на вход (UFW `incoming deny`, бэкенды — только HAProxy + admin).
+2. **Минимум открытых портов** с интернета: 25, 80, 443 на одном хосте.
+3. **Не дублировать** смысл правил без нужды: allowlist в HAProxy для HTTP/SNI; UFW — транспорт и админ.
+4. **Сначала dry-run**, потом `--apply`; для SSH — **console/IPMI** или сессия, которую UFW не оборвёт.
+5. **Логи**: HAProxy + UFW + fail2ban; алерты на аномалии (Zabbix уже в инфраструктуре).
diff --git a/docs/security/backend-hosts.md b/docs/security/backend-hosts.md
new file mode 100644
index 0000000..a2388e8
--- /dev/null
+++ b/docs/security/backend-hosts.md
@@ -0,0 +1,88 @@
+# Firewall на бэкендах
+
+Цель: сервисный порт доступен **с HAProxy** (`192.168.160.117`) и при необходимости с **admin**-подсетей; **не** с произвольного LAN/Internet напрямую.
+
+Скрипт: [backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh).
+
+## Таблица бэкендов
+
+| Сервис | IP | Порт | Протокол | HAProxy backend | Примечание |
+|--------|-----|------|----------|-----------------|------------|
+| Exchange | 192.168.160.42 | 443 | TCP | be_exchange | OWA, ActiveSync |
+| RDS Gateway | 192.168.160.40 | **4430** | TCP | be_rds | не 443 |
+| 1С ERP | 192.168.160.150 | 443 | TCP | be_erp | |
+| Git (Gitea) | 192.168.160.129 | 443 | TCP | be_git | SSH Gitea 2222 — отдельно |
+| SAC | 192.168.160.145 | 443 | TCP | be_sac | |
+| Zabbix | 192.168.160.53 | **80** | TCP | be_zabbix | HTTP |
+| Synology heap | 192.168.160.185 | **5001** | TCP | be_syno_heap | DSM HTTPS |
+| Synology exchange | 192.168.160.35 | **5001** | TCP | be_syno_exchange | |
+| KSMG | 192.168.160.57 | 25 | TCP | be_ksmg_smtp | см. ниже |
+
+## Шаблон правил (логика)
+
+```
+allow from 192.168.160.117 to
+allow from ADMIN_ALLOWED to 22/tcp # SSH, если на этом хосте
+allow from LAN to ... # только если нужен прямой доступ (минимизировать)
+deny incoming (default)
+```
+
+### Пример env для Git (192.168.160.129)
+
+```bash
+HAPROXY_IP=192.168.160.117
+BACKEND_PORT=443
+ADMIN_ALLOWED="192.168.160.0/24"
+UFW_EXTRA_SERVICES="2222/tcp" # если Gitea SSH с admin LAN
+```
+
+## Особые случаи
+
+### KSMG (192.168.160.57:25)
+
+Почта с интернета приходит на **HAProxy:25** → KSMG. На KSMG в `EXTRA_ALLOWED` укажите **192.168.160.117**. Если другие MTA шлют **напрямую** на `.57` (минуя HAProxy), их IP тоже нужно явно разрешить — иначе после UFW почта встанет.
+
+### RDS (192.168.160.40)
+
+Порт **4430**, не 443. VPN-клиенты с прямым RDP на `.3` / `.40` обходят HAProxy — ограничивать **3389/4430** на session hosts отдельной GPO/файрволом.
+
+### Synology
+
+DSM может требовать доп. порты (5000, 5001, relay). Для доступа **только через HAProxy** достаточно **5001** с `.117`. Прямой доступ к NAS с LAN — отключить или ограничить в DSM **Панель → Безопасность → Брандмауэр**.
+
+### Exchange
+
+Внутренние клиенты Outlook могут ходить **напрямую** на `.42` по LAN — UFW не сломать без списка подсетей Exchange. Варианты:
+
+- разрешить `192.168.160.0/24` на 443 (шире);
+- или только `.117` + подсеть Exchange DAG/clients (уже).
+
+### Windows vs Linux
+
+Скрипт UFW — для **Linux**. Для Windows (Exchange, RDS, 1С) — **Windows Firewall with Advanced Security**: inbound allow **Remote IP** `192.168.160.117`, порт сервиса.
+
+## Развёртывание на Linux-бэкенде
+
+```bash
+sudo mkdir -p /etc/haproxy/security
+sudo cp backend-ufw.env.example /etc/haproxy/security/backend-ufw.env
+sudo nano /etc/haproxy/security/backend-ufw.env
+sudo bash backend-ufw-from-haproxy.sh
+sudo bash backend-ufw-from-haproxy.sh --apply
+```
+
+## Проверка
+
+С **HAProxy** (или симуляция):
+
+```bash
+curl -kI --resolve git.kalinamall.ru:443:ПУБЛИЧНЫЙ_IP https://git.kalinamall.ru/
+```
+
+С **другого** LAN-хоста (не .117):
+
+```bash
+curl -kI --connect-timeout 3 https://192.168.160.129/ # ожидается timeout/refused
+```
+
+В логе HAProxy — backend **UP**.
diff --git a/docs/security/checklist.md b/docs/security/checklist.md
new file mode 100644
index 0000000..57a9170
--- /dev/null
+++ b/docs/security/checklist.md
@@ -0,0 +1,44 @@
+# Чеклист после внедрения защиты
+
+## HAProxy (192.168.160.117)
+
+- [ ] `ufw status verbose` — active; есть 22 из admin, 25/80/443 open
+- [ ] SSH с admin-IP работает; с random IP — **нет** (или только ключ + fail2ban)
+- [ ] `ss -tlnp` — нет лишних listen (8404, 3000, …)
+- [ ] `haproxy -c -f /etc/haproxy/haproxy.cfg` — OK
+- [ ] С allowlist IP: git, rds, zabbix, erp, syno — **OK**
+- [ ] С чужого IP: git/rds/erp — **reject** (нет TCP reset или обрыв на ACL)
+- [ ] `fail2ban-client status sshd` — active
+- [ ] Логи: `grep -E 'SC|NOSRV|reject' /var/log/haproxy.log | tail`
+
+## MikroTik / периметр
+
+- [ ] С интернета на публичный IP: открыты только **25, 80, 443**
+- [ ] Нет DNAT 3389, 5001, 4430 на внутренние хосты
+- [ ] HAProxy health-check не блокируется
+
+## Бэкенды (каждый)
+
+- [ ] UFW/firewall active
+- [ ] С `.117` на сервисный порт — OK
+- [ ] С другого LAN IP на сервисный порт — **closed** (если так задумано)
+- [ ] HAProxy backend **UP** в stats/log
+- [ ] Прикладной smoke-test (OWA, RD Web, Gitea login, …)
+
+## KSMG (.57)
+
+- [ ] Почта inbound через HAProxy:25 — OK
+- [ ] Прямые отправители (если есть) — в `EXTRA_ALLOWED`
+
+## Документация и runbook
+
+- [ ] `/etc/haproxy/security/*.env` — backup в password store
+- [ ] Откат UFW: `ufw disable` + процедура задокументирована
+- [ ] Ответственный и дата внедрения фикс
+
+## Регулярно (ежемесячно)
+
+- [ ] `apt upgrade` на HAProxy
+- [ ] Проверка актуальности allowlist / `rds-allowed` cron
+- [ ] Review fail2ban / auth.log
+- [ ] Тест restore конфига из git
diff --git a/docs/security/deployment-plan.md b/docs/security/deployment-plan.md
new file mode 100644
index 0000000..579f213
--- /dev/null
+++ b/docs/security/deployment-plan.md
@@ -0,0 +1,100 @@
+# План развёртывания защиты
+
+Оценка: **2–4 рабочих дня** с окнами обслуживания на бэкендах (по одному хосту).
+
+## Фаза 0 — Подготовка (без изменений на проде)
+
+- [ ] Зафиксировать **публичный IP** HAProxy и правила DNAT на MikroTik.
+- [ ] Убедиться, что **SSH** на `192.168.160.117` доступен с admin-IP (не только с одного ноутбука).
+- [ ] Снимок/backup: `haproxy.cfg`, allowlist, `/etc/ssh/sshd_config`.
+- [ ] Клонировать репозиторий на HAProxy: `git pull kalinamall main`.
+- [ ] Прочитать [checklist.md](checklist.md).
+
+**Критерий готовности:** есть консоль или второй admin-IP для отката UFW.
+
+---
+
+## Фаза 1 — HAProxy: базовый hardening (без UFW enable)
+
+На **haproxy.kalinamall.ru**:
+
+- [ ] `apt update && apt upgrade` (или unattended-upgrades).
+- [ ] SSH: `PasswordAuthentication no`, `PermitRootLogin no`, только ключи (`/etc/ssh/sshd_config.d/`).
+- [ ] Установить **fail2ban** (`sshd` jail), см. [haproxy-host.md](haproxy-host.md).
+- [ ] Убедиться, что **stats :8404** не включены в prod (закомментированы в `haproxy.cfg`).
+- [ ] Права: `/etc/haproxy/*-allowed` — `0644 root:root`; скрипты в `/usr/local/bin` — `0755`.
+- [ ] `sysctl`: syncookies, rp_filter (см. [haproxy-host.md](haproxy-host.md)).
+
+**Проверка:** `ss -tlnp` — только 22, 25, 80, 443 (+ systemd-resolved если локально).
+
+---
+
+## Фаза 2 — UFW на HAProxy
+
+- [ ] `cp scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env`
+- [ ] Заполнить **`SSH_ALLOWED`** (офис, VPN, admin; минимум ваш текущий IP + LAN).
+- [ ] Dry-run: `bash scripts/security/haproxy-ufw-apply.sh`
+- [ ] **Не закрывая SSH-сессию**, в second tab: `bash scripts/security/haproxy-ufw-apply.sh --apply`
+- [ ] Проверить с admin-IP: `ssh haproxy.kalinamall.ru`, `curl` на 80/443 с allowlist.
+- [ ] Проверить SMTP 25 с внешнего MTA (если используется).
+
+**Откат:** `ufw disable` (временно) или `ufw delete` по номерам правил; env-файл сохранить.
+
+---
+
+## Фаза 3 — UFW на бэкендах (по одному хосту)
+
+Порядок рекомендуемый (от менее критичных к более):
+
+1. Zabbix `.53:80`
+2. Git `.129:443`
+3. SAC `.145:443`
+4. Synology `.185:5001`, `.35:5001`
+5. ERP `.150:443`
+6. RDS `.40:4430`
+7. Exchange `.42:443`
+8. KSMG `.57:25` — **особый случай** (почта может идти не только с HAProxy); см. [backend-hosts.md](backend-hosts.md)
+
+На каждом хосте:
+
+- [ ] `cp backend-ufw.env.example` → `/etc/haproxy/security/backend-ufw.env` (или `/etc/security/`)
+- [ ] Задать `BACKEND_PORT`, при необходимости `ADMIN_ALLOWED`, `EXTRA_ALLOWED`
+- [ ] Dry-run → `--apply`
+- [ ] Проверить сервис через HAProxy снаружи и health-check в логе HAProxy
+
+**Критерий:** с другого LAN-хоста (не `.117`) `curl https://:` **не** отвечает; с HAProxy — работает.
+
+---
+
+## Фаза 4 — MikroTik и мониторинг
+
+- [ ] На MikroTik: с интернета на HAProxy только **25, 80, 443** (не пробрасывать 3389, 5001, 4430 на внутренние IP).
+- [ ] Raw/firewall: опционально drop obvious scanners (согласовать с `rds-allowed` / RU geo).
+- [ ] Zabbix: триггеры на `ufw status`, `fail2ban`, рост `fe_https_sni` reject, backend DOWN.
+- [ ] Ротация логов `/var/log/haproxy.log`.
+
+---
+
+## Фаза 5 — Усиление (по приоритету)
+
+| Задача | Зачем |
+|--------|--------|
+| Allowlist или rate-limit для **Exchange** | Сейчас 443 без IP-фильтра |
+| Ограничить **sac-api** (если бизнес позволяет) | Сейчас без allowlist |
+| TLS verify на бэкендах HAProxy | Вместо `verify none` — внутренняя CA |
+| Отдельный VLAN для DMZ / HAProxy | Сегментация |
+| WAF / reverse proxy HTTP для Zabbix → HTTPS | Порт 80 только HTTP |
+| 2FA на Git, OWA, RD Gateway | Учётные записи |
+
+---
+
+## Окна и коммуникация
+
+| Фаза | Риск | Окно |
+|------|------|------|
+| 1 | Низкий | любое |
+| 2 | **SSH lockout** | рабочее время + console |
+| 3 | Обрыв сервиса при ошибке порта | вечер / по сервису |
+| 4 | Ошибка NAT | согласование с сетью |
+
+После каждой фазы — отметки в [checklist.md](checklist.md).
diff --git a/docs/security/haproxy-host.md b/docs/security/haproxy-host.md
new file mode 100644
index 0000000..243c393
--- /dev/null
+++ b/docs/security/haproxy-host.md
@@ -0,0 +1,138 @@
+# Hardening хоста HAProxy
+
+Хост: **haproxy.kalinamall.ru**, **192.168.160.117**, Ubuntu 24.04, HAProxy 2.8.x.
+
+## Слушающие порты (prod)
+
+| Порт | Frontend | Назначение |
+|------|----------|------------|
+| 22 | sshd | Администрирование (не в haproxy.cfg) |
+| 25 | fe_smtp | SMTP → KSMG |
+| 80 | fe_http_zabbix | HTTP Zabbix |
+| 443 | fe_https_sni | TLS passthrough по SNI |
+
+Не включать **8404/stats** на `0.0.0.0` без VPN и пароля.
+
+## UFW
+
+Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh).
+
+Политика:
+
+```
+default incoming: deny
+default outgoing: allow
+allow 22/tcp from SSH_ALLOWED
+allow 25, 80, 443/tcp from anywhere # сервисы периметра
+```
+
+**SSH_ALLOWED** — несколько CIDR/ IP через пробел в env. Минимум:
+
+- подсеть админов / VPN;
+- LAN `192.168.160.0/24` (если SSH только изнутри);
+- **не** `0.0.0.0/0` без крайней необходимости.
+
+Перед `--apply` обязательно dry-run и **вторая SSH-сессия**.
+
+## SSH
+
+Пример drop-in `/etc/ssh/sshd_config.d/99-hardening.conf`:
+
+```
+PermitRootLogin no
+PasswordAuthentication no
+KbdInteractiveAuthentication no
+PubkeyAuthentication yes
+MaxAuthTries 3
+AllowUsers papatramp
+```
+
+После правок: `sshd -t && systemctl reload sshd`.
+
+## fail2ban
+
+```bash
+sudo apt install -y fail2ban
+```
+
+`/etc/fail2ban/jail.d/sshd.local`:
+
+```ini
+[sshd]
+enabled = true
+port = ssh
+filter = sshd
+logpath = /var/log/auth.log
+maxretry = 5
+findtime = 600
+bantime = 3600
+```
+
+`systemctl enable --now fail2ban`, проверка: `fail2ban-client status sshd`.
+
+## HAProxy
+
+### Уже в конфиге
+
+- Reject неизвестного SNI.
+- Allowlist по сервисам.
+- Длинные timeout для RDP/Exchange — осознанно; не занижать без теста.
+
+### Рекомендуется добавить (отдельный PR / окно)
+
+1. **Лимит соединений** (global уже `maxconn 50000`; при атаке снизить или ограничить на frontend):
+
+ ```
+ # frontend fe_https_sni
+ # maxconn 10000
+ ```
+
+2. **Exchange / sac-api без allowlist** — рассмотреть:
+ - отдельный allowlist;
+ - или `tcp-request content reject` при превышении conn rate (HAProxy 2.8+: stick-table / sc1 — сложнее в TCP+SNI).
+
+3. **Логирование отказов** — grep `SC` / `` / короткие сессии в SIEM.
+
+4. **`ssl verify required`** на бэкендах после выпуска внутренней CA.
+
+### systemd
+
+```bash
+systemctl enable haproxy
+# после reload: systemctl status haproxy
+```
+
+## sysctl (опционально)
+
+`/etc/sysctl.d/99-haproxy-security.conf`:
+
+```
+net.ipv4.tcp_syncookies = 1
+net.ipv4.conf.all.rp_filter = 1
+net.ipv4.conf.default.rp_filter = 1
+net.ipv4.icmp_echo_ignore_broadcasts = 1
+```
+
+`sysctl --system`
+
+## Обновления
+
+```bash
+sudo apt install -y unattended-upgrades
+sudo dpkg-reconfigure -plow unattended-upgrades
+```
+
+Исключить автоперезапуск HAProxy в неудобное окно — настроить `Unattended-Upgrade::Automatic-Reboot` по политике.
+
+## Права и секреты
+
+- Allowlist и `haproxy.cfg` — root, без world-readable секретов (в репо секретов нет).
+- `/etc/haproxy/security/` — `0750 root:root`.
+- Не коммитить `haproxy-ufw.env` с реальными IP в публичный GitHub (в kalinamall — по политике repo).
+
+## Мониторинг
+
+- HAProxy backend **DOWN** / **L4CON**.
+- Рост отклонённых TCP (`SC`, reject по ACL).
+- `ufw status numbered`, диск под логами.
+- fail2ban bans.
diff --git a/scripts/security/backend-ufw-from-haproxy.sh b/scripts/security/backend-ufw-from-haproxy.sh
new file mode 100644
index 0000000..17df52f
--- /dev/null
+++ b/scripts/security/backend-ufw-from-haproxy.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+# UFW на бэкенде: сервисный порт только с HAProxy (+ admin/extra).
+#
+# cp backend-ufw.env.example /etc/haproxy/security/backend-ufw.env
+# ./backend-ufw-from-haproxy.sh --apply
+set -Eeuo pipefail
+
+ENV_FILE="${BACKEND_UFW_ENV:-/etc/haproxy/security/backend-ufw.env}"
+APPLY=0
+
+die() { echo "error: $*" >&2; exit 1; }
+log() { echo "[*] $*"; }
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --apply) APPLY=1; shift ;;
+ --env-file) ENV_FILE=$2; shift 2 ;;
+ -h | --help)
+ echo "Usage: $0 [--apply] [--env-file PATH]"
+ exit 0
+ ;;
+ *) die "unknown option: $1" ;;
+ esac
+done
+
+[[ -f $ENV_FILE ]] || die "missing env: $ENV_FILE"
+
+# shellcheck source=/dev/null
+source "$ENV_FILE"
+
+HAPROXY_IP=${HAPROXY_IP:-192.168.160.117}
+BACKEND_PORT=${BACKEND_PORT:-}
+ADMIN_ALLOWED=${ADMIN_ALLOWED:-}
+EXTRA_ALLOWED=${EXTRA_ALLOWED:-}
+UFW_EXTRA_SERVICES=${UFW_EXTRA_SERVICES:-}
+UFW_IPV6=${UFW_IPV6:-no}
+
+[[ -n $BACKEND_PORT ]] || die "BACKEND_PORT required"
+
+command -v ufw >/dev/null 2>&1 || die "ufw not installed"
+
+run() {
+ log "$*"
+ [[ $APPLY -eq 1 ]] && "$@"
+}
+
+[[ $APPLY -eq 0 ]] && echo "=== DRY-RUN (pass --apply) ==="
+
+if [[ $APPLY -eq 1 ]]; then
+ sed -i "s/^IPV6=.*/IPV6=${UFW_IPV6}/" /etc/default/ufw 2>/dev/null || true
+fi
+
+run ufw --force reset
+run ufw default deny incoming
+run ufw default allow outgoing
+
+run ufw allow from "$HAPROXY_IP" to any port "$BACKEND_PORT" proto tcp comment 'haproxy'
+
+for cidr in $EXTRA_ALLOWED; do
+ [[ -z $cidr ]] && continue
+ run ufw allow from "$cidr" to any port "$BACKEND_PORT" proto tcp comment 'extra'
+done
+
+for cidr in $ADMIN_ALLOWED; do
+ [[ -z $cidr ]] && continue
+ run ufw allow from "$cidr" to any port 22 proto tcp comment 'admin ssh'
+ for svc in $UFW_EXTRA_SERVICES; do
+ [[ -z $svc ]] && continue
+ run ufw allow from "$cidr" to any port "$svc" comment 'admin extra'
+ done
+done
+
+if [[ $APPLY -eq 1 ]]; then
+ ufw --force enable
+ ufw status verbose
+ log "done port=$BACKEND_PORT haproxy=$HAPROXY_IP"
+else
+ echo "=== end dry-run ==="
+fi
diff --git a/scripts/security/backend-ufw.env.example b/scripts/security/backend-ufw.env.example
new file mode 100644
index 0000000..a5b0713
--- /dev/null
+++ b/scripts/security/backend-ufw.env.example
@@ -0,0 +1,14 @@
+# UFW на бэкенде — пример для Git 192.168.160.129
+HAPROXY_IP=192.168.160.117
+BACKEND_PORT=443
+
+# Admin SSH / мониторинг (пробел), опционально
+ADMIN_ALLOWED="192.168.160.0/24"
+
+# Доп. порты с ADMIN_ALLOWED, напр. 2222/tcp для Gitea SSH
+UFW_EXTRA_SERVICES=""
+
+# Доп. источники на BACKEND_PORT (напр. MTA для KSMG)
+EXTRA_ALLOWED=""
+
+UFW_IPV6=no
diff --git a/scripts/security/haproxy-ufw-apply.sh b/scripts/security/haproxy-ufw-apply.sh
new file mode 100644
index 0000000..ffd614e
--- /dev/null
+++ b/scripts/security/haproxy-ufw-apply.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+# UFW для HAProxy: default deny, SSH из SSH_ALLOWED, inbound 25/80/443.
+# Dry-run по умолчанию; --apply для включения.
+#
+# cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env
+# ./haproxy-ufw-apply.sh
+# ./haproxy-ufw-apply.sh --apply
+set -Eeuo pipefail
+
+ENV_FILE="${HAPROXY_UFW_ENV:-/etc/haproxy/security/haproxy-ufw.env}"
+APPLY=0
+
+die() { echo "error: $*" >&2; exit 1; }
+log() { echo "[*] $*"; }
+
+usage() {
+ cat <<'EOF'
+Usage: haproxy-ufw-apply.sh [--apply] [--env-file PATH]
+
+ --apply выполнить ufw enable/reset (иначе только показ команд)
+ --env-file путь к env (default: /etc/haproxy/security/haproxy-ufw.env)
+EOF
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --apply) APPLY=1; shift ;;
+ --env-file) ENV_FILE=$2; shift 2 ;;
+ -h | --help) usage; exit 0 ;;
+ *) die "unknown option: $1" ;;
+ esac
+done
+
+[[ -f $ENV_FILE ]] || die "missing env: $ENV_FILE (copy from haproxy-ufw.env.example)"
+
+# shellcheck source=/dev/null
+source "$ENV_FILE"
+
+SSH_ALLOWED=${SSH_ALLOWED:-}
+INBOUND_TCP=${INBOUND_TCP:-25 80 443}
+UFW_IPV6=${UFW_IPV6:-no}
+
+[[ -n $SSH_ALLOWED ]] || die "SSH_ALLOWED is empty — lockout risk"
+
+command -v ufw >/dev/null 2>&1 || die "ufw not installed (apt install ufw)"
+
+run() {
+ log "$*"
+ [[ $APPLY -eq 1 ]] && "$@"
+}
+
+if [[ $APPLY -eq 0 ]]; then
+ echo "=== DRY-RUN (pass --apply to execute) ==="
+fi
+
+if [[ $APPLY -eq 1 ]]; then
+ sed -i "s/^IPV6=.*/IPV6=${UFW_IPV6}/" /etc/default/ufw 2>/dev/null || true
+fi
+
+run ufw --force reset
+run ufw default deny incoming
+run ufw default allow outgoing
+
+for cidr in $SSH_ALLOWED; do
+ run ufw allow from "$cidr" to any port 22 proto tcp comment 'admin ssh'
+done
+
+for port in $INBOUND_TCP; do
+ run ufw allow "$port/tcp" comment "haproxy service $port"
+done
+
+if [[ $APPLY -eq 1 ]]; then
+ ufw --force enable
+ ufw status verbose
+ log "done"
+else
+ echo "=== end dry-run ==="
+fi
diff --git a/scripts/security/haproxy-ufw.env.example b/scripts/security/haproxy-ufw.env.example
new file mode 100644
index 0000000..e8c78e7
--- /dev/null
+++ b/scripts/security/haproxy-ufw.env.example
@@ -0,0 +1,11 @@
+# UFW для haproxy.kalinamall.ru — пример.
+# Копия: /etc/haproxy/security/haproxy-ufw.env (не коммитить в GitHub с prod-IP при необходимости)
+
+# IP/CIDR для SSH (через пробел). Обязательно перед --apply.
+SSH_ALLOWED="192.168.160.0/24 5.100.81.121"
+
+# Публичные сервисы HAProxy (пробел)
+INBOUND_TCP="25 80 443"
+
+# IPv6: yes | no
+UFW_IPV6=no