Активация сокетов в systemd: запуск сервисов по запросу и безопасная настройка | AdminWiki

Активация сокетов в systemd: запуск сервисов по запросу и безопасная настройка

29 августа 2026 15 мин. чтения
Содержание статьи

systemd socket activation позволяет заранее открыть Unix- или TCP-сокет, а сам сервис запускать только после первого обращения клиента. За создание listening endpoint отвечает юнит .socket, за процесс сервера отвечает связанный юнит .service. Пока запросов нет, основной процесс может не занимать память и не выполнять лишнюю инициализацию.

Типовая схема выглядит так: systemd загружает example.socket, создает сокет и ждет подключения; клиент устанавливает соединение; systemd запускает example.service и передает ему файловый дескриптор. Для Accept=no сервис получает listening socket и сам вызывает accept(). Для Accept=yes systemd принимает соединение сам и запускает отдельный экземпляр сервиса для этого клиента.

В статье разобраны связь .socket и .service, выбор между Accept=yes и Accept=no, Unix и TCP, права SocketUser, SocketGroup и SocketMode, миграция существующего демона, диагностика и план отката. Перед настройкой проверьте версию systemd и поддержку socket activation самим приложением.

Как работает systemd socket activation

Связь юнитов .socket и .service

По умолчанию systemd связывает юниты с одинаковым базовым именем. Пара example.socket и example.service означает, что активация сокета запускает сервис example.service. В socket-юнит помещают параметры endpoint, например ListenStream= или ListenDatagram=. В service-юнит помещают пользователя, команду запуска, окружение, зависимости и ограничения процесса.

[Unit]
Description=Example server socket

[Socket]
ListenStream=/run/example/example.sock
SocketUser=root
SocketGroup=example
SocketMode=0660

[Install]
WantedBy=sockets.target

Минимальный сервис для Accept=no может выглядеть так:

[Unit]
Description=Example server

[Service]
Type= simple
ExecStart=/usr/local/bin/example-server
User=example
Group=example
Restart=on-failure

В реальном файле между Type= и значением не должно быть пробела: используйте Type=simple. Приложение должно уметь принимать уже открытый дескриптор systemd. Обычно оно получает его через переменные LISTEN_PID, LISTEN_FDS и протокол активации systemd. Библиотеки приложения могут скрывать эту работу за готовым API.

Проверьте итоговую конфигурацию и связи командами:

sudo systemd-analyze verify /etc/systemd/system/example.socket
sudo systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl cat example.socket
sudo systemctl cat example.service
sudo systemctl status example.socket
sudo systemctl list-dependencies example.socket
sudo systemctl list-sockets

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

Что происходит при первом обращении

После команды systemctl enable --now example.socket сокет создается сразу. Процесс сервера при этом может оставаться в состоянии inactive. Это нормальное состояние для ленивого запуска.

  1. systemd читает настройки .socket и открывает Unix-файл или TCP endpoint;
  2. клиент подключается к уже существующему сокету;
  3. systemd активирует связанный сервис;
  4. сервис получает дескриптор и начинает обработку запроса;
  5. последующие соединения обслуживаются тем же процессом при Accept=no либо отдельными экземплярами при Accept=yes.

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

После остановки сервиса сокет способен остаться активным и снова запустить службу при новом обращении. Это зависит от того, остановили ли вы только .service или оба юнита. Команда systemctl stop example.service не равна остановке example.socket.

Когда socket activation оправдана

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

Постоянный запуск лучше подходит для сервисов с высокой частотой запросов, длительным прогревом, постоянными фоновыми задачами, долгими соединениями или строгими требованиями к задержке первого запроса. Socket activation не исправляет архитектурные ограничения приложения и не заменяет балансировщик, firewall или аутентификацию.

До настройки проверьте четыре условия:

  • демон умеет работать с переданным файловым дескриптором;
  • его протокол совместим с выбранной моделью приема соединений;
  • первичная задержка укладывается в таймаут клиента;
  • старый менеджер процесса не будет одновременно запускать тот же демон.

