---
tags: [linux, tooling]
verified: 2026-09-02
---

# Linux — systemd: init, сервисы, таймеры

## Обзор

systemd — это init-система и менеджер сервисов, работающий как PID 1 практически на всех major Linux-дистрибутивах. Разработан Lennart Poettering и Kay Sievers (Red Hat) с 2010 года, впервые включён по умолчанию в Fedora 15 (2011). systemd принципиально отличается от предшественника SysVinit: вместо последовательного выполнения shell-скриптов он использует декларативные unit-файлы с явным описанием зависимостей и параллельным запуском сервисов.

Масштаб systemd выходит далеко за рамки init: проект включает journald (логирование — см. статью 10), networkd/resolved (сеть — см. статью 3), logind (управление сессиями), tmpfiles, timedated, hostnamed и десятки других компонентов. Это основная причина критики: systemd нарушает принцип UNIX «do one thing well», концентрируя огромную функциональность вокруг PID 1. Однако именно эта интеграция обеспечивает согласованное поведение: cgroups-tracking, socket activation, структурированное логирование и декларативное управление ресурсами работают вместе, а не как разрозненные инструменты.

## Архитектура

### PID 1 и его роль

Ядро запускает systemd как первый userspace-процесс (PID 1). У PID 1 особая роль в UNIX: он становится приёмным родителем для всех осиротевших процессов и получает сигналы, не убивающие обычные процессы. systemd использует это: он отслеживает все дочерние процессы и через Linux cgroups гарантирует, что ни один процесс не «сбежит» от контроля (проблема double-fork, которая преследовала SysVinit).

PID 1 systemd выполняет три ключевые функции: загружает и парсит unit-файлы, строит граф зависимостей (DAG — направленный ациклический граф), и управляет транзакционной очередью jobs (запуск, остановка, перезапуск сервисов). Коммуникация с PID 1 идёт по протоколу D-Bus, но не обязательно через системную шину: systemctl разговаривает с менеджером по приватному сокету `/run/systemd/private`, поэтому управлять сервисами можно и тогда, когда dbus-daemon ещё не поднялся или упал, — в том числе чтобы починить саму шину.

### Units: базовая абстракция

Всё, чем управляет systemd, — это unit. Тип unit определяется суффиксом файла:

**.service** — процесс или демон. Основной тип, с которым работают чаще всего. **.timer** — планировщик, аналог cron (подробнее ниже). **.socket** — сокет для socket activation: systemd создаёт сокет и передаёт его сервису при первом подключении. **.mount** и **.automount** — точки монтирования (генерируются из /etc/fstab или описываются вручную). **.path** — триггер на события файловой системы (inotify). **.target** — группа units, точка синхронизации (аналог runlevel). **.device** — устройство из udev. **.slice** — иерархия cgroups для управления ресурсами. **.scope** — группа внешне-созданных процессов (например, сессия пользователя).

### Приоритет загрузки unit-файлов

Unit-файлы загружаются из нескольких директорий, приоритет от высшего к низшему:

`/etc/systemd/system/` — администраторские override'ы и кастомные units. `/run/systemd/system/` — units, созданные в runtime. `/usr/lib/systemd/system/` — units, поставляемые пакетным менеджером. Transient units от `systemd-run` лежат не здесь, а в отдельном каталоге `/run/systemd/transient/`, приоритет которого выше `/etc/systemd/system/`.

Файл в /etc/ полностью перекрывает одноимённый из /usr/lib/. Для частичного переопределения используются drop-in директории: `/etc/systemd/system/<unit>.d/*.conf` — фрагменты, которые дополняют или переопределяют отдельные директивы оригинального unit-файла. Это каноничный способ кастомизации: не трогать файлы пакетного менеджера, а создавать drop-in override.

### Зависимости: ordering vs requirement

systemd разделяет два ортогональных понятия, которые часто путают:

**Requirement** (что запускать) — директивы Wants= и Requires=. `Wants=B.service` — при активации текущего unit'а systemd попытается запустить B, но если B упадёт, текущий unit продолжит работать. `Requires=B.service` — жёсткая зависимость: если B не удастся запустить, текущий unit тоже будет помечен как failed.

