Это шестая статья серии «Эксплуатация VPS / DevOps-база». К концу прошлого шага (База данных) у нас уже стоит полный рабочий стек: сервер на ногах (Анатомия VPS), сервисы живут (systemd), nginx отдаёт HTTPS (Nginx), PHP-FPM считает детей (PHP-FPM), MariaDB бэкапится и не висит (MariaDB). И при этом с первой секунды жизни на сервере вокруг него ходят боты: в статье №1 мы уже видели, как auth.log наполняется попытками подбора пароля к SSH. SSH-пароль мы тогда убрали (вход только по ключу), MariaDB не открывали наружу (bind-address=127.0.0.1). Но на машине уже торчит открытым целый набор «дверей», и одна из них — порт 22 — до сих пор видна из интернета как мишень, а на некоторых сборках в открытом виде висит ещё и не одно внутреннее, «невидимое по задумке», Listening-порты.
Эта статья про периметр — границу между «ваша машина» и «весь интернет». Мы: сформулируем модель угроз (что именно защищаем и от чего) и главный принцип firewall («по умолчанию запрещено, открыто только нужное»); поднимем ufw с правильным порядком команд (чтобы не отрезать себе SSH-вход навсегда); поймём, чем он отличается от nftables и когда лезть глубже; поставим fail2ban, который реагирует не «пустой порт», а живые атаки подбора на SSH и на форму входа WordPress; доработаем SSH до «следующего уровня» поверх того, что сделали в №1 (keep-alive, ограничение по пользователям, обдуманная смена порта); и, наконец, научимся регулярно делать аудит слушающих портов (ss -tulpn), чтобы firewall не превратился в «магию, которую поставили и забыли», а был инструментом, который вы сами читаете.
Термины, которые введём один раз: файрвол (firewall) — правило на сетевом уровне, которое решает «входящий/исходящий пакет пропустить, отбросить или отвергнуть»; на одной машине это набор правил ядра. Пакет — единица данных в сети (кусочек соединения). Порт — «номер двери» внутри IP: у SSH один номер (22), у веб-сервера другой (443); на одном IP их десятки. Слушающий (LISTEN) порт — порт, на котором какой-то процесс ждёт подключений (его видно через
ss). Inbound/outbound — «входящий» (из интернета к вам) и «исходящий» (от вас наружу); закрывают обычно inbound. DROP vs REJECT — тихое «не отвечаю» (проверяющему непонятно) и вежливый «закрыто, я отказываю» (отвечает, что порт не открыт). SSH (Secure Shell, протокол удалённого входа) — уже знаком из №1. ufw (Uncomplicated Firewall, «несложный файрвол») — человеческая надстройка над низкоуровневымnftables. fail2ban — демон, следит за логами и по правилам сам добавляет IP нарушителя в firewall-бан. Все домены/IP условные (example.test, адреса из диапазона203.0.113.x— «тестовые», как в №1); подставляйте свои.
Часть I. Периметр: от чего защищает firewall и почему «закрыть всё» само по себе не ответ
Прежде чем писать ufw enable, сформулируем задачу словами. Firewall «на интуиции» («закрою всё, будет безопаснее») часто ломает больше, чем защищает.
1. Что firewall реально делает
Пакет за пакетом ядро машины пропускает через цепочку правил; каждое правило говорит пропустить (ACCEPT), отбросить тихо (DROP) или отвергнуть с ответом (REJECT). «Тихий DROP» для внешнего проверяющего выглядит как «хост не существует/порт мёртвый»; REJECT честно отвечает «порт закрыт». Для атакующего разницы мало, но для диагностики (telnet/nmap глазами) это важно: REJECT помогает отличить «файрвол закрыл» от «процесс не слушает».
Важный физический факт: файрвол на машине ничего не знает о трафике, который к машине не приходит. Если ваш WordPress прикладной слой (nginx, PHP, плагины) пропускает SQL-инъекцию из «валидного пакета на 443» — firewall тут не при чём, пакет на 443 вам разрешён. Firewall защищает только сеть и порты, это периметр, а не прикладная безопасность.
2. Firewall vs WAF — два разных слоя, и их путают
Ваша серия уже закрывает прикладной слой, и вот как они соотносятся:
| Инструмент | Где режет | Что ловит | Не ловит |
|---|---|---|---|
| ufw/nftables/fail2ban | сетевой уровень (IP, порт, «частота подключений») | сканирование портов, перебор SSH, IP-флуд, лишние слушающие порты | «злой запрос» по HTTPS к разрешённому 443 |
| WAF (ModSecurity) | прикладной уровень (внутри HTTP-трафика на 443) | SQL-инъекции, XSS в параметрах, эксплойты к известным уязвимостям CMS | то, что не «внутри HTTP» (злой пакет на другой порт) |
То есть firewall и WAF не заменяют, а дополняют друг друга. WAF поверх nginx у вас отдельная тема — см. WAF и ModSecurity; прикладной rate-limit и «защита от перебора на уровне приложения» — см. Защита API, rate-limiting (там же про то, что «банить по IP» лучше там, где вы видите логику, а не ядро). Аудит «что вообще торчит» — тема Аудит безопасности сайта. Эта статья — про сетевой периметр, который держит машину до того, как трафик попадёт в приложение.
3. Модель угроз на одном VPS простыми словами
Что реально случается с сервером в интернете, и что закрываем:
- Перебор по SSH (боты лезут через 22) → главная цель. В №1 уже ушло «паролем» (ключ), fail2ban в этой статье догоняет «ключ/неизвестные попытки», firewall режет сканирование.
- Лишние слушающие порты, которые вы забыли → внутренняя БД на 3306, закешированный memcached/Redis «на 0.0.0.0» без пароля, dev-сервер, «временно» поднятый порт. Их видно в
ss(Часть VI) и их режет firewall, но правильнее — чтобы они не слушали наружу (bind к127.0.0.1, как вы делали с MariaDB). - Злой/большой inbound на прикладные порты (80/443) → это не firewall (443 вам нужен открыт для всех!), а rate-limit приложения + WAF.
- Вы сами должны выходить наружу:
apt update(TCP 80/443 к зеркалам),certbot(HTTP-01 challenge и ACME API), SMTP-отправка почты, NTP-время, DNS. Если закрыть исходящее «по принципу симметрии» — обновление и авто-продление сертификата встанут, а авто-продление — это мёртвый HTTPS через 90 дней.
Вывод модели: inbound — закрыт, открыто ровно {22, 80, 443} (+ то, что вы явно зовёте наружу, например dev-порт только под свой IP); outbound — открыт по умолчанию. Это и есть дефолт, который мы сейчас настроим правильно.
Чек-лист части
- Включили firewall, не решив «что должно оставаться открытым», и потом неделю чинили «почему не обновляется / почему сертификат не продлевается».
- Думают, что firewall от SQL-инъекций «по HTTPS» — нет, там работает WAF, это другой слой.
- Открыли исходящие на всё подряд «чтобы ничего не ломалось», но закрыли их и не заметили — тогда падает `apt`/`certbot`.
Часть II. ufw: поднимаем файрвол и не режем себе SSH-вход
ufw (Uncomplicated Firewall) — утилита, которая не изобретает свой файрвол, а пишет понятные команды в низкоуровневый nftables. На Ubuntu она обычно стоит, но «выключена по умолчанию».
1. Установка и статус
sudo apt update && sudo apt install -y ufw
sudo ufw status verbose # что сейчас; обычно "Status: inactive"2. Ключевое: правильная последовательность команд
Логика «сначала впустить себя, потом закрывать остальное» спасает от вечного lockout. Делайте строго в таком порядке (и не закрывайте текущий SSH-сеанс, пока не откроете новый в другом терминале — ваш живой сеанс переживет ufw enable, а вот новый зайдёт или нет — надо проверить до того, как вы потеряете старый):
# 1) правила по умолчанию: входящее запрещено, исходящее разрешено
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 2) ТОЛЬКО ПОСЛЕ ЭТОГО открываем то, что нужно (порядок не важен для add, но важен для enable ниже)
sudo ufw allow 22/tcp comment 'SSH' # вход по SSH (22 — стандарт)
sudo ufw allow 80/tcp comment 'HTTP→redirect' # порт 80 нужен и для HTTP→HTTPS, и для certbot (см. ниже)
sudo ufw allow 443/tcp comment 'HTTPS'
# 3) теперь включаем — уже с добавленными правилами, не голым deny
sudo ufw enable⚠️ Почему не
ufw enableпервым. Если включитьufwдо добавленияallow 22, дефолтdeny incomingприменится и новый SSH-вход будет запрещён (ваш текущий сеанс обычно доживёт, но стоит вам перезагрузиться/потерять соединение — и вы вне машины). Держите порядок: правила → enable.
3. Проверка и живой тест
sudo ufw status numbered # список правил с номерами (ими удаляют: sudo ufw delete <N>)Тест до того, как закрыть окно: из другого терминала ssh deploy@ваш.IP должно зайти. Если не заходит — не закрывайте первый сеанс, а из первого (status numbered) добавьте allow 22/tcp. Убедились — можно спокойно продолжать.
Если на сервере ещё и iptables-persistent/netfilter-persistent — правила переживут перезагрузку (ufw сам хранит свои, но проверьте после reboot: ufw status и новый SSH-вход).
4. Что мы открыли и почему этого хватает WordPress
- 22/tcp — SSH (для вас и только для вас по факту — доступ ограничен в Части V ключом и
AllowUsers). - 80/tcp — nginx нужен на HTTP: (а) редирект на HTTPS (см. Часть IV), (б) HTTP-01 challenge для Let’s Encrypt: бот ACME ходит на
http://example.test/.well-known/...— без 80 авто-продление сертификата падает. Это частая грабля «закрыл всё, кроме 443 — сертификат не продлевается». - 443/tcp — сам HTTPS для гостей.
- Outbound allow — чтобы
apt/certbot/wp-cron-запросы могли выходить.
Чего сознательно не открываем: 3306 (база — только через сокет, см. MariaDB), 25/587 наружу (если не держите SMTP-сервер — это тема отдельной статьи), dev-порты (:8000, :3000 — «временно» поднят, забыт и доступен миру).
5. Точечные правила и limit (fail2ban-лайт)
# открыть порт только под конкретный ваш IP (например, вход по SSH с постоянного адреса)
sudo ufw allow from 203.0.113.7 to any port 22 proto tcp comment 'SSH from home'
# или оставить SSH «для всех», но с rate-limit (ядро само троттлит частые попытки)
sudo ufw limit 22/tcp comment 'SSH rate-limit'limit — это встроенный в ufw модуль recent (считает попытки с одного IP за окно и банит частые). Ограничение: это не «полноценный бан на часы», а троттлинг «пока чаще N раз за 60 сек» — хорошо для первого слоя, но полноценный fail2ban живёт отдельно (Часть IV), потому что там реакция на логи и разные сервисы, а не только 22-й порт.
Чек-лист части
- `ufw enable` до добавления `allow 22` → потеряли вход после рестарта (самый частый случай «сервер есть, входа нет»).
- Закрыли 80 «лишнее, у меня же HTTPS» → certbot не продлевает сертификат → HTTPS гаснет через 90 дней.
- Включили исходящий `deny outgoing` «для безопасности» → `apt update`/NTP/DNS не работают, авто-обновления не ставятся.
- Не проверили, что правила переживают `reboot` → после обновления ядра и рестарта firewall «слетел».
Часть III. nftables под капотом ufw: когда и зачем лезть глубже
Чтобы ufw не был «магией», достаточно понимать, что под ним.
1. Короткая эволюция
Исторически ядро фильтровало пакеты через iptables; в новых ядрах на смену пришёл nftables (fast, гибче синтаксис, один общий язык). ufw на новых Ubuntu пишет свои правила в nftables (через nft-бекенд или совместимый слой — зависит от версии). Вы можете проверить:
sudo ufw version # у части сборок строка "Backend: backend (command ...)" — покажет nft/iptables
sudo nft list ruleset # если стоит nftables, увидите итоговые правила (ufw их и создаёт)2. Что умеет nftables, чего нет в человеческом ufw
Пользоваться им напрямую нужно, когда правило не выражается одной командой ufw:
- диапазоны IP/подсети (например, пустить весь ваш офисный
/24на SSH) —ufw allow from 203.0.113.0/24 to any port 22это, впрочем, тоже умеет. - сложные stateful-цепочки, NAT (проброс порта с одного адреса на другой, masquerade для контейнеров), «толькоEstablished/New» тонко, логирование отдельных цепочек.
- связка с
docker(контейнеры сами лезут в netfilter и «дырявят» даже закрытые ufw-порты через цепочкуDOCKER-USER— это как раз классическая причина, по которой «ufw закрыл 3306, а его всё равно видно снаружи». (Тема контейнеров — отдельная серия Контейнеры и оркестрация.)
# пример «глубже», только если читаете документацию и понимаете; на чистом VPS без Docker
# это почти никогда не нужно делать руками
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established,related accept
tcp dport { 22, 80, 443 } accept
}
}
Практическое правило для одного человека на одной машине: пользуйтесь ufw; лезьте в nftables, только когда ufw не выражает нужное (обычно — Docker/подсети/специфичный NAT). Не «учите nftables, чтобы показать, что можете»: больше ручных правил = больше мест, где легко ошибиться и остаться без входа.
Чек-лист части
- Пишут руками `nftables` поверх `ufw` — два инструмента конфликтуют, результат непредсказуем; выберите один источник истины (для ufw-машин — ufw).
- Docker «дырявит» firewall: контейнер опубликовал порт мимо ufw. Чинится либо публикацией только `127.0.0.1:PORT`, либо цепочкой `DOCKER-USER` (не тупым `allow` из ufw).
- Изменили правила в `nftables` и не сохранили (`nft list ruleset > /etc/nftables.conf` и `systemctl enable nftables`) — после рестарта слетает.
Часть IV. fail2ban: реагируем на атаки, а не только держим порт закрытым
Firewall «закрыл дверь». fail2ban делает шаг дальше: следит за логами и сам временно закрывает дверь тому IP, который пытается её подобрать. Механика: демон читает auth.log/логи приложений, по «фильтру» (filter — регулярка «что считать неудачей») считает попадания с одного IP и, когда превышен порог, через action вписывает этот IP в firewall-бан.
1. Установка и устройство
sudo apt install -y fail2ban
sudo systemctl enable --now fail2banТри слоя конфигурации (важно не править дефолты, а переопределять):
/etc/fail2ban/jail.conf— базовые настройки (НЕ трогаем, обновится с пакетом)./etc/fail2ban/jail.local— ваши overrides (копируем/дописываем нужные jails сюда)./etc/fail2ban/filter.d/*.conf— регулярки «что такое неудача» (sshd.conf и т.п.).
⚠️ Не правьте
jail.conf/filter.dпрямо — при обновлении пакета затрётся. Пишите в.local-файлы (jail.local,filter.d/ssh.local). Тот же принцип «override-файлы», что мы применяли в nginx (sites-available/конфиги, которые apt не трогает) и в MariaDB (/etc/mysql/mariadb.conf.d/99-wp.cnf).
2. Jail для SSH: важный нюанс про «пароль vs ключ»
В Части III статьи №1 мы поставили PasswordAuthentication no. Это значит: боты-переборщики не получают «неверный пароль» — их отбивает уже сам SSH («no supported authentication methods»). Дефолтный фильтр sshd исторически смотрел на строки Failed password for .... На машине «только по ключу» таких строк почти нет — jail по паролю будет ловить воздух.
Что ловить вместо этого — «неудачный вход» (неизвестный пользователь / неверный ключ / отклонённый баннер):
; /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s ; на Ubuntu это /var/log/auth.log; systemd-джорнал — %(sshd_journalmatch)s
backend = %(sshd_backend)s ; auto; на новых — systemd (читает journal, надёжнее файла)
maxretry = 3 ; после 3 неудач — бан
bantime = 1h ; на час; "-1" = навсегда
findtime = 10m ; окно, за которое считаем попытки
backend = systemd читает journalctl -u ssh напрямую (а не файл) — актуальнее на Ubuntu с systemd (механика журнала — Часть V); при нём не нужна правка rsyslog.
3. Проверка, что jail реально работает
sudo fail2ban-client status # список активных jail
sudo fail2ban-client status sshd # по sshd: текущие забаненные IP, счётчики
# проверить, что фильтр «видит» неудачу (тестовый запуск на логе без бана):
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conffail2ban-regex — отладчик: показывает, сколько строк лога совпали с фильтром. Если «0 match» — фильтр не ловит ваши атаки, правьте регулярку/logpath.
4. Jail для WordPress-формы входа (xmlrpc / wp-login)
Боты лезут не только в SSH, но и в wp-login.php (перебор паролей админа) и через xmlrpc.php (множитель запросов в одном). Тут у fail2ban две дороги:
- По nginx access-log (грубо, но без плагинов): фильтр по
POST /wp-login.phpс401/403или по «часто повторяющемуся IP на wp-login».
; jail.local — бан по wp-login через nginx access log
[nginx-wordpress]
enabled = true
port = http,https
filter = nginx-wordpress
logpath = /var/log/nginx/example.test-access.log
maxretry = 5
findtime = 60s
bantime = 30m
- На уровне приложения: WordPress-плагин, который сам логирует «неудачные логины» (например, Limit Login Attempts), и fail2ban читает его лог, или плагин сам банит. Границы: fail2ban по IP банит на firewall-уровне — дёшево и работает даже против тех, кто ещё не прошёл в приложение; плагин банит «внутри» WordPress — видит смысл попытки, но тратит PHP на каждый запрос. Оба допустимы; часто ставят плагин для логинов, fail2ban — для xmlrpc/flood. Про rate-limit на прикладном уровне — Защита API, rate-limiting.
⚠️ Осторожно с баном «по 401»: если у вас авторизация через
.htpasswd/nginx-auth на служебных URL (или ваш собственный мониторинг ходит с IP, который попадёт подmaxretry), fail2ban отбанит вас/его. Исключение из бана задаютignoreip = 127.0.0.1/8 203.0.113.7(ваш IP/подсеть) в[DEFAULT]— пропишите себя заранее, иначе отбаните сами себя.
5. banaction и совместимость с ufw
fail2ban банит, добавляя правило в файрвол. Для ufw-машин ставят action, который пишет туда же, где ufw (иначе два файрвола живут раздельно):
[DEFAULT]
banaction = ufw ; вместо iptables-multiport; бан идёт через ufw
ignoreip = 127.0.0.1/8 ::1 203.0.113.7
Чек-лист части
- Оставили `PasswordAuthentication no` (как №1) и дефолтный jail по «Failed password» — ботов не ловит, потому что неудачных паролей нет; ловите «invalid user / невалидный ключ».
- Не прописали `ignoreip` со своим адресом → в один прекрасный день отбанили сами себя по wp-login/SSH-джанку.
- Правят `jail.conf`/`filter.d/*.conf` → при `apt upgrade fail2ban` переписывается, ваши правила исчезают.
- Один и тот же IP и «ваша подсеть» не в `ignoreip`, а мониторинг ходит к `wp-login` → мониторинг отбанен, алерты замолчали.
- `bantime` «навечно» без ручного `fail2ban-client unban` — однажды нужный партнёр/VPN с динамическим IP не вернётся без вашей команды.
Часть V. SSH-харднинг: доводим то, что уже есть, и думаем про порт
Базу (ключ вместо пароля, PermitRootLogin no) заложили в №1. Здесь добавляем «следующий слой» и обсуждаем спорные вещи.
1. Настройки в /etc/ssh/sshd_config.d/ — не правим основной файл
Как и с fail2ban/nginx, на Ubuntu-системах конфиг SSH разбит на «drop-in»-и: /etc/ssh/sshd_config содержит строку Include /etc/ssh/sshd_config.d/*.conf. Создайте 50-hardning.conf (имена раньше 99 применяются позже и имеют приоритет — уточните поведение sshd -T после правки):
# бэкап перед правкой (механика «бэкап → правка → проверяем, что второй сеанс зашёл» из №1)
sudo cp -a /etc/ssh/sshd_config.d/ /etc/ssh/sshd_config.d.bak.$(date +%F) 2>/dev/null || true
sudo nano /etc/ssh/sshd_config.d/50-hardning.conf2. Keep-alive: рвём «висящие» и мёртвые сессии
Когда обрывается сеть/ноутбук заснул, SSH-сессия может «висать» открытой; боты же держат полуоткрытые соединения. Таймауты закрывают неактивные:
ClientAliveInterval 300 ; каждые 5 мин слать «ты живой?»
ClientAliveCountMax 2 ; если 2 ответа не пришлют — разорвать сессию
LoginGraceTime 30 ; сколько секунд даём на аутентификацию до отброса
MaxAuthTries 3 ; максимум попыток авторизации на одно соединение
LoginGraceTime/MaxAuthTries работают вместе с fail2ban: соединение с перебором рвётся быстрее, а попытки считаются.
3. AllowUsers / AllowGroups — пустить только нужных
# пускать только этого пользователя (и, при надобности, только от этого IP)
AllowUsers deploy@203.0.113.7
# или — по группе: в группу ssh-users включены только те, кому можно
AllowGroups ssh-users
Это «белый список по логину»: даже с украденным чужим ключом войти смогут только те, кто в списке. Для AllowGroups — создайте группу и добавьте deploy (sudo groupadd ssh-users; sudo usermod -aG ssh-users deploy).
4. Отключаем рискованное, что иногда включено «по привычке»
PasswordAuthentication no ; (уже из №1 — фиксируем, что не откатилось)
PermitRootLogin no ; (из №1)
PermitEmptyPasswords no ; вход без пароля — никогда
X11Forwarding no ; не нужен на headless-сервере
MaxStartups 10:30:60 ; мягко троттлить одновременные НЕаутентифицированные соединения
5. Смена порта 22: честно про плюсы и минусы
Многие советы «обязательно смените SSH-порт». Честно:
- Плюс реальный: боты сканируют массово именно
22; на нестандартном порту поток «шума» вauth.logрезко падает (проверено на живых VPS — в разы меньше строк). Это уменьшает нагрузку на fail2ban и мусор в логах. - Минус и ложное ожидание: смена порта не делает вас защищённее перед целевым атакующим, который сначала просканирует все 65535 портов (
nmap) и найдёт SSH. Этоsecurity through obscurity— «защита незнанием»: удобно против фонового шума, бесполезно против того, кто целится в вас.
Как менять — безопасно (то же правило «не резать себя»):
# 1) добавить второй порт, НЕ удаляя 22 (чтобы не остаться без входа, если что-то пошло не так)
sudo nano /etc/ssh/sshd_config.d/50-hardning.conf
# Port 22
# Port 2244
sudo systemctl reload ssh # перечитать конфиг, текущий сеанс не рвётся
# 2) ОТКРЫТЬ новый порт в firewall ДО проверки (!): sudo ufw allow 2244/tcp
# 3) из нового терминала зайти на 2244; если зашло — теперь можно оставить только Port 2244
sudo systemctl reload ssh
Забыть пункт 2 (открыть новый порт в ufw до переключения) — способ остаться с «SSH есть, входа нет».
6. Аккуратный рестарт/релоад без разрыва текущего входа
Важно: релоад не рвёт установленные сеансы (механику reload vs restart разбирали в Части III про systemd), поэтому меняя sshd_config, используйте reload и всегда держите второй терминал открытым до проверки. Проверяйте конфиг без рестарта:
sudo sshd -t # синтаксис конфига (молчит = ок)
# и обязательно — новый SSH-сеанс из другого терминалаЧек-лист части
- Поменяли порт SSH, но забыли `ufw allow <новый>` → потеряли вход (22 уже отключён).
- Пишут правки прямо в `/etc/ssh/sshd_config` → обновление пакета/конфиг-менеджмент их затирает; используйте `.d/`-drop-in.
- Оставили `PermitRootLogin yes` «чтобы rescue заходил» — rescue лучше делать ключом+через панель хостера, не открытым root-логином.
- Поставили `AllowUsers`, но не включили в список текущего пользователя → сам себя не пускаете после reload.
- Считают «нестандартный порт = защита» и на этом успокаиваются — это против ботов-шума, не против целевой атаки.
Часть VI. Аудит слушающих портов: ss -tulpn как штатная проверка
Самая практичная привычка из всей статьи: уметь в любой момент ответить «что у меня вообще открыто и кто это слушает». Firewall без аудита — это «включил и забыл», а забытый firewall и «дырявый Docker» (Часть III) дают сюрприз именно на рестарте.
1. Кто слушает наружу
# все TCP/UDP слушающие порты + PID и имя процесса
sudo ss -tulpn
# удобнее: только LISTEN + разбор адреса
sudo ss -tulnp | grep -E 'LISTEN|UNCONN'Как читать колонки:
- Recv-Q/Send-Q — очереди (для LISTEN обычно 0).
- Local Address:Port — главное.
127.0.0.1:3306= база локально (хорошо);0.0.0.0:3306или*:3306= база торчит из интернета (плохо);[::]:80= IPv6-any. - Process (
-p, нужен sudo) — какой демон занял порт (nginx, mariadbd, sshd, ваш dev-сервер).
2. Что ищем и как чиним
| Видим | Расшифровка | Чиним |
|---|---|---|
0.0.0.0:3306 mariadbd | база открыта наружу (хотя в №5 ждали сокет) | bind-address=127.0.0.1 (Часть I №5) + reload |
0.0.0.0:11211 memcached/6379 redis | кэш без пароля снаружи = лёгкий путь украсть сессии | bind 127.0.0.1/protected-mode yes + firewall |
0.0.0.0:3000 node/8000 | «временно» поднятый dev-сервер забыт | снять npm start или bind+ufw |
*:8081 docker-proxy | Docker опубликовал порт мимо ufw | публиковать 127.0.0.1:8081:8081 или DOCKER-USER (Часть III) |
3. «А что реально достижимо снаружи?» — не путать слушание и доступность
ss покажет слушание внутри машины. Проверить, что видит внешний мир, надо снаружи: с другой машины/онлайн-сканера или через ваш же firewall (ufw status). Отсюда связка: процесс слушает 0.0.0.0, но ufw не открыл порт → снаружи закрыто (firewall спас). Процесс слушает 127.0.0.1 → наружу не торчит в принципе (правильная первая линия). Идеальная картина — «слушает только loopback + firewall по умолчанию deny»: две независимые линии, а не одна.
Про
netstat/lsof: старыйnetstatчастично вытеснёнss(тот быстрее и нативнее в современном ядре).sudo lsof -i -P -n | grep LISTEN— альтернативный взгляд (по процессам).nmap— внешняя проверка «что видно снаружи»; используйтеnmapтолько на своей машине (скан чужих сетей без разрешения — атака).
4. Регулярность и привязка к финальной статье
Аудит портов — это не «настроил один раз», а привычка: раз в месяц/после «что-то поставили» прогон ss -tulpn и сверка с ожидаемым списком. Этим же способом («что открыто, кто слушает, нет ли лишнего») начинается и разбор аварии в финальной статье серии — «Диагностика: когда сервер не дышит» (Часть I — опрос сверху вниз: nginx → PHP-FPM → БД → ОС, и первым вопросом будет именно «что у нас слушает и что доступно»).
Чек-лист части
- Считают, что «ufw закрыл — значит процесс не торчит». Нет: процесс может слушать `0.0.0.0` (и светить banner в localhost-запросы), просто снаружи firewall прикрывает; надо чинить bind.
- После «временно» подняли dev-порт, забыли закрыть → через месяц он ещё слушает (см. `ss`) и firewall мог перезагрузиться/сброситься.
- Аудит портов ни разу не делали → не знаете, что реально слушает ваша машина, и «забытая дверь» surprise случается на рестарте.
Инструменты под задачу
Собрали правила ufw, конфиг fail2ban и sshd_config — проверьте их по чек-листу граблей из этой статьи: порядок enable/allow 22, не зарезан ли certbot на 80, актуален ли фильтр ban (пароль уже выключен), есть ли ignoreip, не правите ли вы конфиги пакетов напрямую. ИИ найдёт дыры и предложит исправления.
ИИ найдёт грабли firewall, fail2ban и SSH по чек-листу и предложит исправления.
Чек-лист граблей всей статьи
- `ufw enable` до `allow 22` / смена SSH-порта без открытия нового в ufw → потеряли вход.
- Закрыли 80 «лишнее» → certbot не продлевает → HTTPS гаснет.
- Закрыли исходящий трафик → `apt`/NTP/DNS не работают.
- fail2ban ловит «Failed password», а паролей давно нет (`PasswordAuthentication no`) — бан-джанк вхолостую; правьте на invalid-user.
- Не прописали `ignoreip` → отбанили себя или мониторинг.
- Правят `jail.conf`/`sshd_config`/конфиги пакетов напрямую → затрётся обновлением; используйте `.local`/`.d`-overrides.
- Два файрвола (`ufw` + ручные `nft`/Docker) борются — выберите один источник правил.
- Считают нестандартный SSH-порт «защитой» — это шумоподавление, не безопасность.
- Firewall «поставили и забыли», не делаете аудит `ss -tulpn` — лишние слушатели всплывают в худший момент.
Материал написан простым языком; любой англоязычный термин (периметр, firewall, пакет, порт, LISTEN, inbound/outbound, DROP/REJECT, ufw, nftables, netfilter,
DOCKER-USER, fail2ban, jail/filter/action/backend, keep-alive,AllowUsers,security through obscurity,ss,lsof,nmap, unattended-upgrades) объясняется при первом употреблении. Конфиги — для Ubuntu/Debian; версии (ufw 0.36+/0.41+, поведениеBackend: nft/iptables, имя SSH-юнитаsshпротивsshd) сознательно условны — проверяйте черезufw version,systemctl list-units | grep ssh. Домены/IP/номера портов условные (203.0.113.x— диапазон TEST-NET для примеров, как в №1). Правило «не остаться без входа» действует во всех частях: держите второй SSH-сеанс открытым и проверяйте новый до того, как закрыть старый или перезагрузиться. Тонкие механизмы (stateful NAT, conntrack-тюнинг,iptables-persistentvs nft-конфиг, гео-бан по AS-сетям на уровне провайдера/Cloudflare,sshguardкак альтернативу fail2ban) упомянуты лишь где нужно — это следующий уровень, когда базовый закрытый VPS уже работает стабильно.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
