Мониторинг шлёт тревогу, пользователи пишут, что «всё лежит», но SSH пускает на сервер. Значит, машина включена, и 22-й порт доступен. О домене, портах 80 и 443, TLS, веб-сервере и приложении это пока ничего не говорит… Кажется, что нужно просто перезапустить nginx, но перезапуск стирает часть следов и может превратить частичную аварию в полную беду. Под катом расскажу, как пройти путь запроса сверху вниз и найти место, где он остановился.

Шаг 1. Воспроизводим ошибку

Фраза «сайт не открывается» ещё ни о чём не говорит, сначала проверьте его из другой сети. Если проблема только у вас, возможны локальный DNS-кеш, запись в /etc/hosts, блокировка адреса или маршрут провайдера. Если жалуются все, запускайте curl на клиентской машине:

curl -v --connect-timeout 5 --max-time 15 https://example.com/

Также посмотрите на последнюю успешно пройденную стадию:

  • Could not resolve host ведёт к DNS. Если в выводе нет Connected to, то соединение не дошло до готового TCP или другой стадии подключения. 

  • Когда TCP уже установлен, а дальше висит TLS-рукопожатие, проверять нужно сертификаты и настройки TLS. 

  • Если HTTP-запрос ушёл, но ответа нет до --max-time, ищите проблему с прокси, приложением и его зависимостями.

Мгновенный Connection refused чаще всего значит, что на порту никто не слушает, либо фильтр ответил отказом. Тайм-аут чаще указывает на DROP, проблему маршрута или зависание на поздней стадии. Однако одной строки с ошибкой мало, смотрите весь вывод.

Коды HTTP тоже могут сузить поиск. При 502 шлюз получил некорректный ответ от upstream, при 504 — не дождался ответа вовремя, а 503 означает временную недоступность или перегрузку и может прийти как от прокси, так и от приложения.

Отдельно сравните IPv4 и IPv6:

curl -4 -v --connect-timeout 5 --max-time 15 https://example.com/

curl -6 -v --connect-timeout 5 --max-time 15 https://example.com/

Если IPv4 работает, а IPv6 нет, проверьте AAAA-запись, маршрутизацию и слушающий сокет. Сломанную запись лучше исправить сразу, а не надеяться на то, что клиенты переключатся на другой протокол. 

Шаг 2. Сверьте DNS и нужный сервер

Зачастую SSH идёт на новый сервер по IP, а домен остаётся на старом адресе, или A-запись исправили, AAAA забыли, а некоторые резолверы ещё держат ответ в пределах TTL. Для проверки проблемы используйте: 

dig +short A example.com

dig +short AAAA example.com

В целом, сравните адреса с реальной схемой. За NAT публичного IP внутри виртуалки может не быть, а домен за CDN не должен указывать на origin. Нужный сервер проверяйте без правки DNS (некоторые команды буду писать с слэшами-переносами, чтобы в кучку всё не сваливалось):

curl -v \

  --connect-timeout 5 \

  --max-time 15 \

  --resolve example.com:443:203.0.113.10 \

  https://example.com/

--resolve подменяет IP, сохраняя доменное имя в HTTP и SNI. Если обычный запрос падает, а origin отвечает, смотрите DNS, CDN или балансировщик. Сервер, который принимает трафик только с адресов CDN, должен отклонить прямой запрос — его проверяют из разрешённого источника, либо через CDN.

Шаг 3. Узнайте, кто слушает 80 и 443

Дальше заходите по SSH и смотрите сокеты в текущем сетевом пространстве:

sudo ss -ltnp 'sport = :80'

sudo ss -ltnp 'sport = :443'

