Socket activation — одна из самых практичных возможностей systemd, о которой часто вспоминают только на серверах с ограниченной памятью. Но выгода не только в экономии RAM: запуск приложения по первому входящему соединению дает контроль над очередью, повышает отказоустойчивость и упрощает развертывание. Для веб‑нагрузки это особенно ценно: многие внутренние API, админки, экспортеры и редкие инструменты большую часть времени простаивают, но потребляют память. Зачем держать их запущенными постоянно — особенно если у вас VDS с десятками небольших сервисов?
Идея socket activation: как это работает
В классической схеме вы запускаете демон, он сам открывает порт или Unix‑сокет и ждет запросов. При socket activation порядок обратный: слушающий сокет создает и обслуживает systemd. Пока нет запросов — ваше приложение не стартует. Как только приходит подключение, systemd порождает процесс, передает ему уже открытый файловый дескриптор сокета (через стандартные переменные окружения, которые выставляет сам systemd), и приложение сразу обслуживает запрос.
Ключевые элементы — пара юнитов: myapp.socket и myapp.service (имена совпадают до точки). Юнит .socket описывает, где слушать (порт/путь), а .service — чем обрабатывать. Для HTTP обычно используют Accept=no (один общий слушатель и один процесс/группа процессов), тогда приложение само раскладывает соединения по воркерам. Режим Accept=yes порождает отдельный процесс на каждое подключение — уместно для простых протоколов или очень коротких задач.
Главное преимущество: приложение не потребляет ресурсы в простое, а первый запрос «будит» процесс за счет уже открытого сокета — без гонок за порт и TOCTOU‑проблем.
Где socket activation выгоднее всего
Не все сервисы стоит «усыплять». Высоконагруженные фронты с тысячами RPS лучше держать горячими, чтобы избежать даже короткого cold‑start. Но типичных кейсов для активации по запросу более чем достаточно:
- Редко используемые административные панели и внутренние API.
- Веб‑хуки интеграций, которые дергаются раз в часы/сутки.
- Отчетность и экспортеры метрик, к которым обращается мониторинг по расписанию.
- Вспомогательные инструменты разработки и внутренние демо.
- Сервисы, которые должны пережить перезапуск приложения без потери входящих подключений, так как сокет «живет» в systemd.

Базовая схема: Unix‑сокет и reverse‑proxy
Unix‑сокет удобен тем, что его проще изолировать правами и он исключает сетевой стек. Обычно перед приложением стоит reverse‑proxy (например, Nginx), который проксирует HTTP на Unix‑сокет.
Пример myapp.socket
[Unit]
Description=MyApp socket
[Socket]
ListenStream=/run/myapp.sock
SocketMode=0660
SocketUser=myapp
SocketGroup=www-data
Accept=no
Backlog=256
[Install]
WantedBy=sockets.target
Пример myapp.service
[Unit]
Description=MyApp service
Requires=myapp.socket
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
ExecStart=/usr/local/bin/myapp
Restart=on-failure
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=read-only
[Install]
WantedBy=multi-user.target
Важный момент: не все приложения «знают» про унаследованный сокет автоматически. Многие серверы и фреймворки поддерживают «fd=3» явно. Если ваше приложение — это, например, Gunicorn, можно привязаться к fd://3 в параметрах запуска (см. ниже). Для самописных сервисов в любом популярном языке есть способ принять готовый файловый дескриптор.
Пример проксирования на Unix‑сокет в Nginx
server {
listen 80;
server_name example.local;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
proxy_pass http://unix:/run/myapp.sock:;
}
}
На холодном старте первый запрос может подождать сотни миллисекунд или секунду — пока поднимется приложение. В большинстве случаев это укладывается в стандартные таймауты прокси, но их лучше проверить и при необходимости увеличить.
Вариант с TCP‑портом (без Unix‑сокета)
Если вам нужен TCP‑порт, схема аналогична. Чаще это локальный порт, до которого добирается Nginx, но можно слушать и внешний интерфейс (с учетом безопасности).
[Unit]
Description=MyApp TCP socket
[Socket]
ListenStream=127.0.0.1:9000
Accept=no
Backlog=256
ReusePort=no
[Install]
WantedBy=sockets.target
В связке с TCP сценарий cold‑start такой же: подключение попадает в очередь, systemd запускает сервис, и приложение подхватывает уже открытый сокет.
Как приложения принимают «унаследованный» сокет
Сердце socket activation — протокол передачи файловых дескрипторов через переменные окружения (LISTEN_FDS и LISTEN_PID), которые автоматически выставляет systemd. В системном вызове это «тот самый» fd=3 и далее по порядку. В разных стэках это выглядит так:
Gunicorn (Python)
Gunicorn умеет слушать дескриптор напрямую. В myapp.service достаточно указать запуск вида:
ExecStart=/usr/bin/gunicorn --bind fd://3 --workers 2 wsgi:app
Node.js
В Node можно слушать дескриптор, если передать его в server.listen:
const http = require('http');
const server = http.createServer((req, res) => { res.end('ok'); });
const fd = 3; // первый дескриптор от systemd
server.listen({ fd }, () => console.log('listening on inherited fd'));
Go
В Go можно собрать net.Listener из файлового дескриптора и передать его HTTP‑серверу:
package main
import (
"net"
"net/http"
"os"
)
func main() {
f := os.NewFile(3, "listener")
ln, err := net.FileListener(f)
if err != nil { panic(err) }
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) })
http.Serve(ln, nil)
}
Экономия ресурсов и стабильность: что реально меняется
В простое приложение вообще не запущено — экономия памяти равна «всему, что ело приложение». Для Python/Node это могут быть сотни мегабайт на процессе с модулями, пулом соединений и интерпретатором. Если на одном сервере десятки подобных сервисов, выигрыш драматичен. При этом принятие соединений «вынесено» в systemd, а значит перезапуск сервиса (например, при обновлении) не отбивает порт и не приводит к ошибкам привязки: сокет остается живым, а очередь подключений сохраняется в ядре.
Еще одно следствие — меньше фоновой «турбулентности»: нет висящих воркеров, нет случайных GC‑пауз и утечек, когда сервис спит. С другой стороны, учитывайте cold‑start: если сервис стартует несколько секунд, внимательно настройте таймауты прокси/клиентов.
Таймауты, очередь и холодный старт
В момент первого запроса ядро ставит соединение в очередь прослушивающего сокета (который открыл systemd), затем запускается процесс. Важно:
- Backlog в
.socketопределяет глубину очереди соединений. Поставьте запас — 128/256 для редких всплесков. - proxy_connect_timeout и proxy_read_timeout в обратном прокси должны выдерживать холодный старт плюс обработку запроса.
- Если cold‑start длинный, используйте периодический «пинг» через systemd timers, чтобы держать сервис «теплым» в рабочие часы.
Нужно минимизировать задержку первого запроса? Держите небольшой пул воркеров запущенным, а дополнительные экземпляры поднимайте по socket activation — так баланс между латентностью и экономией оптимален.