**Ordering** (в каком порядке) — директивы After= и Before=. `After=B.service` — текущий unit запустится после B. Без After=/Before= units с Wants=/Requires= могут стартовать параллельно.

Критический нюанс: `Wants=` и `Requires=` не определяют порядок. Если написать `Wants=network.target` без `After=network.target`, сервис может запуститься одновременно с поднятием сети. Канонический паттерн для сетевых сервисов: `After=network-online.target` + `Wants=network-online.target`.

Есть и обратные зависимости: BindsTo= (как Requires, но unit останавливается если зависимость останавливается), PartOf= (unit перезапускается/останавливается вместе с зависимостью), Conflicts= (активация одного unit'а вызывает деактивацию другого).

### Targets как точки синхронизации

Targets заменяют runlevels SysVinit. Они не выполняют никакой работы сами — это точки синхронизации, которые собирают зависимости:

`multi-user.target` — полностью загруженная система без GUI (аналог runlevel 3). `graphical.target` — включает multi-user.target + display manager (runlevel 5). `rescue.target` — однопользовательский режим (runlevel 1). `emergency.target` — минимальная оболочка, даже без монтирования ФС. `reboot.target`, `poweroff.target` — завершение работы.

`default.target` — симлинк на целевой target, обычно graphical.target или multi-user.target. Загрузка системы — это рекурсивная активация default.target и всех его зависимостей.

## Анатомия service unit

### Секции unit-файла

Каждый .service файл состоит из трёх секций:

```ini
[Unit]
Description=My Application Server
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=notify
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.toml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
WatchdogSec=30s

# hardening (см. статью 7)
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/myapp

[Install]
WantedBy=multi-user.target
```

### Type= и момент готовности

Выбор Type= определяет, когда systemd считает сервис «запущенным» и начинает стартовать зависимые units. Это один из самых важных и неочевидных параметров:

**Type=simple** (default) — systemd считает сервис запущенным немедленно после fork(). Процесс не должен уходить в background. Подходит для современных демонов, которые остаются на переднем плане. Проблема: systemd не знает, действительно ли сервис готов принимать запросы — зависимые units могут запуститься раньше, чем сервис инициализируется.

**Type=exec** — как simple, но systemd ждёт успешного exec() (а не просто fork()). Чуть надёжнее: если бинарник не найден, unit сразу переходит в failed.

**Type=forking** — для классических UNIX-демонов, которые делают fork() и завершают родительский процесс. Рекомендуется указывать PIDFile=, чтобы systemd мог отследить основной процесс. Унаследованная модель — для нового софта лучше использовать notify.

**Type=notify** — сервис явно сообщает systemd о готовности через sd_notify(3) (вызов `sd_notify(0, "READY=1")`). Самый надёжный вариант: зависимые units стартуют только когда сервис действительно готов. Требует поддержки в коде приложения.

**Type=notify-reload** (появился в systemd 253) — то же самое плюс протокол перезагрузки конфигурации: на `systemctl reload` менеджер шлёт главному процессу SIGHUP и ждёт `RELOADING=1` с меткой `MONOTONIC_USEC=`, а затем `READY=1`. Это делает reload синхронным и избавляет от `ExecReload=`, который завершается раньше, чем сервис реально перечитал конфиг.

**Type=oneshot** — для скриптов, которые выполняются и завершаются. systemd ждёт завершения и только потом активирует зависимые units. С `RemainAfterExit=yes` unit остаётся в состоянии active после завершения процесса — полезно для настроечных скриптов и shutdown hooks. Единственный Type, допускающий несколько директив ExecStart= (выполняются последовательно).

**Type=dbus** — как simple, но готовность определяется по появлению заданного имени на D-Bus шине.

Рекомендация из актуальной systemd.service(5) звучит иначе, чем принято думать: для долгоживущих сервисов советуют `exec`, потому что он корректно отслеживает ошибки запуска — отсутствующий бинарник или несуществующего пользователя. Там, где момент готовности нужно определить точно, предпочтительны `notify`, `notify-reload` или `dbus` (последний — если сервис предоставляет интерфейс на шине). А `forking` в документации прямо помечен как discouraged: PID-файлы в новом софте рекомендуют не заводить вовсе. Любой тип кроме simple потенциально задерживает загрузку, потому что systemd ждёт готовности.

### Restart и watchdog

`Restart=` контролирует автоперезапуск: `on-failure` (при ненулевом exit code, сигнале, timeout), `always` (при любом завершении), `on-abnormal` (только при сигнале или timeout, но не при чистом exit). `RestartSec=` задаёт паузу перед перезапуском (по умолчанию 100 мс) — важно для предотвращения restart-storm. С systemd 254 паузу можно наращивать экспоненциально: `RestartSteps=` задаёт число шагов от `RestartSec=` до `RestartMaxDelaySec=`, что заметно гуманнее фиксированного интервала для сервиса, чья зависимость лежит уже полчаса.

`WatchdogSec=` — сервис должен периодически вызывать `sd_notify(0, "WATCHDOG=1")`. Если вызова нет в течение указанного интервала, systemd считает сервис зависшим и применяет Restart-политику. Это мощный механизм для обнаружения deadlock'ов, недоступный в SysVinit.

### Socket activation

Socket activation — одна из ключевых инноваций systemd. Идея: systemd создаёт сокет заранее, а сервис запускается только при первом подключении. Преимущества: параллелизация загрузки (сокеты создаются одновременно, зависимости разрешаются «ленивым» запуском), on-demand запуск (сервис не потребляет ресурсы, если к нему никто не обращается).

Пример: sshd.socket + sshd@.service — SSH-демон запускается только при подключении клиента. На сервере с редкими SSH-подключениями это экономит ресурсы.

```ini
# sshd.socket
[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target
```

## Таймеры как замена cron

systemd timers — декларативная альтернатива cron с рядом преимуществ: интеграция с journald (логи таймера доступны через journalctl), Persistent= (выполнение пропущенных заданий), зависимости от других units, sandboxing через директивы безопасности в [Service].

Таймер — это пара файлов: .timer (расписание) и .service (что выполнять). По умолчанию timer ищет service с тем же именем.

Два типа таймеров: **monotonic** — относительно события (OnBootSec=, OnUnitActiveSec=, OnStartupSec=). **Calendar** — по расписанию (OnCalendar=), аналог cron expression.

Формат OnCalendar= — более читаемый чем cron:

```ini
OnCalendar=daily                    # каждый день в 00:00
OnCalendar=Mon..Fri *-*-* 09:00    # будни в 9:00
OnCalendar=*-*-* *:0/15            # каждые 15 минут
OnCalendar=*-01-01 00:00:00        # раз в год, 1 января
```

Валидация расписания перед применением:
```bash
systemd-analyze calendar "Mon..Fri *-*-* 09:00" --iterations=5
```

Полный пример таймера для бэкапа:

```ini
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
```

```ini
# /etc/systemd/system/backup.service
[Unit]
Description=Backup job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Nice=19
IOSchedulingClass=idle
```

`RandomizedDelaySec=` — случайная задержка, предотвращающая thundering herd если много таймеров с одинаковым OnCalendar. `Persistent=true` — если система была выключена во время запланированного запуска, таймер сработает при следующем включении. `AccuracySec=` (по умолчанию 1min) — точность срабатывания; systemd намеренно группирует таймеры в окна для экономии энергии (coalescing wake-ups). Для высокой точности — `AccuracySec=1us`.

Важный gotcha: oneshot сервис с `RemainAfterExit=yes` не подходит для повторяющихся таймеров — systemd не перезапустит уже active сервис. Для таймеров используйте oneshot без RemainAfterExit (default).

## Практика

Основные операции systemctl:

```bash
# Жизненный цикл
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx          # HUP, без перезапуска процесса
systemctl reload-or-restart nginx

# Автозапуск при загрузке
systemctl enable nginx          # создаёт симлинк в target.wants/
systemctl disable nginx
systemctl enable --now nginx    # enable + start одной командой

# Статус и инспекция
systemctl status nginx          # статус + последние строки лога
systemctl is-active nginx       # exit code 0 если active
systemctl is-enabled nginx
systemctl show nginx            # все свойства unit'а
systemctl cat nginx             # показать unit-файл(ы)

# mask — жёсткое отключение (симлинк на /dev/null)
systemctl mask nginx            # ни start, ни enable не сработают
systemctl unmask nginx
```

Различие enable/disable vs mask/unmask: disable снимает симлинк из target.wants (сервис не стартует при загрузке, но можно запустить вручную). mask создаёт симлинк на /dev/null — сервис не может быть запущен никак, пока не unmask.

Drop-in override без редактирования unit-файла пакетного менеджера:

```bash
# Создаёт /etc/systemd/system/nginx.service.d/override.conf
systemctl edit nginx
# В открывшемся редакторе можно переопределить отдельные директивы

# Пример: увеличить лимит открытых файлов
# [Service]
# LimitNOFILE=65536

systemctl daemon-reload  # нужен после ручной правки файлов; systemctl edit делает reload сам
```

Диагностика загрузки:

```bash
systemd-analyze                        # общее время загрузки
systemd-analyze blame                  # время каждого unit'а
systemd-analyze critical-chain         # цепочка критического пути
systemd-analyze plot > boot.svg        # визуализация параллелизма

# Проверка unit-файла на ошибки
systemd-analyze verify /etc/systemd/system/myapp.service

# Просмотр дерева зависимостей
systemctl list-dependencies multi-user.target
systemctl list-dependencies --reverse nginx.service  # кто зависит от nginx
```

Работа с таймерами:

```bash
systemctl list-timers --all            # все таймеры и их следующее срабатывание
systemctl status backup.timer
journalctl -u backup.service          # логи выполнений
```

Transient units — одноразовое выполнение с полным sandboxing без создания файлов:

```bash
# Запуск команды в одноразовом cgroup с лимитами
systemd-run --unit=my-task --scope -p MemoryMax=512M /usr/local/bin/heavy-job

# Запуск пользовательского таймера
systemd-run --user --on-calendar="*:0/30" /usr/local/bin/check-something
```

## Нюансы и trade-off'ы

**daemon-reload и его стоимость.** `systemctl daemon-reload` пересериализует состояние всех units. На системе с тысячами units (крупные k8s-ноды, контейнерные хосты) это может занять секунды. Не стоит вызывать reload в tight loops — лучше batch'ить изменения.

**Ordering vs readiness race.** `After=network.target` не гарантирует, что сеть работает. network.target активируется когда *начата* настройка сети, а не когда она завершена. Для сервисов, которым действительно нужна работающая сеть, используйте `After=network-online.target` и убедитесь, что `NetworkManager-wait-online.service` или `systemd-networkd-wait-online.service` активен.

**Потеря stdout/stderr.** Если сервис пишет в stdout/stderr, journald собирает эти данные. Но если journald перегружен или unit имеет `StandardOutput=null`, логи теряются. Для критичных сервисов рассмотрите дополнительное логирование в файл через приложение.

**ExecStartPre и порядок.** Если ExecStartPre завершается с ошибкой (без префикса -), unit переходит в failed и ExecStart не выполняется. Префикс `-` перед командой игнорирует ненулевой exit code: `ExecStartPre=-/usr/bin/cleanup-maybe`. Аналогично для ExecStartPost.

**cgroups и scope of control.** systemd помещает каждый сервис в отдельный cgroup. Это значит: `systemctl stop` убивает все процессы сервиса, включая дочерние (через SIGTERM → SIGKILL). В SysVinit дочерние процессы могли «сбежать». Это же позволяет ставить лимиты ресурсов через unit-файл: MemoryMax=, CPUQuota=, TasksMax= — без ручной настройки cgroups. Выбора между иерархиями больше нет: в systemd 258 (сентябрь 2025) поддержка cgroup v1 — и legacy, и hybrid — удалена совсем, unified-иерархия монтируется всегда, а минимальная версия ядра поднята до 5.4. Подробнее о cgroups — в статье 6.

**User units.** systemd поддерживает пользовательские units (без root): файлы в `~/.config/systemd/user/`, управление через `systemctl --user`. Пользовательский systemd-инстанс запускается при логине (или через `loginctl enable-linger` — без логина). Полезно для пользовательских демонов и таймеров.

**Portability скриптов.** Unit-файлы непортабельны за пределы systemd. Если нужна совместимость с несколькими init-системами, придётся поддерживать отдельные механизмы запуска (init-скрипт + unit-файл). На практике это актуально для софта, работающего на Alpine (OpenRC) или Void (runit). Обратный мостик, наоборот, разобран: до недавнего времени systemd подхватывал legacy-скрипты из `/etc/init.d/` через systemd-sysv-generator, но в версии 260 (март 2026) генератор удалён вместе с systemd-sysv-install и rc-local.service, так что нативный unit-файл теперь обязателен.

## Альтернативы systemd

**OpenRC** (Gentoo, Alpine, Artix) — dependency-based init, работает поверх SysVinit. Shell-скрипты вместо декларативных unit-файлов. Поддерживает параллельный запуск сервисов. Не привязан к Linux — работает на FreeBSD. Не берёт на себя логирование, сеть и прочее — каждый компонент отдельный. Trade-off: проще и модульнее, но нет cgroups-tracking, socket activation, watchdog, структурированного логирования.

**runit** (Void Linux, используется внутри Docker-контейнеров) — минималистичный process supervisor. Сервис = директория с исполняемым скриптом `run`. Если процесс умирает, runit немедленно перезапускает его. Три стадии загрузки: init, supervision, shutdown. Нет dependency management как такового — порядок определяется скриптами. Код PID 1 — 18KB (vs ~1.7MB у systemd). Чрезвычайно надёжен, но требует ручного управления зависимостями.

**s6/s6-rc** (проект Skarnet, используется в 66 init system) — модульная supervision suite с отдельным service manager (s6-rc). Акцент на correctness: гарантирует, что сервис не будет запущен до готовности зависимостей (в отличие от простых supervisor'ов). Поддержка readiness notification. Маленький codebase, пригоден для embedded. Trade-off: сложнее в освоении чем runit, меньше community.

На практике выбор init-системы определяется дистрибутивом. Переключение между ними — серьёзная операция, затрагивающая весь стек (инструменты мониторинга, deployment-скрипты, конфигурация).

## Сравнение с FreeBSD

FreeBSD использует rc-систему: shell-скрипты в /etc/rc.d/ (base system) и /usr/local/etc/rc.d/ (порты), управляемые через /etc/rc.conf. Включение сервиса — одна строка: `nginx_enable="YES"` в rc.conf. Зависимости описываются через REQUIRE/BEFORE/KEYWORD в заголовке скрипта.

Ключевые различия:

**Декларативность.** systemd unit-файл — декларативное описание «что должно быть». rc-скрипт FreeBSD — императивная программа «как это сделать». Unit-файл проще парсить, валидировать, генерировать. rc-скрипт гибче: это полноценный shell, можно написать любую логику.

**Supervision.** systemd отслеживает процессы через cgroups и может перезапускать упавшие сервисы. rc-система FreeBSD — fire-and-forget: если демон упал, его нужно перезапустить вручную (или использовать daemon(8) с -r). Service jails в FreeBSD (появились сравнительно недавно) дают процессную изоляцию через один параметр в rc.conf, но это изоляция, а не supervision.

**Логирование.** systemd захватывает stdout/stderr сервисов в journald автоматически. В FreeBSD логирование — ответственность самого демона (через syslog или файлы). Это проще в плане архитектуры, но неудобнее для диагностики.

**Таймеры.** В FreeBSD нет встроенного аналога systemd timers — используется cron (с periodic(8) как обёрткой для системных задач). cron проще, но не имеет dependency management, persistent catch-up, и sandboxing.

**Единая конфигурация.** FreeBSD rc.conf — один файл для всей системы: hostname, сеть, сервисы, firewall. Это удобно для обзора, но не масштабируется: на сложной системе rc.conf превращается в длинный плоский список. systemd распределяет конфигурацию по отдельным unit-файлам — сложнее для обзора, но лучше модуляризировано.

**Service jails** в FreeBSD заслуживают отдельного упоминания: они позволяют изолировать сервис в jail одной строкой в rc.conf (`<service>_svcj="YES"`). Это аналог sandbox-директив systemd (ProtectSystem=, PrivateTmp= и т.д.), но реализованный через jail-механизм ядра FreeBSD — потенциально сильнее изоляция, но с меньшей гранулярностью настроек.

## Источники

- [systemd.service(5)](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html) — полное описание service units
- [systemd.timer(5)](https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html) — описание timer units
- [systemd.unit(5)](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) — общие директивы для всех типов units
- [systemd releases и NEWS](https://github.com/systemd/systemd/releases) — первоисточник по удалениям и новым директивам: cgroup v1 в 258, SysV-скрипты в 260
- [systemd — ArchWiki](https://wiki.archlinux.org/title/Systemd) — обширное практическое руководство
- [systemd/Timers — ArchWiki](https://wiki.archlinux.org/title/Systemd/Timers) — таймеры как замена cron
- [Lennart Poettering, "Rethinking PID 1"](http://0pointer.de/blog/projects/systemd.html) — оригинальная мотивация дизайна systemd
- [EWONTFIX: Systemd has 6 service startup notification types](https://ewontfix.com/15/) — критический анализ Type= от Rich Felker (автор musl)
- [Red Hat: Working with systemd unit files](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_systemd_unit_files_to_customize_and_optimize_your_system/) — enterprise-ориентированная документация
- [s6-rc: why?](https://skarnet.org/software/s6-rc/why.html) — мотивация альтернативного подхода к service management
- [Comparison of init systems — Gentoo Wiki](https://wiki.gentoo.org/wiki/Comparison_of_init_systems) — сравнительная таблица init-систем
- FreeBSD Handbook: [rc(8)](https://man.freebsd.org/cgi/man.cgi?rc(8)) — для сравнения
- [Klara Systems: rc(8) FreeBSD Services and Automation](https://klarasystems.com/articles/rc8-freebsd-services-and-automation/) — обзор rc-системы FreeBSD

---

## Связанные заметки

- [Linux — файловая иерархия и FHS](./linux-filesystem-hierarchy.md) — где лежат unit-файлы и runtime-данные
- [Linux — обновление и управление загрузкой](./linux-boot-management.md) — systemd-boot и порядок инициализации
- [Linux — контейнеризация](./linux-containerization.md) — cgroups как общий фундамент systemd и контейнеров
- [FreeBSD — rc-система](./freebsd-rc-system.md) — init через shell-скрипты, для сравнения
- [Linux — сводный cheatsheet](./linux-map-cheatsheet.md) — быстрая справка по systemctl и journalctl
- [Домашний десктоп на CachyOS](./setups/cachyos-desktop/hyprland-desktop.md) — пример пользовательских скоупов: uwsm запускает приложения сессии
- [Linux — пользователи, группы, PAM и systemd-logind](./linux-users-pam.md) — logind, сессии, linger и пользовательский экземпляр менеджера
- [Резервное копирование: стратегия и проверка восстановления](./backup-strategy.md) — таймеры и `LoadCredentialEncrypted=` на практике: ночное задание с секретом
- [Linux — время и синхронизация](./linux-time-sync.md) — почему `OnCalendar=` в локальном поясе ломается при переходе на летнее время
- [Наблюдаемость: Prometheus, node_exporter, Grafana, Loki](./observability-stack.md) — юниты экспортеров и директивы изоляции на практике
- [Эксплуатация локальных ИИ-сервисов](./ai-services-operations.md) — юнит для сервиса инференса: почему готовность не выражается через `.device` и `Type=notify`
- [Загрузка: от питания до первой инструкции ядра](./os-boot-sequence.md) — как ядро вообще запускает первый процесс и что происходит до передачи управления init