Пустой вывод означает, что в текущем network namespace нужный порт никто не слушает. Если строки есть, посмотрите, к какому адресу привязан сокет: 127.0.0.1:443 доступен только с самого сервера, а 0.0.0.0:443 принимает IPv4-соединения на всех его интерфейсах. Запись [::]:443 относится к IPv6 — может ли такой сокет одновременно принимать IPv4-соединения, зависит от параметра IPV6_V6ONLY и настроек приложения. Быстрее всего это проверить отдельными запросами (писал их выше). Для nginx команды такие:

sudo systemctl status nginx --no-pager -l

sudo nginx -t

sudo journalctl -u nginx --since '-30 minutes' --no-pager

Active: active (running) подтверждает, что процесс жив, но это не говорит о правильности виртуального хоста и upstream. А nginx -t проверяет синтаксис и доступность указанных в конфигурации файлов. Также после теста перечитайте конфиг без остановки старых воркеров:

sudo systemctl reload nginx

Для Apache используйте sudo apachectl configtest — юнит обычно называется apache2 в Debian-подобных системах и httpd в RHEL-подобных. Знаю, что вы всегда проверяете конфиг перед перезапуском, но на всякий случай решил напомнить. 

Шаг 3. Разделите аварию

Локальный запрос проверяет веб-сервер без внешнего маршрута. Для HTTP передайте правильный Host:

curl -v --noproxy '*' --max-time 15 http://127.0.0.1/ -H 'Host: example.com'

Для HTTPS нужен ещё и SNI:

curl -v --noproxy '*' --max-time 15 --resolve example.com:443:127.0.0.1 https://example.com/

Если сертификат уже известен как проблемный, запрос можно один раз повторить с -k. В мониторинг и рабочие скрипты этот ключ переносить не рекомендую.

Локальный 200 или ожидаемый редирект подтверждает, что веб-сервер обработал запрос с нужными Host и SNI через loopback. Если сайт по-прежнему недоступен, то проблему нужно искать между клиентом и сервером — в локальном файрволе, сетевом экране хостера, балансировщике, CDN или маршрутизации.

Локально работает, снаружи тишина

Если ситуация такая, то начните с правил на самом сервере. Сначала разберитесь, какой firewall backend используется. Для nftables команда будет такой:

sudo nft list ruleset

Если хост использует iptables, одного iptables -S мало, ведь он показывает только filter table текущего семейства. Для полной картины пригодятся:

sudo iptables-save

sudo ip6tables-save

К слову, Docker создаёт правила для bridge-сетей и публикации портов, а опубликованный порт способен обойти обычную логику ufw. Поэтому для начала уточните сетевой режим и firewall backend Docker. 

Следом глядите на сетевой экран в панели хостера — он находится за пределами гостевой ОС, поэтому в локальном наборе не появится. То же относится к балансировщику и списку разрешённых адресов на origin.

Если на сервере установлен Fail2ban, проверьте, не заблокировал ли он IP-адрес, с которого выполняется внешний запрос. В Fail2ban правила сгруппированы по jail, то есть по отдельным наборам фильтров для SSH, nginx и других сервисов. Первая команда покажет активные jail:

sudo fail2ban-client status

Затем подставьте имя нужного jail во вторую команду. В её выводе будет список заблокированных адресов:

sudo fail2ban-client status ИМЯ_JAIL

Если правила выглядят нормально, смотрите пакеты:

sudo tcpdump -ni any 'tcp port 80 or tcp port 443'

В другом окне повторите внешний curl. Если нет SYN на origin, значит, запрос потерялся раньше или ушёл на другой IP. Если SYN есть, но SYN-ACK не уходит, проверяйте firewall, policy routing и сокет. Если SYN-ACK ушёл, но клиент его не видит, смотрите обратный маршрут и провайдера.

Фронтенд отвечает, приложение нет

Если nginx принимает запрос, но возвращает 502 или 504, то проблема чаще всего находится между ним и приложением. То же касается ситуации, когда статическая страница открывается, а динамические разделы не работают. Тут смотрите на активную конфигурацию через sudo nginx -T и найдите директиву, по которой nginx передаёт запросы дальше: proxy_pass, fastcgi_pass или uwsgi_pass.

