Loading content...
When the kernel finishes initialization and calls run_init_process("/sbin/init"), something profound happens: user-space code begins executing for the first time. The init process (PID 1) inherits a system where the kernel is fully operational but nothing else is running—no login prompts, no services, no network, no graphical interface.
From this blank slate, PID 1 must orchestrate the entire user-space environment:
/proc, /sys, /dev)The design of the init system fundamentally shapes how a Unix system boots, operates, and shuts down. Over the decades, init systems have evolved from simple sequential scripts to sophisticated parallel service managers.
By the end of this page, you will understand: the unique responsibilities of PID 1; traditional SysVinit and runlevels; systemd architecture and unit files; service dependencies and parallelism; the critical role of orphan process reaping; and how to manage and troubleshoot init systems.
PID 1 isn't just "the first process"—it has special kernel-enforced responsibilities that no other process can fulfill:
1. Orphan Process Adoption:
When any process terminates, its children become orphans. The kernel automatically reparents orphans to PID 1. This means init must:
2. Signal Handling Special Cases:
Signals behave differently for PID 1:
3. System Lifecycle Control:
PID 1 controls system state transitions:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
// Why PID 1 must reap zombies (conceptual) // When a process exits, it becomes a zombie until its parent// calls wait() to collect the exit status // Without proper reaping in init:void bad_init() { // Fork child processes to do work for (int i = 0; i < 100; i++) { if (fork() == 0) { // Child does work then exits do_service_work(); exit(0); // Child terminates } } // Parent (init) never calls wait() // Result: 100 zombie processes consuming PID table space while(1) pause(); // Just wait forever} // Proper init implementation:void good_init() { // Set up SIGCHLD handler signal(SIGCHLD, sigchld_handler); // Start services start_services(); // Main loop while(1) { pause(); // Wait for signals }} void sigchld_handler(int sig) { int status; pid_t pid; // Reap ALL available children (multiple may have died) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { // Log child termination log_child_exit(pid, status); // Restart service if configured for auto-restart if (should_restart(pid)) { restart_service(pid); } }}| Property | Kernel Behavior | Implication |
|---|---|---|
| Signal immunity | SIGKILL/SIGSTOP ignored for PID 1 | System cannot be "killed" accidentally |
| Orphan adoption | All orphaned processes become PID 1 children | Init must wait() on unknown children |
| Cannot exit | Kernel panics if PID 1 terminates | Init must never exit; crash = system crash |
| Boot critical | System fails to boot without working init | init= parameter specifies alternative |
| Namespace root | Each PID namespace has its own PID 1 | Container init has same responsibilities |
In containers (Docker, Kubernetes), the container's entrypoint process becomes PID 1 within that PID namespace. If this process doesn't properly reap children, zombies accumulate. This is why running applications directly as PID 1 (without a proper init) can cause problems. Solutions: tini, dumb-init, or systemd inside containers.
SysVinit (System V init) was the traditional init system for Unix and Linux for decades. Though largely replaced by systemd on modern distributions, understanding SysVinit is valuable—many concepts carry forward, older systems still use it, and it provides context for why modern init systems were developed.
The /etc/inittab File:
SysVinit's behavior is controlled by /etc/inittab, which specifies:
123456789101112131415161718192021222324252627282930313233343536
# Traditional SysVinit inittab example # Default runlevel (3 = multi-user, 5 = graphical)id:3:initdefault: # System initialization script (runs before any runlevel)si::sysinit:/etc/rc.d/rc.sysinit # Runlevel scriptsl0:0:wait:/etc/rc.d/rc 0 # Haltl1:1:wait:/etc/rc.d/rc 1 # Single-userl2:2:wait:/etc/rc.d/rc 2 # Multi-user, no networkl3:3:wait:/etc/rc.d/rc 3 # Multi-user with networkl4:4:wait:/etc/rc.d/rc 4 # Unused (custom)l5:5:wait:/etc/rc.d/rc 5 # Multi-user with graphicall6:6:wait:/etc/rc.d/rc 6 # Reboot # Getty processes for virtual consoles1:2345:respawn:/sbin/mingetty tty12:2345:respawn:/sbin/mingetty tty23:2345:respawn:/sbin/mingetty tty34:2345:respawn:/sbin/mingetty tty45:2345:respawn:/sbin/mingetty tty56:2345:respawn:/sbin/mingetty tty6 # Ctrl+Alt+Delete handlingca::ctrlaltdel:/sbin/shutdown -t3 -r now # Power failure scriptpf::powerfail:/sbin/shutdown -f -h +2 "Power Failure" # inittab format: id:runlevels:action:process# id - unique 1-4 character identifier# runlevels - runlevels where this entry applies# action - wait, respawn, once, boot, sysinit, etc.# process - command to execute| Runlevel | Name | Description |
|---|---|---|
| 0 | Halt | System shutdown; all services stopped |
| 1 / S | Single-user | Minimal system for maintenance; root shell only |
| 2 | Multi-user | Multi-user without network services (Debian: full multi-user) |
| 3 | Multi-user + Network | Full multi-user with network; text mode (servers) |
| 4 | Undefined | Available for custom use |
| 5 | Graphical | Runlevel 3 plus X11/display manager |
| 6 | Reboot | System reboot |
RC Scripts:
The /etc/rc.d/rc script (or /etc/init.d/rc on some systems) manages runlevel transitions by running scripts in /etc/rcN.d/ directories:
/etc/rc3.d/
├── K01bluetooth (K = Kill; stop bluetooth)
├── K15httpd (stop web server)
├── S10network (S = Start; bring up network)
├── S20sshd (start SSH daemon)
├── S30crond (start cron daemon)
└── S99local (run local startup scripts)
Scripts prefixed with K are run with stop argument (in numeric order) to stop services.
Scripts prefixed with S are run with start argument (in numeric order) to start services.
These are symlinks to actual scripts in /etc/init.d/. The same script handles both start and stop.
SysVinit has significant limitations: purely sequential execution (slow boot), no native dependency handling, shell scripts for everything (error-prone, inconsistent), no service supervision (crashed services stay dead). These limitations drove the development of modern init systems like systemd, Upstart, and OpenRC.
systemd is the default init system for most major Linux distributions (Debian, Ubuntu, Fedora, RHEL, Arch, openSUSE). It represents a fundamental rethinking of system initialization, providing parallel startup, unified configuration, service supervision, and extensive logging integration.
Core Concepts:
| Type | Suffix | Purpose |
|---|---|---|
| Service | .service | Process/daemon management; the most common type |
| Socket | .socket | Socket activation; start service when connection arrives |
| Target | .target | Grouping unit; synchronization points (like runlevels) |
| Mount | .mount | Filesystem mount points (generated from fstab) |
| Automount | .automount | On-demand mounting when path accessed |
| Timer | .timer | Time-based activation (like cron, but integrated) |
| Path | .path | Start service when file/path changes |
| Device | .device | Device activation (generated by udev) |
| Slice | .slice | cgroup resource management grouping |
| Scope | .scope | Externally created process groups (containers, user sessions) |
systemd provides compatibility with runlevel commands. Runlevel 3 = multi-user.target (text mode, networking). Runlevel 5 = graphical.target (display manager). The default target is set via: systemctl set-default graphical.target. Check current target: systemctl get-default.
Unit files are INI-style configuration files that define how systemd manages services and other resources. Understanding unit file syntax is essential for administration and debugging.
Unit File Locations (in order of precedence):
/etc/systemd/system/ — Local administrator overrides (highest priority)/run/systemd/system/ — Runtime units (transient)/lib/systemd/system/ — Distribution-provided units (packaged)Anatomy of a Service Unit:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
# Example systemd service unit file# /etc/systemd/system/myapp.service [Unit]# Metadata and dependenciesDescription=My Application ServerDocumentation=https://example.com/docsAfter=network.target postgresql.serviceWants=postgresql.service# After= : we start after these (ordering only)# Wants= : try to start these with us (weak dependency)# Requires= : must have these (strong dependency, fail if they fail) [Service]# How to run the serviceType=simple # simple, forking, oneshot, notify, dbus, idleUser=myapp # Run as this userGroup=myapp # Run as this groupEnvironment=NODE_ENV=productionEnvironmentFile=-/etc/myapp/env # "-" means optionalWorkingDirectory=/opt/myapp ExecStartPre=/opt/myapp/pre-start.sh # Run before main processExecStart=/opt/myapp/bin/server --port 8080ExecReload=/bin/kill -HUP $MAINPID # How to reload configExecStop=/opt/myapp/graceful-shutdown.sh # Restart behaviorRestart=on-failure # no, always, on-failure, on-abnormal, on-abort, on-watchdogRestartSec=5 # Wait 5 seconds before restartStartLimitBurst=3 # Max 3 restarts...StartLimitIntervalSec=60 # ...per 60 seconds # Resource limitsLimitNOFILE=65536 # Max open filesLimitNPROC=4096 # Max processesMemoryMax=2G # cgroup memory limitCPUQuota=200% # Max 2 CPU cores worth # Security hardeningNoNewPrivileges=yesProtectSystem=strictProtectHome=yesPrivateTmp=yesReadWritePaths=/var/lib/myapp [Install]# How "systemctl enable" integrates this unitWantedBy=multi-user.target# This creates symlink:# /etc/systemd/system/multi-user.target.wants/myapp.service| Section | Directive | Purpose |
|---|---|---|
| [Unit] | After/Before | Ordering (but not dependency; doesn't pull in units) |
| [Unit] | Requires/Wants | Dependencies (Requires=strong, Wants=weak) |
| [Unit] | BindsTo | Strong dependency + stop when dependency stops |
| [Service] | Type | Process model: simple (default), forking, oneshot, notify |
| [Service] | ExecStart | Main command to run |
| [Service] | Restart | When to restart: no, always, on-failure, on-watchdog |
| [Service] | User/Group | Run as specified user/group |
| [Service] | Environment | Set environment variables |
| [Install] | WantedBy | Target that pulls this unit via enable |
123456789101112131415161718192021222324252627282930313233343536
# Essential systemctl commands # Service managementsystemctl start nginx.service # Start nowsystemctl stop nginx.service # Stop nowsystemctl restart nginx.service # Stop then startsystemctl reload nginx.service # Reload configuration (SIGHUP)systemctl status nginx.service # Show status and recent logssystemctl is-active nginx # Exit 0 if running # Enable/disable (auto-start at boot)systemctl enable nginx.service # Create symlinks for bootsystemctl disable nginx.service # Remove symlinkssystemctl is-enabled nginx # Exit 0 if enabled # Masking (completely prevent starting)systemctl mask nginx.service # Link to /dev/nullsystemctl unmask nginx.service # Remove mask # System statesystemctl list-units # Show all loaded unitssystemctl list-units --failed # Show failed unitssystemctl list-unit-files # Show all installed units # Dependenciessystemctl list-dependencies nginx # Show dependency treesystemctl list-dependencies --reverse nginx # What depends on this # Reload configuration after editing unit filessystemctl daemon-reload # Required after modifying .service files # Debuggingsystemctl cat nginx.service # Show unit file contentssystemctl show nginx.service # Show all propertiessystemctl edit nginx.service # Create override file in /etcjournalctl -u nginx.service # View service logsAfter modifying any unit file, you MUST run 'systemctl daemon-reload' before the changes take effect. This tells systemd to rescan unit files. Forgetting this is a common source of 'my changes aren't working' issues.
systemd's dependency system is central to its parallel startup capability. Understanding the difference between ordering and requirement dependencies is crucial.
Ordering vs. Requirement:
You typically need both for services that depend on each other:
[Unit]
After=postgresql.service # Order: start postgresql first
Requires=postgresql.service # Pull in: also start postgresql
| Directive | Type | Behavior on Failure | Common Use |
|---|---|---|---|
| Wants= | Requirement (weak) | Unit starts even if wanted units fail | Optional dependencies |
| Requires= | Requirement (strong) | Unit fails if required units fail to start | Hard dependencies |
| Requisite= | Requirement (pre-check) | Unit fails if requisite not already running | Must be running before check |
| BindsTo= | Requirement (strong) | Unit stops if bound unit stops | Tight coupling (like mount/service) |
| PartOf= | Lifecycle | Propagates stop/restart from parent | Related units stop together |
| Conflicts= | Anti-dependency | Stop conflicting units when starting | Mutually exclusive services |
| After= | Ordering | N/A (just ordering) | Sequence without dependency |
| Before= | Ordering | N/A (just ordering) | Ensure we start first |
1234567891011121314151617181920212223242526272829303132333435363738394041424344
# Viewing and debugging dependencies # Show what a unit needs$ systemctl list-dependencies nginx.servicenginx.service● ├─-.slice● ├─system.slice● └─sysinit.target● ├─dev-hugepages.mount● ├─dev-mqueue.mount● ... # Show what needs this unit (reverse)$ systemctl list-dependencies --reverse nginx.servicenginx.service● └─multi-user.target● └─graphical.target # Show ordering$ systemctl show nginx.service -p AfterAfter=basic.target system.slice -.slice sysinit.target network.target # Dependency debugging: why did unit start?$ systemctl status nginx Triggered By: ● multi-user.target # Check if ordering is satisfied$ systemctl list-jobs # Shows pending jobs and their state # Common dependency patterns: # Web app needing database[Unit]After=network.target postgresql.serviceRequires=postgresql.service # Service needing specific mount[Unit]After=mnt-data.mountBindsTo=mnt-data.mount # Stop service if unmounted # Mutually exclusive configs[Unit]Conflicts=nginx.service # Can't run with nginxsystemd adds implicit dependencies automatically. Mount units add After=local-fs-pre.target. Service units with Type=dbus add Requires=dbus.socket. Units in /etc/systemd/system have implicit After= on units in /usr/lib/systemd/system. Check 'systemctl show <unit>' to see all derived dependencies.
Socket activation is a powerful systemd feature that defers starting services until they're actually needed. Instead of starting a service at boot, systemd creates the socket and starts the service only when a connection arrives.
Benefits:
1234567891011121314151617181920212223242526272829303132
# Socket-activated service example # /etc/systemd/system/myapp.socket[Unit]Description=My App Socket [Socket]ListenStream=8080 # TCP port# Or: ListenStream=/run/myapp.sock # Unix socket# Or: ListenDatagram=5353 # UDPAccept=no # no: pass socket to one service instance # yes: fork new instance per connection (like inetd) [Install]WantedBy=sockets.target # /etc/systemd/system/myapp.service[Unit]Description=My App Service# Note: no After/Wants for socket - systemd handles it [Service]Type=simpleExecStart=/opt/myapp/server # Receives socket as FD 3# Server must accept socket via sd_listen_fds() or inherit FD 3 # For Accept=no (single instance):# StandardInput=socket # Socket becomes stdin (for simple protocols) # Restart handling with socket activationRestart=on-failure# New connections queue on socket while service restartsSocket Activation in Practice:
Common system services using socket activation:
1234567891011121314151617181920212223242526272829
# Working with socket-activated services # List all sockets$ systemctl list-socketsLISTEN UNIT ACTIVATES/run/dbus/system_bus_socket dbus.socket dbus.service/run/docker.sock docker.socket docker.service[::]:22 sshd.socket sshd.service # Enable socket activation for SSH$ systemctl stop sshd.service # Stop direct service$ systemctl disable sshd.service$ systemctl enable sshd.socket # Enable socket instead$ systemctl start sshd.socket # Now sshd only runs when someone connects$ ss -tlnp | grep 22LISTEN 0 128 *:22 *:* users:(("systemd",pid=1,fd=36))# Note: systemd holds socket, not sshd $ ssh localhost# NOW sshd starts$ systemctl status sshd.serviceActive: active (running) # Started on demand # Converting existing service to socket activation:# 1. Create .socket unit matching .service name# 2. Ensure service can inherit socket (sd_listen_fds or fd=3)# 3. Disable .service, enable .socketsystemd passes sockets to services via the sd_listen_fds() protocol. Sockets are passed as file descriptors starting at 3 (after stdin=0, stdout=1, stderr=2). The SD_LISTEN_FDS_START (3) environment variable indicates the first FD, and LISTEN_FDS contains the count. Most modern daemons support this natively.
systemd-journald provides structured, indexed logging that replaces traditional syslog. Logs are stored in binary format with extensive metadata, enabling powerful filtering and analysis.
Journal Features:
1234567891011121314151617181920212223242526272829303132333435363738394041424344
# Essential journalctl commands # View all logs (newest first)$ journalctl$ journalctl -r # Reverse (newest first)$ journalctl -f # Follow (like tail -f)$ journalctl -n 100 # Last 100 lines # Filter by unit$ journalctl -u nginx.service$ journalctl -u nginx -u php-fpm # Multiple units # Filter by priority (0=emerg to 7=debug)$ journalctl -p err # Errors and above$ journalctl -p warning..err # Warning through error # Filter by time$ journalctl --since "2024-01-15"$ journalctl --since "10 minutes ago"$ journalctl --since "09:00" --until "10:00" # Filter by boot$ journalctl -b # Current boot only$ journalctl -b -1 # Previous boot$ journalctl --list-boots # List all stored boots # Kernel messages (like dmesg)$ journalctl -k$ journalctl -k -b -1 # Kernel msgs from previous boot # Output formats$ journalctl -o short # Default: syslog-like$ journalctl -o verbose # All metadata fields$ journalctl -o json # JSON format$ journalctl -o cat # Message only (no timestamp) # Disk usage and maintenance$ journalctl --disk-usage$ journalctl --vacuum-size=500M # Trim to 500MB$ journalctl --vacuum-time=7d # Keep only 7 days # Advanced filtering$ journalctl _SYSTEMD_UNIT=nginx.service _PID=1234$ journalctl CONTAINER_NAME=mycontainer| Setting | Location | Use Case |
|---|---|---|
| Storage=persistent | /var/log/journal/ | Logs survive reboot (default if dir exists) |
| Storage=volatile | /run/log/journal/ | Logs in tmpfs, lost on reboot |
| Storage=auto | persistent if dir exists | Default behavior |
| Storage=none | N/A | Disable journal storage entirely |
systemd-journald can forward logs to a traditional syslog daemon (rsyslog, syslog-ng) via /run/systemd/journal/syslog socket. This allows keeping familiar /var/log files while gaining journal benefits. Set ForwardToSyslog=yes in /etc/systemd/journald.conf. Most distributions do this by default.
While systemd dominates modern Linux, alternative init systems exist and are used in production environments. Understanding options helps you work with diverse systems.
OpenRC:
Used by Gentoo, Alpine Linux, and Artix Linux. OpenRC provides a middle ground—dependency-based parallel startup with traditional shell scripts:
#!/sbin/openrc-run
# /etc/init.d/myservice
depend() {
need net
use dns
}
start() {
ebegin "Starting myservice"
start-stop-daemon --start --exec /usr/bin/myservice
eend $?
}
stop() {
ebegin "Stopping myservice"
start-stop-daemon --stop --exec /usr/bin/myservice
eend $?
}
| Feature | SysVinit | OpenRC | systemd |
|---|---|---|---|
| Parallel startup | No | Yes | Yes |
| Dependency handling | Script order only | Explicit dependencies | Explicit + implicit |
| Service supervision | No | Optional (supervise-daemon) | Yes (built-in) |
| Config format | Shell scripts | Shell scripts | INI-style unit files |
| Socket activation | No | No | Yes |
| cgroup integration | No | No | Yes |
| Logging | syslog | syslog | Journal + syslog |
| Used by | Legacy, embedded | Gentoo, Alpine | Most major distros |
runit:
Minimalist init and service supervision; used by Void Linux, formerly by Gentoo:
/etc/sv/myservice/
├── run # Executable that runs the service (must stay in foreground)
├── finish # Optional: runs when service exits
└── log/
└── run # Logging service (usually svlogd)
s6:
Advanced supervision suite, emphasis on correctness and security:
Containers need minimal init for zombie reaping. Options: tini (Docker's default --init), dumb-init (Yelp's solution), s6-overlay (full supervision). Using your application directly as PID 1 works but won't reap orphans—problematic for applications that fork. Best practice: always use a proper init in containers.
Init system issues can prevent boot, cause service failures, or create subtle operational problems. Mastering troubleshooting techniques is essential for system administration.
Common Issues and Solutions:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
# systemd Troubleshooting Techniques # 1. Service won't start - check status and logs$ systemctl status myapp.service● myapp.service - My Application Loaded: loaded (/etc/systemd/system/myapp.service; enabled) Active: failed (Result: exit-code) since ... Process: 1234 ExecStart=/opt/myapp/bin/server (code=exited, status=1/FAILURE)$ journalctl -u myapp.service -n 50 # 2. Service starts but immediately dies$ systemctl start myapp$ systemctl status myapp# Check: Is Type= correct? Forking service with Type=simple fails# Check: Does ExecStart run in foreground?# Check: Does the binary exist and have execute permission? # 3. Boot hangs - identify the blocking unit# At stuck boot, press Escape to see status# Or add 'systemd.log_level=debug systemd.log_target=console' to kernel cmdline$ systemctl list-jobs # Shows blocked/waiting jobs # 4. View startup time analysis$ systemd-analyzeStartup finished in 3.456s (kernel) + 7.890s (userspace) = 11.346s$ systemd-analyze blame # Slowest units$ systemd-analyze critical-chain # Blocking path to target$ systemd-analyze plot > boot.svg # Visual timeline # 5. Service fails to stop (timeout)$ systemctl stop myapp # Hangs...# Check: Is ExecStop doing something wrong?# Check: Is the process ignoring SIGTERM?$ systemctl kill myapp # Send SIGKILL # 6. Dependency issues$ systemctl list-dependencies myapp --all$ systemctl list-dependencies myapp --reverse # 7. Unit file syntax errors$ systemd-analyze verify /etc/systemd/system/myapp.service# Shows parsing errors # 8. Emergency recovery# Boot with: systemd.unit=rescue.target (single-user)# Or: systemd.unit=emergency.target (pre-mount root only)# Or: init=/bin/bash (bypass init entirely)ausearch -m AVC) for denials.If systemd is completely broken, boot with init=/bin/bash on the kernel command line. This bypasses init entirely and gives you a root shell (read-only root by default; run 'mount -o remount,rw /' to write). From there, fix or reinstall broken packages. Exit the shell to continue boot attempt.
The init process transforms a kernel-only system into a fully operational user environment. Understanding init—whether SysVinit, systemd, or alternatives—is fundamental to system administration.
What's Next:
With init running and services started, the system approaches its operational state. The final page examines the complete system initialization process—from the first filesystem mounts through service readiness to reaching the login prompt or graphical desktop. We'll see how all the components we've studied work together to produce a usable system.
You now understand the init process: PID 1's special role, SysVinit's historical approach, systemd's modern architecture, unit files and dependencies, socket activation, journald logging, and troubleshooting techniques. The init system is the bridge from kernel to user space—its design shapes everything about how the system operates.