Accept=yes и Accept=no: какую модель запуска выбрать

Accept=no для многоклиентского демона

Accept=no используется чаще всего. systemd передает сервису listening socket, а приложение само выполняет accept(), распределяет клиентов между потоками или worker-процессами и управляет очередью соединений.

[Socket]
ListenStream=127.0.0.1:9100
Accept=no

[Service]
ExecStart=/usr/local/bin/example-server --systemd
User=example
Group=example

Такой режим подходит серверу, который рассчитан на множество клиентов и долгую работу. Один service-юнит может принимать новые соединения после запуска. В командной строке не нужно повторно указывать bind-адрес, если программа ожидает дескриптор systemd. Иначе процесс попытается занять тот же порт и завершится с ошибкой Address already in use.

Проверьте документацию конкретного демона: наличие обычного режима TCP-сервера еще не гарантирует поддержку socket activation. Для самостоятельной адаптации приложение должно корректно читать LISTEN_FDS и LISTEN_PID или использовать совместимую библиотеку.

Accept=yes для отдельного процесса на соединение

При Accept=yes systemd сам принимает входящее соединение. Для каждого клиента он запускает отдельный экземпляр шаблонного сервиса, обычно с именем example@.service. Процесс получает уже принятое соединение через стандартный ввод, стандартный вывод или механизм, предусмотренный конкретной реализацией systemd.

[Socket]
ListenStream=127.0.0.1:9200
Accept=yes

[Install]
WantedBy=sockets.target
[Service]
ExecStart=/usr/local/bin/example-session
User=example
Group=example
StandardInput=socket
StandardOutput=socket

Этот режим удобен для простых session-oriented протоколов, где один процесс обслуживает одно соединение и завершает работу. Каждый экземпляр получает собственный жизненный цикл и отдельные ограничения. Цена такой модели, это расход памяти и времени на создание процесса для каждого подключения. При большом числе клиентов потребуется контролировать лимиты systemd и нагрузку на планировщик.

Типичные ошибки при выборе Accept

  • Демон с Accept=no сразу завершается. Он ожидает обычный порт и не умеет читать переданный listening descriptor.
  • Приложение с Accept=yes пытается вызвать bind(). Оно должно работать с уже принятым соединением.
  • Шаблонный сервис отсутствует. При Accept=yes проверьте наличие нужного example@.service.
  • Сервис получает неверный stdin. Проверьте StandardInput=socket, протокол приложения и записи в журнале.
  • Соединения создают слишком много процессов. Для многоклиентского демона выбирайте Accept=no.

Unix socket systemd или TCP: выбор транспорта

Локальный Unix-сокет через ListenStream

Unix domain socket, или AF_UNIX, подходит для IPC внутри одного хоста. Он не публикует порт в сеть, поддерживает обычные права файловой системы и позволяет ограничить доступ владельцем и группой.

[Socket]
ListenStream=/run/example/example.sock
SocketUser=root
SocketGroup=example
SocketMode=0660
DirectoryMode=0750
RemoveOnStop=yes

[Install]
WantedBy=sockets.target

Каталог /run очищается при перезагрузке. Создайте его через RuntimeDirectory=example в service-юнитe либо отдельный tmpfiles-конфиг, если это требуется приложению. Не создавайте каталог вручную в ExecStartPre с широкими правами без необходимости.

[Service]
RuntimeDirectory=example
RuntimeDirectoryMode=0750
ExecStart=/usr/local/bin/example-server --systemd
User=example
Group=example

Проверьте наличие endpoint и его права:

sudo ss -lx | grep example.sock
sudo stat -c '%A %U %G %n' /run/example/example.sock
sudo ls -l /run/example/example.sock

Клиент должен поддерживать Unix-адрес. Для HTTP-протокола используйте клиент, умеющий подключаться через Unix socket, либо локальный прокси. Права файла сокета ограничивают сам факт подключения, но не проверяют права внутри протокола. Если API выполняет опасные операции, добавьте прикладную аутентификацию.