Тонкая настройка производительности
- Accept=no для HTTP. Пусть приложение само мультиплексирует соединения. Режим
Accept=yesполезен для коротких TCP‑диалогов. - Unix‑сокет там, где нет внешних клиентов. Он быстрее и проще ограничивается правами, особенно если прокси и приложение на одном хосте.
- Backlog с запасом — недорогая страховка от пиков.
- Осторожнее с ReusePort в
.socketбез явной необходимости: балансировка по хэшу иногда распределяет трафик неожиданно. - Разделите статику и динамику: статику отдавайте из прокси, а динамику держите на socket activation — так холодных стартов меньше.
Отладка: как проверить, что все работает
Стандартная последовательность:
- Включить и запустить сокет:
systemctl enable --now myapp.socket. - Убедиться, что сокет слушает:
ss -lx | grep myapp.sockилиss -ltn | grep :9000. - Проверить, что сервис не запущен:
systemctl status myapp.service(должен быть неактивным). - Сделать HTTP‑запрос и посмотреть, что сервис стартовал:
curl -sS http://127.0.0.1/, затемsystemctl status myapp.service. - Логи:
journalctl -u myapp.service -bиjournalctl -u myapp.socket -b.
Для локального теста без юнитов удобно: systemd-socket-activate -l 127.0.0.1:9000 /usr/local/bin/myapp — утилита поднимет слушающий сокет и запустит процесс при первом подключении.
Интеграция с фреймворками: частые рецепты
- Gunicorn: запуск с
--bind fd://3и нужным числом воркеров. Сокет на стороне systemd. - Uvicorn: можно привязаться к
fd://3или использовать API сервера для принятия дескриптора. - Node.js: слушать
{ fd: 3 }; логика воркеров остается в Node/PM2 при необходимости. - Go/Rust: собрать
net.Listenerили аналог изfd=3и передать серверу.
Если фреймворк не поддерживает fd‑привязку напрямую, помогает тонкая обертка на входе процесса, которая собирает слушатель из дескриптора и передает дальше — изменения минимальны.
Безопасность: почему Unix‑сокет удобнее
Unix‑сокеты позволяют точно ограничить доступ правами файловой системы: только владелец/группа смогут проксировать запросы. Для TCP‑портов используйте firewall, bind на loopback или частной сети, и лимиты запросов на уровне прокси. В юните сервиса включайте базовую изоляцию пространств, отказ от новых привилегий и ограничения на чтение системных директорий. Подробно про усиление изоляции мы разбирали в материале жесткая песочница systemd для сервисов.
Сценарии развёртывания и эксплуатации
В реальной жизни socket activation помогает упорядочить хозяйство на сервере:
- Много мелких сервисов для внутренних задач: все они «спят», пока к ним не обратятся — память уходит под кеши базы и фронтов.
- Сезонные/редкие функции: например, генерация отчетов раз в сутки — не нужно держать тяжелую библиотеку в памяти.
- Атомные обновления: при деплое сокет остается у systemd, вы просто переключаете бинарник/среду, не теряя порт.
- Поэтапный прогрев: системный таймер дергает локальный URL раз в N минут в рабочие часы, чтобы не было холодных стартов для пользователей.
Чек‑лист внедрения
- Выделите кандидатов: редко используемые веб‑ручки и внутренние сервисы.
- Выберите транспорт: Unix‑сокет для локального прокси, TCP — если есть внешние клиенты.
- Соберите пару
.socket/.serviceи проверьте поддержку «унаследованного» сокета в приложении. - Настройте таймауты прокси с учетом cold‑start и поставьте адекватный
Backlog. - Добавьте базовую изоляцию в юнит сервиса и проверьте права на Unix‑сокет.
- Промониторьте реальную экономию:
systemd-cgtop,top, метрики.
Итоги
Socket activation в systemd — простой способ сделать инфраструктуру веб‑приложений экономичнее и устойчивее. Вы отдаете управление сокетами системе и запускаете процессы только тогда, когда они действительно нужны. Для сервисов со спорадическим трафиком это сразу освобождает память и упрощает деплой, а для остального стека дает аккуратный механизм очереди и контроля таймаутов. Начните с одного вспомогательного сервиса, замерьте эффект и распространяйте подход — выгода часто видна уже в первый день.