После этого обратитесь к upstream напрямую. Используйте тот же протокол, адрес, порт, Host и путь, которые указаны в конфигурации nginx:

sudo ss -ltnp 'sport = :8000'

curl -v --max-time 10 http://127.0.0.1:8000/health -H 'Host: example.com'

Порт 8000 и путь /health привёл для примера. Подставьте адрес и URL из конфигурации nginx. Если отдельного healthcheck нет, запросите рабочий динамический маршрут.

HTTP-upstream может быть подключён и через Unix-сокет. В таком случае смотрите через: 

curl --unix-socket /run/myapp/app.sock http://localhost/health

Сокеты из fastcgi_pass и uwsgi_pass работают не по HTTP, поэтому обычным curl их не проверить. Сначала проверьте наличие сокета, права на него, статус PHP-FPM и журналы. Для полноценного запроса потребуется FastCGI-клиент, например, cgi-fcgi, с параметрами конкретного приложения.

Для контейнеров нужны другие команды:

docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

docker logs --since 30m --tail 200 myapp

docker inspect --format '{{json .State.Health}}' myapp

docker port myapp

Пустой docker port норма, если прокси находится в той же сети или используется host network. Up означает лишь, что жив PID 1, а Health появится только при настроенном healthcheck.

Базу проверяйте по тому же адресу и порту, которые использует приложение:

pg_isready -h 127.0.0.1 -p 5432 -d appdb

mysqladmin --host=127.0.0.1 --port=3306 ping

Напомню, имена юнитов зависят от пакета: postgresql.service бывает мета-юнитом, а MySQL может называться mysql. При 504 смотрите также блокировки, медленные запросы и пул соединений.

Шаг 4. Проверьте TLS 

Для OpenSSL 1.1.1+/3.x цепочку и соответствие имени можно проверить так:

openssl s_client \

  -connect 203.0.113.10:443 \

  -servername example.com \

  -verify_hostname example.com \

  -verify_return_error \

  -brief </dev/null

-servername передаёт SNI, -verify_hostname проверяет имя, а -verify_return_error не даёт s_client продолжить работу после ошибки проверки. А все сертификаты, присланные сервером, выводит команда:

openssl s_client \

  -connect 203.0.113.10:443 \

  -servername example.com \

  -showcerts </dev/null

На старой системе сначала смотрите openssl version (часть ключей может отсутствовать). Сертификат выбранного IP легче всего проверить через curl --resolve.

Шаг 5. Обратите внимание на ресурсы  

Если процессы на месте, но запросы висят, смотрите ресурсы:

df -h

df -i

free -h

uptime

vmstat 1 5

sudo journalctl -k -g 'oom|out of memory|killed process'

Первая строка vmstat 1 содержит средние значения с момента загрузки, текущую картину дают последующие. В systemd версий до 237 нет journalctl -g, там используйте:

sudo journalctl -k --no-pager \

  | grep -Ei 'oom|out of memory|killed process'

Если df -h показывает заполненную файловую систему, выясните, какой каталог занял место. Например, содержимое /var можно проверить так:

sudo du -xhd1 /var 2>/dev/null | sort -h

Ключ -x не даёт du переходить на другие файловые системы, а -d1 ограничивает проверку каталогами первого уровня. После уже можете изучить самый крупный каталог.

К слову, показания df и du иногда не совпадают — это происходит, когда файл уже удОлён из каталога, но процесс продолжает держать его открытым. Имени у файла больше нет, поэтому du его не видит, однако занятые блоки всё ещё учитываются в df. Найти такие файлы поможет команда:

sudo lsof +L1

Место освободится, когда процесс закроет файл, но в слепую его не завершайте. 

На счёт df -i. Если закончились inode, свободное место на диске не поможет — система не сможет создать новый файл. Причиной могут быть каталоги с множеством мелких файлов, например, кэшем, сессиями или очередью.