TCP-сокет и границы сетевого доступа

TCP выбирайте для межпроцессного взаимодействия через сеть или для приложений, которые не умеют работать с AF_UNIX. Адрес bind задается явно:

[Socket]
ListenStream=127.0.0.1:9100

[Install]
WantedBy=sockets.target

127.0.0.1:9100 принимает подключения только с IPv4 loopback. Запись 0.0.0.0:9100 открывает endpoint на всех IPv4-интерфейсах. Для IPv6 отдельно проверьте адрес, параметр dual-stack и настройки firewall.

sudo ss -ltnp | grep ':9100'
sudo systemctl status example.socket
sudo journalctl -u example.socket -b

TCP-сокет не имеет аналога SocketMode. Доступ контролируют bind-адрес, firewall, сетевые ACL и аутентификация приложения. Публикация на внешнем интерфейсе без этих ограничений создает сетевой сервис, даже если сам процесс работает от непривилегированного пользователя.

Для локального административного интерфейса обычно выбирайте Unix socket с режимом 0660. Для межхостового доступа используйте TCP с конкретным bind-адресом, фильтрацией firewall и проверкой подлинности клиента.

Безопасная настройка прав доступа к сокету

SocketUser и SocketGroup

SocketUser задает владельца Unix-сокета, а SocketGroup задает его группу. Практичная схема для локального API: владелец root, отдельная группа example, клиентские процессы запускаются пользователями из этой группы.

sudo groupadd --system example
sudo usermod -aG example client-user
sudo systemctl daemon-reload
sudo systemctl restart example.socket

Новое членство в группе обычно появится после нового входа пользователя. Для проверки используйте id client-user. Не добавляйте в группу всех пользователей хоста: членство дает доступ к интерфейсу, а через него могут быть доступны административные операции.

SocketMode без избыточного доступа

Выбирайте режим по модели доверия:

  • 0600, если подключается только владелец;
  • 0660, если доступ нужен владельцу и выделенной группе;
  • 0666 только при доказанной необходимости для всех локальных пользователей, что для административных API обычно неприемлемо.

Проверяйте права от имени реального клиента. Тест под root не показывает, сможет ли обычный процесс подключиться или будет ли доступ ошибочно открыт группе.

sudo -u client-user stat /run/example/example.sock
sudo -u client-user curl --unix-socket /run/example/example.sock http://localhost/health
sudo -u unauthorized-user curl --unix-socket /run/example/example.sock http://localhost/health

Модель угроз и дополнительные ограничения

Защитите четыре границы: кто может открыть соединение, какие операции доступны после подключения, от какого пользователя работает сервис и какие файлы он может изменять. Права Unix-сокета решают первую задачу. Они не ограничивают команды внутри API.

Для service-юнита отдельно оцените User, Group, NoNewPrivileges, PrivateTmp, ProtectSystem, ProtectHome, RestrictAddressFamilies и CapabilityBoundingSet. После каждого ограничения запускайте тестовый запрос и изучайте журнал.

Руководство по проверке sandbox-параметров и systemd-analyze security есть в статье о безопасной настройке systemd-сервисов. Секреты приложения не храните в командной строке или открытом environment-файле. Для этой задачи используйте отдельный механизм credentials, описанный в руководстве по LoadCredential и systemd-creds.

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

В таблице приведено сравнение способов передачи секретов. Оно пригодится при переносе демона на новый service-юнит, когда старое окружение часто копируют без проверки.

СпособХранениеВидимостьЗащитаСценарий применения
.envОткрытый текст в файле проектаДоступен процессам и пользователям с правами чтенияЗависит от владельца, режима и исключения из GitЛокальная разработка и временные тесты
EnvironmentFileОткрытый текст в отдельном файлеЗначения попадают в окружение процессаПрава файла и ограничения доступа к процессуСовместимость со старым демоном
LoadCredential=Отдельный файл credentialСервис получает путь к закрытому credential-каталогуМинимальные права и изоляция credentialsПередача паролей и токенов без обычных переменных окружения
LoadCredentialEncrypted=Зашифрованный credentialВ исходном файле секрет не виден в открытом видеШифрование systemd-creds и права доступа к ключевому материалуХранение секрета на диске при повышенных требованиях

