FreeBSD rc System: Service Management Guide
FreeBSD's rc system manages everything that happens between kernel boot and login prompt. It starts services, configures network interfaces, mounts filesystems, sets sysctls, and runs periodic maintenance tasks. Unlike systemd on Linux, the rc system is built on shell scripts -- readable, debuggable, and predictable.
The rc system has three core components: /etc/rc.conf (what to start and how to configure it), /etc/rc.d/ and /usr/local/etc/rc.d/ (the scripts that do the work), and the service command (the interface you use to control everything).
This guide covers all three in depth, including how to write your own rc scripts.
rc.conf: The Central Configuration File
/etc/rc.conf is the single most important configuration file on a FreeBSD system. It controls which services start at boot, how network interfaces are configured, and system-wide settings.
Structure
shcat /etc/rc.conf
A typical production server's rc.conf:
shell# Network hostname="web1.example.com" ifconfig_em0="inet 10.0.0.10 netmask 255.255.255.0" defaultrouter="10.0.0.1" # Services sshd_enable="YES" nginx_enable="YES" postgresql_enable="YES" pf_enable="YES" # Firewall pf_rules="/etc/pf.conf" # ZFS zfs_enable="YES" # Jails bastille_enable="YES" # Time ntpd_enable="YES" # Logging syslogd_flags="-ss" # Misc clear_tmp_enable="YES" sendmail_enable="NONE" dumpdev="AUTO"
Each line follows the pattern variable="value". The convention for enabling services is servicename_enable="YES".
rc.conf.local
For machine-specific overrides, use /etc/rc.conf.local. It is read after rc.conf and overrides any conflicting settings:
sh# /etc/rc.conf -- base configuration (shared across fleet) # /etc/rc.conf.local -- per-machine overrides cat /etc/rc.conf.local
shellhostname="web2.example.com" ifconfig_em0="inet 10.0.0.11 netmask 255.255.255.0"
Editing rc.conf with sysrc
Never edit rc.conf by hand in scripts. Use sysrc for safe, atomic modifications:
sh# Set a value sysrc nginx_enable="YES" # Delete a value sysrc -x nginx_enable # Query a value sysrc nginx_enable # Show all settings sysrc -a # Edit rc.conf.local instead sysrc -f /etc/rc.conf.local hostname="web2.example.com" # Show where a value is defined sysrc -A nginx_enable
sysrc handles quoting, prevents duplicate entries, and validates syntax. It is the correct way to modify rc.conf programmatically.
rc.conf.d Directory
For complex configurations, you can split rc.conf into per-service files under /etc/rc.conf.d/:
sh# Instead of putting everything in /etc/rc.conf: cat > /etc/rc.conf.d/nginx << 'EOF' nginx_enable="YES" nginx_flags="-c /usr/local/etc/nginx/nginx.conf" nginx_pidfile="/var/run/nginx.pid" EOF
Each file is named after the service and sourced when that service's rc script runs. This keeps configuration modular on complex systems.
The service Command
The service command is the primary interface for managing services:
sh# Start a service service nginx start # Stop a service service nginx stop # Restart a service service nginx restart # Reload configuration (if supported) service nginx reload # Check service status service nginx status # Run a one-time command (even if not enabled in rc.conf) service nginx onestart service nginx onestop # List all enabled services service -e # List all available services service -l # Show the rc.d script path for a service service -r nginx
onestart vs start
service nginx start only works if nginx_enable="YES" is set in rc.conf. If you want to start a service temporarily without enabling it permanently:
shservice nginx onestart
This is useful for testing. onestart runs the service without checking the _enable variable.
Checking What Is Running
sh# All enabled services and their status service -e | while read svc; do service "$svc" status 2>/dev/null done # Simple process check pgrep -l nginx
rc.d Scripts: How Services Work
Every service on FreeBSD is managed by an rc.d script. Base system services live in /etc/rc.d/. Third-party services (from packages) live in /usr/local/etc/rc.d/.
Anatomy of an rc.d Script
Look at a real one:
shcat /usr/local/etc/rc.d/nginx
sh#!/bin/sh # PROVIDE: nginx # REQUIRE: LOGIN cleanvar # KEYWORD: shutdown . /etc/rc.subr name="nginx" rcvar=nginx_enable command="/usr/local/sbin/nginx" pidfile="/var/run/nginx.pid" required_files="/usr/local/etc/nginx/nginx.conf" nginx_enable=${nginx_enable:-"NO"} nginx_flags=${nginx_flags:-""} extra_commands="reload configtest" reload_cmd="nginx_reload" configtest_cmd="nginx_configtest" nginx_reload() { echo "Reloading ${name}." ${command} -s reload } nginx_configtest() { echo "Checking ${name} configuration." ${command} -t } load_rc_config $name run_rc_command "$1"
Key elements:
- PROVIDE -- The name this script provides (used for dependency resolution)
- REQUIRE -- Services that must start before this one
- KEYWORD -- Special flags (
shutdownmeans run during shutdown) . /etc/rc.subr-- Loads the rc framework functionsname-- The service namercvar-- The rc.conf variable that enables this servicecommand-- Path to the daemon binarypidfile-- Where the daemon writes its PIDrequired_files-- Files that must exist for the service to startextra_commands-- Additional commands beyond start/stop/restartload_rc_config-- Loads variables from rc.confrun_rc_command-- Executes the requested action
Dependency System
The REQUIRE and PROVIDE tags create a dependency graph. The rc system uses rcorder to determine the correct startup sequence:
sh# Show the boot order rcorder /etc/rc.d/* /usr/local/etc/rc.d/*
Common dependency targets:
| Target | When |
|---|---|
| FILESYSTEMS | After filesystems are mounted |
| NETWORKING | After network interfaces are up |
| SERVERS | After basic server services |
| DAEMON | After daemon infrastructure |
| LOGIN | After login services (PAM, etc.) |
A service that needs networking and filesystems:
shell# REQUIRE: NETWORKING FILESYSTEMS
A service that must start before another:
shell# PROVIDE: mydb # REQUIRE: NETWORKING # In another script: # REQUIRE: mydb
Writing Custom rc.d Scripts
Minimal Script
Create /usr/local/etc/rc.d/myapp:
sh#!/bin/sh # PROVIDE: myapp # REQUIRE: LOGIN NETWORKING # KEYWORD: shutdown . /etc/rc.subr name="myapp" rcvar=myapp_enable command="/usr/local/bin/myapp" pidfile="/var/run/${name}.pid" myapp_enable=${myapp_enable:-"NO"} load_rc_config $name run_rc_command "$1"
Make it executable and enable:
shchmod +x /usr/local/etc/rc.d/myapp sysrc myapp_enable="YES" service myapp start
Script with Custom Start/Stop
For applications that do not daemonize themselves:
sh#!/bin/sh # PROVIDE: myapp # REQUIRE: LOGIN NETWORKING # KEYWORD: shutdown . /etc/rc.subr name="myapp" rcvar=myapp_enable command="/usr/local/bin/myapp" command_args="-c /usr/local/etc/myapp.conf" pidfile="/var/run/${name}.pid" myapp_enable=${myapp_enable:-"NO"} myapp_user=${myapp_user:-"www"} myapp_group=${myapp_group:-"www"} myapp_logfile=${myapp_logfile:-"/var/log/myapp.log"} start_cmd="${name}_start" stop_cmd="${name}_stop" status_cmd="${name}_status" myapp_start() { echo "Starting ${name}." /usr/sbin/daemon -f -p ${pidfile} -u ${myapp_user} \ ${command} ${command_args} >> ${myapp_logfile} 2>&1 } myapp_stop() { if [ -f ${pidfile} ]; then echo "Stopping ${name}." kill $(cat ${pidfile}) rm -f ${pidfile} else echo "${name} is not running." fi } myapp_status() { if [ -f ${pidfile} ] && kill -0 $(cat ${pidfile}) 2>/dev/null; then echo "${name} is running as pid $(cat ${pidfile})." else echo "${name} is not running." return 1 fi } load_rc_config $name run_rc_command "$1"
The daemon utility is key. It handles backgrounding, PID file creation, and user switching. Use it for any application that does not daemonize itself.
Script with Environment Variables
sh#!/bin/sh # PROVIDE: myapp # REQUIRE: LOGIN NETWORKING # KEYWORD: shutdown . /etc/rc.subr name="myapp" rcvar=myapp_enable command="/usr/local/bin/myapp" pidfile="/var/run/${name}.pid" myapp_enable=${myapp_enable:-"NO"} myapp_env=${myapp_env:-""} myapp_chdir=${myapp_chdir:-"/usr/local/www/myapp"} start_cmd="${name}_start" myapp_start() { echo "Starting ${name}." cd ${myapp_chdir} /usr/bin/env ${myapp_env} /usr/sbin/daemon -f -p ${pidfile} \ ${command} ${command_args} } load_rc_config $name run_rc_command "$1"
Set environment variables in rc.conf:
shsysrc myapp_enable="YES" sysrc myapp_env="DATABASE_URL=postgres://localhost/mydb SECRET_KEY=abc123" sysrc myapp_chdir="/usr/local/www/myapp"
Script with Health Checks
sh#!/bin/sh # PROVIDE: myapp # REQUIRE: LOGIN NETWORKING postgresql # KEYWORD: shutdown . /etc/rc.subr name="myapp" rcvar=myapp_enable command="/usr/local/bin/myapp" pidfile="/var/run/${name}.pid" myapp_enable=${myapp_enable:-"NO"} extra_commands="health" health_cmd="${name}_health" myapp_health() { local response response=$(fetch -qo - http://127.0.0.1:8080/health 2>/dev/null) if [ "$response" = "ok" ]; then echo "${name} is healthy." return 0 else echo "${name} health check failed." return 1 fi } load_rc_config $name run_rc_command "$1"
Use it:
shservice myapp health
Advanced rc.conf Techniques
Profiles
Some services support profiles for running multiple instances:
shsysrc jail_enable="YES" sysrc jail_list="web db cache" sysrc jail_web_hostname="web.example.com" sysrc jail_db_hostname="db.example.com"
Conditional Configuration
rc.conf supports shell-like conditional logic when sourced:
sh# /etc/rc.conf.d/nginx case $(hostname) in web*) nginx_enable="YES" nginx_flags="-c /usr/local/etc/nginx/web.conf" ;; proxy*) nginx_enable="YES" nginx_flags="-c /usr/local/etc/nginx/proxy.conf" ;; *) nginx_enable="NO" ;; esac
Service Flags
Most services accept flags through rc.conf:
shsysrc sshd_flags="-o Port=2222 -o PermitRootLogin=no" sysrc syslogd_flags="-ss" # Disable remote logging sysrc ntpd_flags="-g" # Allow large initial offset sysrc sendmail_enable="NONE" # Disable all sendmail components
Network Interface Configuration
rc.conf handles all network configuration:
sh# Static IP sysrc ifconfig_em0="inet 10.0.0.10 netmask 255.255.255.0" sysrc defaultrouter="10.0.0.1" # DHCP sysrc ifconfig_em0="DHCP" # VLAN sysrc vlans_em0="100 200" sysrc ifconfig_em0_100="inet 10.1.0.10/24" sysrc ifconfig_em0_200="inet 10.2.0.10/24" # Bridge sysrc cloned_interfaces="bridge0" sysrc ifconfig_bridge0="addm em0 addm em1 up" # Wireless sysrc wlans_iwn0="wlan0" sysrc ifconfig_wlan0="WPA DHCP"
Debugging rc Scripts
Verbose Boot
To see every rc script as it executes during boot:
sh# At the boot loader prompt set boot_verbose="YES" boot
Or permanently:
shsysrc rc_startmsgs="YES"
Running Scripts in Debug Mode
sh# Trace a specific script sh -x /usr/local/etc/rc.d/nginx start # Check syntax without running sh -n /usr/local/etc/rc.d/myapp # Check dependency order rcorder /etc/rc.d/* /usr/local/etc/rc.d/* | head -50
Common Problems
Service will not start -- "not enabled"
sh# Check if enabled sysrc nginx_enable # If NO, either enable it or use onestart: service nginx onestart
PID file stale after crash
sh# Remove stale PID file rm /var/run/nginx.pid service nginx start
Service starts but immediately exits
sh# Check logs tail -50 /var/log/messages tail -50 /var/log/myapp.log # Run the command manually to see errors su -m www -c '/usr/local/bin/myapp -c /usr/local/etc/myapp.conf'
Dependency order wrong
sh# Check what rcorder produces rcorder /etc/rc.d/* /usr/local/etc/rc.d/* | grep -A2 -B2 myapp
The rc System Boot Sequence
Understanding the boot sequence helps debug startup issues:
- Kernel loads and runs
/sbin/init initreads/etc/ttysand runs/etc/rc/etc/rcsources/etc/rc.subrand/etc/rc.confrcorderdetermines script execution order from/etc/rc.d/- Scripts run in dependency order: filesystems, networking, base services
/usr/local/etc/rc.d/scripts run after base system scripts- Login prompt appears
The entire sequence is shell scripts. If something goes wrong, you can read the scripts and understand exactly what happened.
Comparison with systemd
| Feature | FreeBSD rc | Linux systemd |
|---|---|---|
| Language | Shell scripts | Unit files + C daemon |
| Dependencies | REQUIRE/PROVIDE tags | After=/Before= directives |
| Parallel startup | No (sequential) | Yes |
| Socket activation | No | Yes |
| Resource limits | Via login.conf/rctl | Via cgroups |
| Logging | syslog | journald |
| Configuration | /etc/rc.conf (text) | Unit files + dbus |
| Complexity | Low | High |
| Debuggability | Excellent (sh -x) | Moderate (journalctl) |
FreeBSD's rc system is simpler, more transparent, and easier to debug. Systemd is more feature-rich and starts services faster through parallelization. For server workloads where boot time is irrelevant (servers run for months), rc's simplicity is an advantage.
FAQ
How do I make a service start at boot?
Set servicename_enable="YES" in /etc/rc.conf using sysrc:
shsysrc nginx_enable="YES"
Then either reboot or start it now with service nginx start.
What is the difference between /etc/rc.d/ and /usr/local/etc/rc.d/?
/etc/rc.d/ contains scripts for the base system (sshd, syslogd, cron, networking). /usr/local/etc/rc.d/ contains scripts for third-party packages installed via pkg or ports. Never put custom scripts in /etc/rc.d/ -- use /usr/local/etc/rc.d/.
Can I run services in parallel on FreeBSD?
The default rc system runs services sequentially. There is no built-in parallel execution like systemd. In practice, this adds a few seconds to boot time -- negligible for servers. If parallel startup matters, look at rc.d/rcorder with the -p flag (experimental).
How do I disable sendmail completely?
shsysrc sendmail_enable="NONE" sysrc sendmail_submit_enable="NO" sysrc sendmail_outbound_enable="NO" sysrc sendmail_msp_queue_enable="NO"
How do I view service logs?
FreeBSD services log to syslog by default. Check /var/log/messages and /var/log/daemon.log. Some services log to their own files (check the service's configuration). Use tail -f /var/log/messages to watch logs in real time.
Can I use the rc system inside jails?
Yes. Jails have their own /etc/rc.conf and run their own rc scripts. Services inside a jail are managed with service just like on the host. The jail's rc.conf is independent of the host's rc.conf.