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

# FreeBSD — rc-система и управление сервисами

## Обзор

Rc-система — это init-подсистема FreeBSD, которая управляет всем жизненным циклом системы от момента, когда ядро передаёт управление в userland, до корректного завершения работы. Она определяет, какие сервисы запускаются, в каком порядке, с какими зависимостями, и предоставляет единый интерфейс для управления ими в runtime.

В отличие от systemd в Linux, rc — это набор shell-скриптов с детерминированным порядком исполнения. Это делает систему полностью прозрачной и отлаживаемой: любой аспект поведения можно проследить, прочитав конкретный sh-скрипт. Trade-off — отсутствие параллельного запуска, сокет-активации и встроенной интеграции с cgroups/namespaces (хотя service jails частично закрывают последний пункт).

## Историческая эволюция

Утилита rc(8) впервые появилась в 4.0BSD в 1980 году. Классический процесс загрузки был предельно прост: ядро BSD запускало init(8), который передавал управление скрипту /etc/rc, а тот вызывал /etc/netstart для настройки сети и /etc/rc.local для запуска пользовательских демонов.

Исторический BSD имел монолитный стартовый скрипт /etc/rc, который выполнял все задачи перехода в многопользовательский режим. Проблема монолитного подхода была в отсутствии контроля над отдельными компонентами: чтобы перезапустить один демон, администратору приходилось вручную находить процесс, убивать его и восстанавливать параметры запуска из скрипта.

В 2000 году NetBSD начала модернизацию этой подсистемы: сначала появился каталог /etc/rc.d для отдельных скриптов каждого сервиса, затем команда rcorder(8) для определения порядка запуска. FreeBSD интегрировала систему rc.d из NetBSD в 2002 году.

## Процесс загрузки

При загрузке ядро запускает `init(8)` (PID 1), который выполняет `/etc/rc`. Далее rc действует по следующей логике:

При автозагрузке устанавливается флаг `rc_fast=yes`, который предотвращает проверку уже запущенных процессов для ускорения загрузки. Затем rc загружает `/etc/rc.subr` — библиотеку shell-функций, используемую всеми скриптами. После этого загружаются конфигурационные файлы и определяется, выполняется ли загрузка в jail (в этом случае скрипты с ключевым словом `nojail` пропускаются).

Далее вызывается rcorder(8) для упорядочивания файлов в /etc/rc.d/, не имеющих ключевого слова `nostart`. Скрипты выполняются последовательно до `early_late_divider` (по умолчанию это виртуальная точка `FILESYSTEMS`, то есть момент, когда смонтированы корень и остальные критичные файловые системы). После этого rcorder(8) вызывается повторно, уже включая скрипты из каталогов `local_startup` (по умолчанию `/usr/local/etc/rc.d/`), и выполнение продолжается.

Двухфазная загрузка (early/late) — архитектурно важный момент. `early_late_divider` определяет границу между стадиями: ранняя стадия содержит все сервисы, необходимые для монтирования дисков, чтобы поздняя стадия могла включать скрипты из `local_startup`. Это решает проблему курицы и яйца: скрипты из `/usr/local/etc/rc.d/` не могут быть прочитаны, пока `/usr/local` не смонтирован.

При завершении работы `/etc/rc.shutdown` отбирает через rcorder(8) только скрипты с ключевым словом `shutdown`, разворачивает порядок и вызывает их с аргументом `faststop` — предварительные проверки вроде чтения PID-файла при этом пропускаются ради скорости выключения.

## rcorder: механизм разрешения зависимостей

`rcorder(8)` — ключевой компонент системы. Он разбирает метаданные зависимостей в заголовках скриптов и выстраивает их в топологически корректном порядке. Программа rcorder(8) анализирует комментарии в начале каждого стартового скрипта для определения порядка вызова сервисов с учётом зависимостей.

Доступные директивы:

`PROVIDE` — объявляет, какой сервис (или виртуальную «возможность») предоставляет скрипт. Имя в PROVIDE — это то, на что могут ссылаться REQUIRE и BEFORE других скриптов. Рекомендуется совпадение имени файла, значения PROVIDE и переменной `$name` — это упрощает отладку.

`REQUIRE` — перечисляет сервисы, которые должны быть запущены до данного скрипта. Если скрипт A содержит `REQUIRE: B`, то A будет запущен после B.

`BEFORE` — обратная логика: скрипт будет запущен до перечисленных сервисов. Используется реже и с осторожностью.

`KEYWORD` — теги, влияющие на поведение скрипта в определённых контекстах: `shutdown` (запускать скрипт при выключении), `nojail` (не запускать в jail-окружении), `nojailvnet` (не запускать в jail без VNET), `firstboot` (запускать только при первой загрузке), `nostart` (не запускать автоматически).