Матрица ниже показывает практическое распределение владельцев и прав для типовой схемы. Значения прав приведены как ориентир, их проверяют под конкретной системой.

СекретВладелецПраваРасположениеОсновная угрозаМера защиты
Пароль базы данныхroot0600Закрытый credential-файлЧтение другими пользователямиВыдавать доступ только unit и владельцу каталога
API-токенroot0600Зашифрованный credentialУтечка из резервной копииШифровать через systemd-creds и ограничить доступ к резервным копиям
Файл конфигурацииroot0640/etc/exampleПодмена параметров запускаГруппа только для администраторов, контроль изменений
Unix-сокетroot0660/run/example/example.sockДоступ неавторизованного локального процессаВыделенная группа и проверка реальным клиентом

Зависимости, автозапуск и управление жизненным циклом

Что включать при загрузке: .socket или .service

Для запуска по запросу обычно включают .socket:

sudo systemctl daemon-reload
sudo systemctl enable --now example.socket
sudo systemctl status example.socket
sudo systemctl is-enabled example.socket
sudo systemctl is-active example.service

После этого example.socket должен быть активен, а example.service может оставаться неактивным до первого обращения. Команда systemctl start example.service запускает сервис напрямую. Она не создает отсутствующий socket endpoint и может привести к конфликту, если приложение само пытается занять адрес.

Для постоянного запуска процесса включайте .service, но это уже другая модель. Не включайте оба юнита без понимания их связей и поведения при ручном старте.

Порядок запуска и зависимости

After= задает порядок, но сам по себе не запускает зависимость. Wants= просит systemd запустить связанный юнит, а Requires= создает более жесткую зависимость. Для сетевого TCP-сервиса часто используют:

[Unit]
Wants=network-online.target
After=network-online.target

Conflicts= помогает исключить одновременный запуск старой и новой схемы. PartOf= связывает остановку и перезапуск жизненного цикла, но не заменяет Requires=. Проверяйте фактические связи:

systemctl list-dependencies example.socket
systemctl show example.socket -p Requires -p Wants -p After -p Triggers
systemctl show example.service -p PartOf -p Conflicts

Поле Triggers в выводе помогает увидеть, какой юнит активирует сокет. Для загрузки системы обычно используют WantedBy=sockets.target. Для ручного теста достаточно start, для остановки endpoint используйте stop, а после изменения файлов выполните daemon-reload.

Миграция существующего демона на socket activation

Аудит текущего unit-файла и процесса

Сначала зафиксируйте исходное состояние:

sudo systemctl cat old-example.service
sudo systemctl show old-example.service
sudo systemctl status old-example.service
sudo systemctl list-dependencies old-example.service
sudo ss -ltnp
sudo ss -lx

Сохраните ExecStart, пользователя, группу, EnvironmentFile, рабочий каталог, RuntimeDirectory, лимиты, Restart, зависимости и адрес прослушивания. Проверьте, не запускает ли демон supervisor, контейнерный runtime или другой unit. Два менеджера процессов часто приводят к конфликту порта и циклическим перезапускам.

Зафиксируйте версии:

cat /etc/os-release
systemd --version
/usr/local/bin/example-server --version

Параметры socket activation и поведение Accept зависят от версии systemd и самого демона. Сверяйте поддерживаемые директивы через локальные справочные страницы и документацию установленного приложения.

Подготовка .socket и .service

Для безопасного теста используйте отдельный Unix-путь или свободный loopback-порт. Это позволяет проверить передачу дескриптора, не занимая рабочий endpoint.

[Unit]
Description=Example test socket

[Socket]
ListenStream=127.0.0.1:19100
Accept=no

[Install]
WantedBy=sockets.target
[Unit]
Description=Example test service