Если с диском всё в порядке, переходите к памяти и I/O. В выводе free -h смотрите прежде всего на available, а в vmstat следите за si и so. Постоянный обмен данными со свопом при низком available говорит о нехватке памяти. Записи Killed process в журнале ядра подтверждают, что до процессов уже добрался OOM Killer (про него рассказывал тут).

Высокий load при свободном CPU чаще всего связан не с вычислениями, а с процессами в состоянии D, которые ждут диск, NFS или другой I/O. На виртуалке проверяйте и %st в top — высокие цифры значат, что гипервизор регулярно забирает процессорное время у гостевой системы.

Есть ли короткий маршрут до причины

Начните с внешнего curl и определите, на какой стадии обрывается запрос. Затем проверьте DNS через dig и нужный origin через curl --resolve. На сервере посмотрите слушающие сокеты, состояние nginx и конфигурацию через nginx -t.

После этого выполните локальный запрос. Если локально сайт работает, а снаружи нет, проверяйте файрвол, сетевой экран хостера, балансировщик, CDN и маршрутизацию. Если nginx возвращает 502 или 504, переходите к upstream, приложению, сокетам и базе. TLS, диск, память и I/O проверяйте по симптомам, которые уже показали curl, журналы и состояние сервисов.

После починки НАСТРОЙТЕ УЖЕ внешний мониторинг, ротацию логов и алерты на диск, inode, память и сервисы. Если используете Certbot, прогоните certbot renew --dry-run. Автозагрузку проверяйте по реальным именам юнитов:

systemctl is-enabled nginx

systemctl is-enabled myapp

systemctl is-enabled postgresql@16-main

Последний юнит я привёл в пример, имя кластера и версия будут вашими. И не закрывайте последнюю SSH-сессию, пока не проверите новое подключение и запасную консоль хостера. К упавшему сайту слишком легко добавить ещё одну аварию, но уже на порту 22.

Делитесь в комментариях, сталкивались ли вы с такой ситуацией? Что в итоге оказалось причиной?

© 2026 ООО «МТ ФИНАНС»