Существуют также виртуальные PROVIDE-точки (ALLUPPERCASE), которые обозначают этапы загрузки, а не конкретные сервисы; в порядке запуска это `FILESYSTEMS`, `NETWORKING`, `SERVERS`, `DAEMON`, `LOGIN`. Они работают как барьеры — группируют скрипты по стадиям. Например, `REQUIRE: LOGIN` означает «запустить после того, как система полностью готова к пользовательским сервисам», и это обязательное требование для скриптов, запускающих процессы от имени непривилегированных пользователей.

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

```sh
rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null
```

Это одна из сильных сторон init-системы rc(8) — просто определить и настроить порядок запуска сервисов, тогда как более сложный systemd(1) может загружаться быстрее, но значительно сложнее в отладке.

## rc.conf: центральная конфигурация

`/etc/rc.conf` — единственный файл, который определяет, что и как запускается в системе. Файл rc.conf содержит описательную информацию о локальном имени хоста, параметрах конфигурации сетевых интерфейсов и сервисах, которые должны быть запущены при начальной загрузке.

Архитектура конфигурации трёхуровневая:

`/etc/defaults/rc.conf` — дефолтные значения всех переменных. Этот файл никогда не редактируется — он перезаписывается при обновлении системы.

`/etc/rc.conf` — переопределения дефолтов. Именно здесь администратор включает сервисы, задаёт сетевые настройки, определяет hostname.

`/etc/rc.conf.local` — дополнительные переопределения для конкретной машины. Рекомендуемый подход для кластерных конфигураций: общие настройки в `/etc/rc.conf`, машино-специфичные в `/etc/rc.conf.local`. Файл `rc.conf` распространяется через rsync/puppet, `rc.conf.local` уникален для каждого хоста.

Помимо этих трёх файлов `load_rc_config` подхватывает пофайловые фрагменты из `/etc/rc.conf.d/<имя сервиса>` (и из каталогов `rc.conf.d`, соседних с каждым каталогом из `local_startup`); если по такому пути лежит не файл, а каталог, загружается всё его содержимое.

Все эти файлы — это обычный sh(1)-код, который sourced последовательно: переменные, определённые позже, переопределяют более ранние. Типичные записи:

```sh
hostname="myserver.example.org"
ifconfig_vtnet0="inet 10.0.0.1 netmask 255.255.255.0"
defaultrouter="10.0.0.254"
sshd_enable="YES"
nginx_enable="YES"
```

Принцип прост: для сервиса с именем `foo` переменная `foo_enable="YES"` включает его при загрузке. Если переменная не установлена или установлена в `"NO"` — скрипт при загрузке пропустит запуск.

Дополнительные переменные для сервиса `foo`: `foo_flags` (аргументы командной строки), `foo_user` (пользователь), `foo_chroot` (chroot-директория), `foo_cpuset` (привязка к CPU), `foo_fib` (FIB для setfib). Полный список возможностей документирован в rc.conf(5).

## rc.subr: фреймворк для скриптов

`/etc/rc.subr` — библиотека shell-функций, которая реализует всю логику управления сервисами. Именно она обеспечивает единообразное поведение команд start/stop/restart/status для всех скриптов. Благодаря ей типичный скрипт может состоять всего из нескольких строк sh(1)-кода.

Ключевая функция — `run_rc_command "$1"`. Она принимает аргумент (start, stop, restart, status и др.) и выполняет соответствующее действие, включая: проверку `rcvar` в rc.conf, проверку `required_files`/`required_dirs`/`required_vars`, выполнение `*_precmd` перед основным действием, запуск/остановку процесса, работу с PID-файлами.

Стандартные аргументы, которые должен поддерживать каждый скрипт: `start`, `stop`, `restart`, `status` (проверяет, запущен ли сервис), `rcvar` (показывает, какие переменные rc.conf управляют сервисом), `enabled` (возвращает 0 если сервис включён).

Дополнительные аргументы: `reload` (отправляет SIGHUP для перечитывания конфига), `poll` (ожидает завершения процесса), а также `enable`, `disable` и `delete`, которые правят переменную `_enable` прямо в конфиге — в том файле, где она уже определена (это может быть и `/etc/rc.conf.d/<имя сервиса>`), иначе дописывают её в `/etc/rc.conf`. Скрипты могут определять собственные команды через `extra_commands`.

Модификаторы поведения:

`onestart`/`onestop` — запустить/остановить сервис однократно, игнорируя значение `_enable` в rc.conf. Безопасный способ запустить сервис, не включённый при загрузке.

`forcestart`/`forcestop` — как `one*`, но дополнительно пропускает все prerequisite-проверки и игнорирует ошибки. Использовать с осторожностью.

`faststart` — пропускает проверку на уже запущенный процесс.

## Анатомия rc.d-скрипта

Минимальный рабочий скрипт для пользовательского сервиса:

```sh
#!/bin/sh

# PROVIDE: myapp
# REQUIRE: LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name="myapp"
rcvar=myapp_enable

command="/usr/local/sbin/myapp"
pidfile="/var/run/${name}.pid"

load_rc_config $name

: ${myapp_enable:="NO"}

run_rc_command "$1"
```

Разберём ключевые элементы:

Заголовок с PROVIDE/REQUIRE/KEYWORD — метаданные для `rcorder(8)`. `REQUIRE: LOGIN` — стандартный выбор для пользовательских сервисов; означает запуск после того, как базовая система полностью инициализирована. `KEYWORD: shutdown` — означает, что сервис нужно корректно останавливать при выключении (без этого тега `rc.shutdown` проигнорирует скрипт).

`. /etc/rc.subr` — подключение фреймворка. Без этой строки ничего не работает.

`name="myapp"` — идентификатор сервиса. Определяет имя для `service(8)`, переменные в rc.conf (`myapp_enable`, `myapp_flags`), и PID-файл по умолчанию.

`rcvar=myapp_enable` — связывает скрипт с переменной включения в rc.conf.

`command` — путь к бинарнику. rc.subr автоматически реализует start (запуск command), stop (kill по PID), status (проверка PID).

`load_rc_config $name` — загружает переменные из rc.conf, относящиеся к данному сервису.

`: ${myapp_enable:="NO"}` — дефолтное значение, если не задано в rc.conf. Bourne shell-синтаксис: установить переменную, только если она не определена или пуста.

`run_rc_command "$1"` — передать управление фреймворку.

Для сервисов, которые не демонизируются сами, используется `daemon(8)`:

```sh
command="/usr/sbin/daemon"
command_args="-f -p ${pidfile} /usr/local/bin/myapp --some-flags"
```

Хуки для кастомизации: `start_precmd`, `start_postcmd`, `stop_precmd`, `stop_postcmd` — функции, вызываемые до/после соответствующих действий. Полезны для проверки конфигурации, создания директорий, ротации логов.

## Управление сервисами в runtime

### service(8)

Основной инструмент для взаимодействия с rc-скриптами:

```sh
service sshd status        # проверить состояние
service sshd start         # запустить (если включён в rc.conf)
service sshd stop          # остановить
service sshd restart       # перезапустить
service sshd reload        # отправить SIGHUP (если поддерживается)
service sshd onestart      # запустить однократно (даже если не включён)
service -e                 # список включённых сервисов
service -l                 # список всех доступных скриптов
service -r                 # полный rcorder-список скриптов, включённых и нет
```

`service(8)` — это обёртка, которая находит нужный скрипт в `/etc/rc.d/` или `/usr/local/etc/rc.d/` и вызывает его с переданным аргументом.

### sysrc(8)

Безопасный инструмент для редактирования rc.conf из командной строки:

```sh
sysrc nginx_enable=YES           # включить сервис
sysrc nginx_enable=NO            # выключить
sysrc -n nginx_enable            # показать текущее значение
sysrc -x nginx_enable            # удалить переменную из rc.conf
sysrc -a                         # показать все установленные переменные
sysrc -f /etc/rc.conf.local hostname="node1"  # записать в конкретный файл
```

`sysrc` предпочтительнее ручного редактирования — он корректно обрабатывает кавычки, проверяет синтаксис и работает атомарно.

## Расположение скриптов

`/etc/rc.d/` — скрипты base system. Сюда попадают rc-скрипты для sshd, cron, syslogd, pf, devd и других системных сервисов. Редактировать их не нужно и не рекомендуется — при обновлении системы они перезаписываются.

`/usr/local/etc/rc.d/` — скрипты из пакетов и портов. nginx, postgresql, redis и прочий сторонний софт устанавливает свои скрипты сюда. Это следствие архитектурного разделения base/local, описанного в статье о файловой структуре.

Начиная с FreeBSD 6.1-RELEASE, локальные rc.d-скрипты (включая установленные из портов) включаются в общий rcorder(8) базовой системы. Это значит, что пользовательский скрипт в `/usr/local/etc/rc.d/` может объявлять зависимости от системных сервисов — и rcorder корректно разрешит порядок запуска.

## Service Jails

Механизм появился в FreeBSD 15.0; в ветке 14.x его нет — ни `rc.subr`, ни `rc.conf(5)` там про `svcj` ничего не знают. Service Jails позволяют автоматически запускать rc.d-сервис внутри jail(8) с минимальной конфигурацией — не более двух строк.

В отличие от ручной настройки jail с отдельной файловой системой и сетью, service jail наследует файловую систему хоста, но применяет jail-ограничения: изоляция процессов, ограничение сетевого доступа, запрет монтирования файловых систем.

Включение для конкретного сервиса:

```sh
nginx_svcj_options="net_basic"
nginx_svcj="YES"
```

Это не имеет тех же преимуществ безопасности, что и ручная настройка jail с отдельной файловой системой и IP/VNET, но значительно проще в настройке, обеспечивая при этом часть преимуществ jail — например, сокрытие процессов других пользователей.

Все базовые системные сервисы уже содержат конфигурацию для service jails (переменные `*_svcj_options`), хотя не все протестированы. Подробности — в `rc.conf(5)`, раздел "SERVICE JAILS".

## Отладка

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

`service foo rcvar` — показывает, какие переменные rc.conf управляют сервисом и их текущие значения. Первое, что нужно проверить.

`service foo onestart` — попытка запуска вне зависимости от rc.conf. Если работает — проблема в конфигурации, не в скрипте.

Чтение скрипта: `less /usr/local/etc/rc.d/foo` — скрипт целиком доступен для анализа, включая все зависимости, переменные и хуки.

`sh -x /etc/rc.d/foo start` — запуск с трассировкой shell. Показывает каждую исполняемую строку — наиболее мощный инструмент отладки.

Для диагностики порядка загрузки: `rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null | grep foo` — показывает позицию скрипта в последовательности.

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

Rc и systemd решают одну задачу, но с фундаментально разными подходами.

Rc — это shell-скрипты с последовательным исполнением. Полная прозрачность: любой администратор, знающий sh(1), может прочитать и понять любой аспект поведения системы. Порядок загрузки детерминирован — одинаков при каждой загрузке. Расширение — написание shell-скрипта. Недостатки: последовательный запуск (медленнее на системах с большим количеством сервисов), нет сокет-активации, нет встроенного мониторинга процессов (restart-on-failure требует `daemon -r`), нет нативной интеграции с cgroups.

Systemd — единый бинарный PID 1 с декларативными unit-файлами. Параллельный запуск, сокет-активация, встроенные cgroups, journald, таймеры, мониторинг состояния. Значительно сложнее внутри, отладка менее прозрачна, единая точка отказа.

Для серверных задач с десятками сервисов разница во времени загрузки обычно незначительна. Преимущество rc — в предсказуемости и простоте отладки, что на production-серверах часто важнее скорости загрузки.

## Источники

- `rc(8)`: [man.freebsd.org/cgi/man.cgi?query=rc&sektion=8](https://man.freebsd.org/cgi/man.cgi?query=rc&sektion=8)
- `rc.conf(5)`: [man.freebsd.org/rc.conf](https://man.freebsd.org/rc.conf)
- `rc.subr(8)`: [man.freebsd.org/cgi/man.cgi?query=rc.subr&sektion=8](https://man.freebsd.org/cgi/man.cgi?query=rc.subr&sektion=8)
- `rcorder(8)`: [man.freebsd.org/cgi/man.cgi?query=rcorder&sektion=8](https://man.freebsd.org/cgi/man.cgi?query=rcorder&sektion=8)
- `service(8)`: [man.freebsd.org/cgi/man.cgi?query=service&sektion=8](https://man.freebsd.org/cgi/man.cgi?query=service&sektion=8)
- `sysrc(8)`: [man.freebsd.org/cgi/man.cgi?query=sysrc&sektion=8](https://man.freebsd.org/cgi/man.cgi?query=sysrc&sektion=8)
- Practical rc.d scripting in BSD: [docs.freebsd.org/en/articles/rc-scripting/](https://docs.freebsd.org/en/articles/rc-scripting/)
- FreeBSD Handbook, Configuration and Services: [docs.freebsd.org/en/books/handbook/config/](https://docs.freebsd.org/en/books/handbook/config/)
- Klara Systems, rc(8): FreeBSD Services and Automation: [klarasystems.com/articles/rc8-freebsd-services-and-automation/](https://klarasystems.com/articles/rc8-freebsd-services-and-automation/)
- Alexander Leidinger, FreeBSD Service Jails: [leidinger.net/blog/2024/06/11/freebsd-service-jails-another-layer-in-the-security-onion/](https://www.leidinger.net/blog/2024/06/11/freebsd-service-jails-another-layer-in-the-security-onion/)

---

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

- [FreeBSD — архитектура и философия](./freebsd.md) — вводная статья цикла
- [FreeBSD — pkg и Ports Collection](./freebsd-pkg-and-ports.md) — откуда берутся rc-скрипты в `/usr/local/etc/rc.d`
- [FreeBSD — сеть](./freebsd-networking.md) — конфигурация интерфейсов через `ifconfig_*` в rc.conf
- [Linux — systemd: init, сервисы, таймеры](./linux-systemd.md) — альтернативная модель init в Linux, для сравнения
- [FreeBSD — сводный cheatsheet](./freebsd-cheatsheet.md) — быстрая справка по управлению сервисами