[Service]
Type=simple
ExecStart=/usr/local/bin/example-server --systemd --port-disabled
User=example
Group=example
Restart=on-failure
NoNewPrivileges=yes
PrivateTmp=yes

Параметр ExecStart нужно адаптировать под приложение. Уберите опцию самостоятельного bind, если сервер получает endpoint через systemd. Type=simple подходит процессу, который остается на переднем плане. Type=notify используйте, если приложение действительно отправляет systemd уведомление о готовности. Type=forking нужен старым демонам, которые переходят в фон и создают ожидаемый PID-файл, но для socket activation он усложняет контроль.

Если приложение не умеет принимать дескриптор systemd, есть три варианта: оставить обычный запуск, использовать встроенный адаптер или прокси, который принимает socket activation и передает трафик приложению. Не подменяйте эту проверку случайным изменением Accept.

Переключение и план отката

Перед переключением сохраните unit-файлы, drop-in-каталоги, конфигурацию демона, версии и базовый результат запроса. Для системных файлов используйте отдельный каталог резервной копии с ограниченным доступом:

backup=/root/example-backup-$(date +%Y%m%d-%H%M%S)
sudo mkdir -m 0700 "$backup"
sudo systemctl cat old-example.service | sudo tee "$backup/old-example.service.txt" >/dev/null
sudo systemctl show old-example.service | sudo tee "$backup/old-example.show.txt" >/dev/null
sudo cp -a /etc/systemd/system "$backup/systemd-system"
sudo cp -a /etc/example "$backup/example-config"

После проверки файлов выполните:

sudo systemd-analyze verify /etc/systemd/system/example.socket
sudo systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl stop old-example.service
sudo systemctl disable old-example.service
sudo systemctl enable --now example.socket
sudo systemctl status example.socket

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

sudo systemctl stop example.socket
sudo systemctl disable example.socket
sudo systemctl daemon-reload
sudo systemctl enable --now old-example.service

Если старый unit заменен drop-in-файлом, восстановите именно исходный drop-in и повторите daemon-reload. Не удаляйте рабочую конфигурацию до завершения периода наблюдения.

Диагностика systemd socket activation

Проверка unit-файлов и состояния systemd

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

sudo systemd-analyze verify /etc/systemd/system/example.socket
sudo systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl status example.socket --no-pager
sudo systemctl status example.service --no-pager
sudo systemctl cat example.socket
sudo systemctl show example.socket -p LoadState -p ActiveState -p SubState -p FragmentPath -p Triggers
sudo systemctl list-sockets

Для socket-юнита нормальны Active: active (listening) и наличие нужного адреса. Для service-юнита до первого обращения допустимо inactive. После запроса ожидайте переход в активное состояние или запись об успешном завершении короткого процесса.

Проверка endpoint через ss

sudo ss -lx
sudo ss -ltnp
sudo ss -lx | grep example.sock
sudo ss -ltnp | grep ':9100'

Для Unix-сокета ищите ожидаемый путь и состояние listening. Для TCP проверьте bind-адрес, порт и состояние LISTEN. Отсутствие процесса сервиса при активном .socket до первого подключения не считается ошибкой. Если порт занят, найдите владельца процесса и его unit, прежде чем менять ListenStream.

Анализ journalctl и тестового запроса

sudo journalctl -u example.socket -b --no-pager
sudo journalctl -u example.service -b --no-pager
sudo journalctl -fu example.service

Ищите сообщения Address already in use, Permission denied, ошибки пути, немедленный exit, отказ приложения читать переданный descriptor и повторные активации. Затем выполните запрос подходящим клиентом:

curl --unix-socket /run/example/example.sock http://localhost/health
curl http://127.0.0.1:9100/health
nc -vz 127.0.0.1 9100

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

Таблица помогает быстро сопоставить симптом с проверкой и исправлением.

