- Автор темы
- Администратор
- Модер.
- #1
Защита веб-сервера на FreeBSD 14.5 с помощью pf: установка и настройка
FreeBSD 14.5 вышла в сентябре 2026 года. Это поздний релиз ветки 14.x, в котором в основном исправлены ошибки и закрыты уязвимости, поэтому для продакшен-серверов, которые пока не переходят на 15.x, обновление обязательно. В этой статье разберём, как включить штатный файрвол pf (Packet Filter) и настроить его для типичного веб-сервера с SSH, HTTP и HTTPS, защитой от брутфорса и флуда.
1. Что нужно
- Сервер с FreeBSD 14.5-RELEASE и root-доступ.
- Имя внешнего сетевого интерфейса.
Узнать имя интерфейса:
Код:
ifconfig
Обычно это
vtnet0 на KVM/VPS, em0 / igb0 / ix0 на железе. Ниже в примерах используется vtnet0, подставьте своё.Важно: если вы работаете по SSH, неверное правило отрежет вам доступ. Прочитайте раздел 5 про безопасное применение правил до первого запуска pf.
2. Включаем pf
pf входит в базовую систему и ставить ничего не нужно. Достаточно включить его в
rc.conf:
Код:
sysrc pf_enable="YES"
sysrc pf_rules="/etc/pf.conf"
sysrc pflog_enable="YES"
sysrc pflog_logfile="/var/log/pflog"
Модуль ядра загрузится автоматически при старте службы. Проверить можно так:
Код:
kldload pf # если модуль ещё не загружен
kldstat | grep pf
3. Конфигурация /etc/pf.conf
Создайте файл
/etc/pf.conf:
Код:
ee /etc/pf.conf
Готовая конфигурация для веб-сервера:
Код:
#==============================================
# /etc/pf.conf — веб-сервер на FreeBSD 14.5
#==============================================
### Макросы
ext_if = "vtnet0"
ssh_port = "22"
web_ports = "{ 80, 443 }"
# IP-адреса администраторов (можно добавить несколько)
admin_ips = "{ 203.0.113.10 }"
icmp_types = "{ echoreq, unreach }"
### Таблицы
table <bruteforce> persist file "/etc/pf.bruteforce"
table <abusers> persist
table <blacklist> persist file "/etc/pf.blacklist"
### Опции
set skip on lo0
set block-policy drop
set loginterface $ext_if
set optimization normal
set limit states 200000
### Нормализация трафика
scrub in on $ext_if all fragment reassemble
### Политика по умолчанию: всё запрещено
block log all
### Защита от подмены адресов
antispoof quick for $ext_if
### Блок-листы
block drop in quick on $ext_if from <blacklist>
block drop in quick on $ext_if from <bruteforce>
block drop in quick on $ext_if from <abusers>
### Исходящий трафик сервера разрешён
pass out quick on $ext_if keep state
### SSH с защитой от перебора
pass in on $ext_if inet proto tcp from any to ($ext_if) port $ssh_port \
flags S/SA keep state \
(max-src-conn 10, max-src-conn-rate 5/30, \
overload <bruteforce> flush global)
### HTTP/HTTPS с ограничением флуда
pass in on $ext_if proto tcp from any to ($ext_if) port $web_ports \
flags S/SA keep state \
(max-src-conn 200, max-src-conn-rate 100/5, \
overload <abusers> flush global)
### HTTP/3 (QUIC), если включён в nginx
pass in on $ext_if proto udp from any to ($ext_if) port 443 keep state
### ICMP (IPv4)
pass in on $ext_if inet proto icmp all icmp-type $icmp_types keep state
### ICMPv6 (без него IPv6 работать не будет)
pass in on $ext_if inet6 proto ipv6-icmp all keep state
Создайте пустые файлы для таблиц, иначе pf выдаст ошибку при загрузке:
Код:
touch /etc/pf.bruteforce /etc/pf.blacklist
chmod 600 /etc/pf.bruteforce /etc/pf.blacklist
Что здесь происходит
| Блок | Назначение |
|---|---|
set skip on lo0 | Не фильтровать loopback, иначе сломаются локальные сервисы (PHP-FPM, MySQL на 127.0.0.1) |
block-policy drop | Запрещённые пакеты молча отбрасываются, сканер не получает ответа |
scrub | Собирает фрагменты и нормализует пакеты |
antispoof | Отбрасывает пакеты с поддельным адресом из вашей же сети |
block log all | Всё, что не разрешено явно, запрещено и пишется в лог |
max-src-conn-rate 5/30 | Больше 5 новых SSH-подключений за 30 секунд — IP уходит в <bruteforce> |
flush global | Сбрасывает все уже открытые соединения нарушителя |
keep state | Ответные пакеты пропускаются автоматически |
4. Только для администраторов: закрываем SSH от всех
Если у вас статический IP, надёжнее вообще не показывать SSH миру. Замените блок SSH на:
Код:
pass in on $ext_if inet proto tcp from $admin_ips to ($ext_if) port $ssh_port \
flags S/SA keep state
Также можно перенести SSH на нестандартный порт (в
/etc/ssh/sshd_config и в макросе ssh_port). От целевой атаки это не спасёт, но мусора в логах станет на порядок меньше.5. Безопасное применение правил
Сначала проверьте синтаксис без загрузки:
Код:
pfctl -nf /etc/pf.conf
Если ошибок нет, подстрахуйтесь «таймером отката». Эта команда выключит pf через 5 минут, если вы потеряете доступ:
Код:
nohup sh -c 'sleep 300 && pfctl -d' >/dev/null 2>&1 &
Теперь запускаем:
Код:
service pf start
service pflog start
Откройте новую SSH-сессию, не закрывая старую. Если она подключилась и сайт открывается, отмените таймер:
Код:
pkill -f "sleep 300"
Если вы потеряли доступ, через 5 минут pf отключится сам.
6. Проверка и мониторинг
Код:
pfctl -s info # статистика и состояние
pfctl -s rules # загруженные правила
pfctl -s states # активные соединения
pfctl -vs rules # правила со счётчиками пакетов
Смотреть заблокированные пакеты в реальном времени:
Код:
tcpdump -n -e -ttt -i pflog0
Читать сохранённый лог:
Код:
tcpdump -n -e -ttt -r /var/log/pflog
7. Управление таблицами
Код:
# Посмотреть, кто попал в бан
pfctl -t bruteforce -T show
pfctl -t abusers -T show
# Добавить IP или подсеть в чёрный список
pfctl -t blacklist -T add 198.51.100.0/24
# Разбанить адрес
pfctl -t bruteforce -T delete 192.0.2.55
# Очистить таблицу целиком
pfctl -t abusers -T flush
Автоматическая разблокировка и сохранение банов
Добавьте в
/etc/crontab:
Код:
# Снимать баны старше суток
*/30 * * * * root /sbin/pfctl -t bruteforce -T expire 86400
*/30 * * * * root /sbin/pfctl -t abusers -T expire 3600
# Сохранять список брутфорсеров на диск, чтобы он пережил перезагрузку
0 * * * * root /sbin/pfctl -t bruteforce -T show > /etc/pf.bruteforce
8. Повседневные команды
Код:
pfctl -f /etc/pf.conf # перечитать правила без перезапуска
pfctl -d # выключить pf
pfctl -e # включить pf
service pf reload # то же самое через rc
pfctl -F states # сбросить все соединения (осторожно по SSH!)
9. Частые ошибки
- Сайт работает через Cloudflare или другой прокси. Все посетители приходят с IP прокси, и правило
max-src-conn-rateможет забанить сам Cloudflare. В этом случае разрешите 80/443 только из подсетей Cloudflare (табличкой) и уберите лимиты с веб-портов, а защиту от флуда настройте на стороне CDN и в nginx (limit_req). - Не открыт ICMPv6. Без него IPv6 работает нестабильно или не работает совсем.
- Забыли
set skip on lo0. В результате PHP-FPM, Redis и MySQL на localhost начинают «таинственно» отваливаться. - Файлы таблиц не созданы. pf не загрузит конфигурацию с
persist file, если файла нет. - Правила применены без проверки
pfctl -nf. Как следствие, сервер приходится поднимать через KVM-консоль хостера.
Итог
После этих шагов у вас:
- закрыто всё, кроме SSH, HTTP и HTTPS;
- брутфорс SSH автоматически блокируется;
- флуд на веб-порты ограничен;
- запрещённый трафик логируется;
- баны снимаются сами и переживают перезагрузку.
Документация для углублённого изучения:
man pf.conf, man pfctl и раздел о файрволах в FreeBSD Handbook.