Комментарии (17)


  1. Litemanager_soft
    19.08.2026 10:34

    спасибо за проделанную работу!


  1. event1
    19.08.2026 10:34

    В целом хорошо, но

    Мгновенный Connection refused чаще всего значит, что на порту никто не слушает

    если в Линуксе на порту никто не слушает, будет таймаут. Connection refused — это результат icmp-ответа (вроде port unreachable). Бывает если запрос попал в netfilter target REJECT


    1. lexore
      19.08.2026 10:34

      По умолчанию, будет как раз refused. Попробуйте сами:

      $ time -p curl localhost:888
      curl: (7) Failed to connect to localhost port 888 after 0 ms: Couldn't connect to server
      real 0.00
      user 0.00
      sys 0.00

      timeout будет, если настройки уже не дефолтные, или запрос дропается в iptables.


      1. event1
        19.08.2026 10:34

        Действительно, ваша правда. А я откуда-то был уверен, что будет таймаут.


        1. Chimera87
          19.08.2026 10:34

          зависит от того заблокирован ICMP или нет на конечном сервере.
          Если нет то он вполне может ответить сервисным сообщением что порт не открыт поэтому будет refused. Если ICMP забрит злым фаерволлом то будет таймаут....


          1. event1
            19.08.2026 10:34

            Не, там прямо в реализации TCP, если сокет не найден отвечает rst. Icmp будет если только если есть правило с reject


    1. 3cky
      19.08.2026 10:34

      Если порт никто не слушает, ядро Linux отправит TCP-пакет RST и вызов connect() в клиенте вернет ошибку ECONNREFUSED. Таймаут может быть в результате работы netfilter с правилом по умолчанию DROP.


    1. RTFM13
      19.08.2026 10:34

      Уже ответили что по умолчанию идёт отлуп. Но отлупы часто отключают потому, как их используют для ддоса отправляя запрос на открытие сессии от адреса жертвы и ваш отлуп идёт на адрес жертвы. По этому отлупы рекомендуется фильтровать или ограничивать особенно на мощных популярных серверах.

      Таким образом наличие отлупа можно считать надежным признаком неиспользуемого порта. Но отсутствие отлупа может говорить скорее о фильтрации отлупов нежли о повисшем сервисе.


  1. andreymal
    19.08.2026 10:34

    Когда TCP уже установлен, а дальше висит TLS-рукопожатие

    В наше время это с высокой вероятностью означает роскомнадзор


  1. gp23
    19.08.2026 10:34

    Это похоже на конспект беседы с ИИ 8))


    1. progreccor
      19.08.2026 10:34

      это он и есть


  1. JBFW
    19.08.2026 10:34

    ps ax | grep nginx покажет наличие работающего процесса

    tcpdump -i ifname port XXX - покажет активность по обмену данными, http(s)/приложение/база

    docker ps / ps -a покажет работу контейнеров

    df -H покажет остатки места на дисках

    dmesg покажет аппаратные проблемы

    А потом разбираться, почему что-то не работает, или работает криво: то ли трафик не доходит, то ли приложение упало, то ли базы нет, и вот это всё - почему.

    Еще бывает удобно когда каждое приложение пишет о своих трудностях в /var/log/appname/error.log - но теперь модно засовывать это всё в systemd, который сам может глючить по причине нехватки памяти, диска, или кривизны настройки сервера.


  1. fUS1ONd
    19.08.2026 10:34

    Либо как в статье делать, либо промпт для клода: "разберись чо за фигня"


    1. vyacheslavteplyakov
      19.08.2026 10:34

      Тогда уж дать Клоду/кодексу ссылку на этот пост и попросить сделать из него скилл...


      1. progreccor
        19.08.2026 10:34

        он же его и писал


  1. Chimera87
    19.08.2026 10:34

    Вообще в таких случаях сразу быстро проверяю 3 вещи:

    1) Внимательно присмотреться к тому что пишет браузер при обращении к сайту. (см ниже)

    2) маршрут от сервера до клиента: вполне на каком нибудь из промежуточных хопов могло что нибудь случиться\отвалиться\перестроиться маршрут в динамике

    3) нагрузку на сервер и свободное место: какойнибудь богом забытый дебаг лог по стечению обстоятельств мог разарастись до объема диска....

    Так вто чтобы прямо "оно само упало" - это большая редкость.
    Еще надо внимательно присмотреться к тому что выплевывает браузер: либо Refused тогда все понятно, либо он сразу может написать например про проблемы с DNS (не может обнаружить узел - тогда собственно залезать на вебсервер не надо - надо сразу идти к DNS резолверам) либо он начинает выплевывать какие-то внутренние ошибки вебсервера - 4xx или 5xx - тогда лезем глубже в логи сайта и смотрим что же там происходит.....


  1. Borelli
    19.08.2026 10:34

    Для более полной красоты текста, можно было бы переписать части:

    Если сертификат уже известен как проблемный, запрос можно один раз повторить с -k. В мониторинг и рабочие скрипты этот ключ переносить не рекомендую.

    Для красоты стоит добавить что это и почему не рекомендуется. -k или полный ключ --insecure - отключает проверку SSL/TLS сертификатов сервера. "Curl по-умолчанию проверяет валидность сертификата, его срок действия, доверие к выпустившему его центру (CA) и соответствие имени домена. Если с сертификатом что-то не так, утилита обрывает соединение и выдает ошибку."

    Локально работает, снаружи тишина

    Там в трёх абзацах сразу в кучу свалили nftables, iptables и ufw. Казалось бы, тут по верхам, но лучше было бы более структурировано перечислить возможные варианты.