СимптомВероятная причинаКоманда проверкиБезопасное исправление
Сокет не активируетсяОшибка синтаксиса или не выполнен daemon-reloadsystemd-analyze verify, systemctl statusИсправить unit, выполнить daemon-reload, повторить запуск
Порт занятСтарый демон или другой unit уже слушает адресss -ltnp, systemctl statusОстановить конфликтующий запуск по плану или выбрать тестовый порт
Unix-клиент получает Permission deniedНеверные владелец, группа или режим сокетаstat, id, тест через sudo -uНастроить группу и SocketMode=0660, затем повторить проверку
Сервис сразу завершаетсяПриложение не поддерживает переданный descriptor или неверен Acceptjournalctl -u example.service, systemctl showИспользовать поддерживаемый режим, адаптер или прежний запуск
Первый запрос получает timeoutДолгий старт, неверный Type или слишком короткий таймаут клиентаjournalctl -fu, systemctl show -p TypeИсправить Type, прогрев, таймаут или отказаться от ленивого запуска
Сервис запускается дваждыОдновременно работают systemd и внешний менеджер процессовsystemctl cat, список процессов и unit-зависимостейОставить один источник управления жизненным циклом

Риски, резервное копирование и чек-лист перед production

Что сохранить перед изменением

Резервная копия должна позволять восстановить исходную схему без поиска параметров по истории команд. Сохраните:

  • вывод systemctl cat и systemctl show;
  • файлы из /etc/systemd/system и исходные unit-файлы, если вы их изменяете;
  • все drop-in-каталоги;
  • конфигурацию демона и сведения о секретах без записи самих секретов в журнал;
  • пользователя, группу, права, путь Unix-сокета или TCP-адрес;
  • зависимости, версии ОС, systemd и приложения;
  • результат базового запроса и состояние процесса до переключения.

Права на резервную копию задайте как минимум 0700 для каталога администратора. Секреты не помещайте в команды, которые попадут в shell history. Для шифрования credentials сначала проверьте доступность нужной команды:

systemd-creds --version
systemd-analyze --version

Формат зашифрованного credential и доступные операции зависят от версии systemd. Не переносите команды между дистрибутивами без проверки локальной справки.

План проверки после переключения

  1. После загрузки системы проверьте, что активен example.socket.
  2. Убедитесь, что endpoint слушает нужный путь или адрес.
  3. До тестового запроса проверьте ожидаемое отсутствие основного процесса.
  4. Выполните запрос разрешенным клиентом и убедитесь в запуске .service.
  5. Проверьте успешный ответ, журнал, повторное подключение и рестарт сервиса.
  6. Проверьте отказ для пользователя без разрешенной группы.
  7. Убедитесь, что старый unit, supervisor или контейнер не запускает второй экземпляр.
  8. Проверьте bind-адрес и firewall для TCP.

Для сетевых сервисов в облачной инфраструктуре заранее проверьте правила доступа и тестовый стенд. Материалы по размещению серверов, VDS и Kubernetes доступны в Timeweb Cloud, но саму сетевую политику нужно настроить под вашу инфраструктуру.

Итоговый чек-лист и условия отката

Считайте настройку готовой, если слушается только запланированный endpoint, доступ к Unix-сокету ограничен нужной группой, TCP не опубликован шире необходимого, сервис запускается после первого обращения, запросы не теряются, журналирование сохраняется, а прежний способ запуска отключен.

Откатывайте изменение при конфликте адреса, повторных падениях, непредсказуемой задержке, нарушении прав доступа, потере окружения или несовместимости демона. Последовательность отката: остановить новый .socket, отключить его, восстановить unit и drop-in, выполнить daemon-reload, запустить прежний сервис и проверить базовый запрос.

Для последующего контроля используйте systemctl list-sockets, ss и journalctl. Socket activation дает предсказуемый жизненный цикл только при согласованной конфигурации endpoint, приложения, прав и зависимостей. Зафиксируйте эту конфигурацию в базе знаний команды и повторяйте проверку после обновления systemd или демона.

Поделиться:
Сохранить гайд? В закладки браузера