Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Linux Process Management and Signals

By Kokil Thapa | Last reviewed: September 2026

Linux process management and signals decide whether your web stack stays online or silently eats RAM until the server locks up. Every PHP-FPM worker, MySQL connection, Redis client, and Laravel queue job is a process with a PID, parent, state, and signal handlers. On real client projects I maintain on Ubuntu 22/24, most "mysterious" outages trace back to orphaned workers, wrong stop signals, or a full process table. This guide covers the commands, signals, and systemd patterns you actually use when debugging production servers — not textbook theory.

If you run Laravel, WordPress, or custom APIs on Linux, process hygiene belongs in the same checklist as automated database backups and firewall rules. The kernel tracks each executable as a process tree rooted at PID 1 — historically init, now systemd on Ubuntu. Child processes inherit environment variables, file descriptors, and signal disposition from their parent. When a parent dies without reaping children, you get zombies. When children ignore SIGTERM, deploy scripts hang. Both scenarios show up weekly on shared EC2 boxes where I run Deployer 7 releases.

What is Linux process management and how does the process lifecycle work?

A process is a running instance of a program. The kernel assigns it a unique Process ID (PID), a Parent PID (PPID), a user ID, a current state, and resource counters for CPU time and memory (RSS/VMS). Understanding that model is the foundation of Linux process management and signals.

Processes move through several states during their lifetime:

  • Running (R): executing on a CPU core or ready in the run queue.
  • Interruptible sleep (S): waiting for I/O — disk, network, or a lock.
  • Uninterruptible sleep (D): blocked on hardware; cannot be killed until the driver returns.
  • Stopped (T): paused by SIGSTOP or a debugger.
  • Zombie (Z): exited but not yet reaped by the parent; holds a slot in the process table.
Linux Process LifecycleForkclone() syscallRunningstate RSleepingstate S or DExitwait()Zombie (Z)parent must reapStopped (T)SIGSTOP / debugOrphanadopted by initPID 1 (systemd) owns orphaned children on Ubuntu 22/24Each process: PID, PPID, UID, niceness, open FDs, cgroup
Linux process management lifecycle — fork, run, sleep, exit, and failure states like zombies

Process identifiers you should know

Every troubleshooting session starts with three numbers:

  1. PID — the process itself; you pass this to kill.
  2. PPID — who spawned it; useful when hunting runaway shell scripts.
  3. PGID / SID — process group and session ID; job control and daemon grouping rely on these.

On a typical Laravel host, systemd (PID 1) starts apache2 or nginx. The web server forks php-fpm master, which manages worker pools. Queue workers started via supervisor or systemd units sit alongside cron-triggered php artisan schedule:run children. That tree is what you map when CPU spikes at 3 AM.

How do you list, monitor, and control processes on a Linux server?

Command-line tools are still the fastest path when SSH is your only interface. I reach for these daily on production boxes — often before opening a Netdata monitoring dashboard.

Listing processes with ps and pgrep

# Full process list, human-readable
ps aux

# Tree view — see parent/child relationships
ps auxf

# Find PHP-FPM workers for pool www
ps aux | grep '[p]hp-fpm: pool www'

# Return only PIDs matching a pattern
pgrep -a php-fpm
pgrep -u www-data -a artisan

The ps aux columns matter: %CPU, %MEM, VSZ, RSS, STAT, and START. A worker stuck in D state usually means storage or NFS trouble, not application logic. High RSS on a single PHP worker often signals a memory leak in a long-running queue job.

Live monitoring with top and htop

# Default live view; press M to sort by memory, P by CPU
top

# Better UI — install once per server
sudo apt install htop
htop

Inside top, press k to send a signal to a PID without leaving the TUI. For sustained observation, I prefer htop because it shows the tree and per-process thread counts. Pair this with log rotation checks when disk I/O wait climbs — slow logs often correlate with processes stuck in uninterruptible sleep.

Changing priority with nice and renice

# Start a backup script at lower priority (range -20 to 19; higher nice = lower priority)
nice -n 10 mysqldump -u backup myapp > /var/backups/myapp.sql

# Lower priority of an already-running PID
sudo renice +5 -p 4821

On budget VPS hosts common in Nepal (Rs 1,500–3,000/month, ~USD 11–22), CPU contention between MySQL and PHP-FPM is routine. Bumping backup jobs to nice 10 or 15 keeps checkout flows responsive during nightly dumps.

What are Linux signals and which ones matter for production servers?

Signals are asynchronous notifications sent to processes. They can come from the kernel, another process, or the terminal driver. The canonical reference is the Linux manual page signal(7) on man7.org. Each signal has a default action: terminate, ignore, stop, or continue.

A process may register a handler with signal() or sigaction(). Until then, the default applies. That distinction explains why kill -9 works when kill -15 does not — SIGKILL and SIGSTOP cannot be caught or ignored.

Linux Signal Delivery FlowSenderkill, systemdKernelpending queueTarget PIDphp-fpm workerActionexit / ignoreCustom handlergraceful shutdownDefault actionterminate processUncatchableSIGKILL, SIGSTOPSignal delivered on next kernel return to userspaceSIGTERM allows cleanup; SIGKILL does not flush buffers
How Linux process management and signals travel from sender through the kernel to the target process

Essential signals for web developers and sysadmins

SignalNumberDefault actionTypical use
SIGHUP1TerminateReload config without full restart — Apache graceful, some daemons
SIGINT2TerminateCtrl+C in terminal; interrupt foreground scripts
SIGQUIT3Core dump + terminateDebug crashes; rarely used in production automation
SIGKILL9Terminate (forced)Last resort for stuck workers; no cleanup
SIGTERM15TerminateDefault for kill and systemctl stop; preferred graceful stop
SIGUSR110Terminatenginx reopen logs; PHP-FPM status on some builds
SIGSTOP19StopPause process; cannot be ignored
SIGCONT18ContinueResume after SIGSTOP

Sending signals with kill, killall, and pkill

# Default SIGTERM (15) — ask process to exit cleanly
kill 3847

# Explicit signal by name or number
kill -TERM 3847
kill -15 3847
kill -HUP 892   # reload nginx master after cert renewal

# Kill all processes matching a name — use carefully in production
pkill -TERM -f 'artisan queue:work'
killall -TERM php-fpm8.3

# Nuclear option — only when TERM failed and service is wedged
kill -9 3847

Always try SIGTERM first. I've seen corrupted MySQL tables and half-written uploads after teams reached for SIGKILL during a routine deploy. Laravel queue workers handle SIGTERM correctly when --max-time and graceful shutdown are configured; systemd sends TERM first, then KILL after TimeoutStopSec.

How does systemd fit into Linux process management on Ubuntu?

Modern Ubuntu servers delegate long-running service control to systemd. Units define how processes start, restart, and stop. The official systemd.service documentation describes directives that map directly to signal behaviour.

If you already read my notes on managing services with systemd, this section connects that article to signal-level detail.

Unit files and stop behaviour

# /etc/systemd/system/laravel-worker@.service
[Unit]
Description=Laravel Queue Worker %i
After=network.target mysql.service redis.service

[Service]
User=www-data
WorkingDirectory=/var/www/myapp/current
ExecStart=/usr/bin/php artisan queue:work redis --sleep=3 --max-time=3600
Restart=always
RestartSec=5
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
SendSIGKILL=yes

[Install]
WantedBy=multi-user.target

Key directives:

  • KillSignal — which signal systemd sends on stop (default SIGTERM).
  • TimeoutStopSec — wait time before escalation.
  • SendSIGKILL — whether to send SIGKILL after timeout.
  • KillModecontrol-group kills all processes in the cgroup; mixed sends TERM to main, KILL to remaining.
# Reload unit after editing
sudo systemctl daemon-reload

# Graceful stop — sends SIGTERM per unit file
sudo systemctl stop laravel-worker@1

# Check exit status and last signal
systemctl status laravel-worker@1
journalctl -u laravel-worker@1 -n 50 --no-pager

On sister sites I deploy with Deployer 7 and GitLab CI, the post-deploy step reloads PHP-FPM — not restart — to avoid dropping in-flight requests:

# Sends SIGUSR2 to php-fpm master for graceful worker reload
sudo systemctl reload php8.3-fpm

That pattern matters for opcache invalidation after symlink swap. A full restart sends SIGTERM to every worker mid-request.

How do you troubleshoot stuck processes on a Laravel or PHP-FPM host?

Production debugging follows a repeatable sequence. I use it on legal-tech portals, eCommerce carts, and booking systems where downtime has direct revenue impact.

SIGTERM vs SIGKILLSIGTERM (15)Catches handlerFlushes logsCloses DB handlesFinishes requestPreferred defaultSIGKILL (9)No handler runsInstant deathRisk corrupt stateOrphan temp filesLast resort onlyUse SIGTERM first; wait TimeoutStopSec; then SIGKILL
SIGTERM versus SIGKILL in Linux process management — graceful shutdown beats forced kills on production web stacks

Step-by-step production triage

  1. Identify the offender. Run top or ps aux --sort=-%cpu | head -20. Note PID, user, and runtime.
  2. Inspect the process. Use ls -l /proc/PID/fd for open files. Read /proc/PID/status for state and parent.
  3. Check logs. Correlate with PHP-FPM slow log, Laravel log, and MySQL slow query log.
  4. Send SIGTERM. kill -TERM PID or systemctl stop servicename. Wait 30 seconds.
  5. Verify exit. Confirm PID gone via ps -p PID. If still running in D state, investigate disk — not signals.
  6. Escalate to SIGKILL only if needed. Document why; check data integrity after.

PHP-FPM pool exhaustion

When all pm.max_children workers are busy, new requests queue or time out. Symptoms look like application failure but are pure process limits.

# Count active php-fpm workers
ps -eo pid,user,cmd | grep 'php-fpm: pool' | wc -l

# Check pool config
grep -E 'pm\.(max_children|start_servers|min_spare|max_spare)' /etc/php/8.3/fpm/pool.d/www.conf

# Graceful reload after config change
sudo systemctl reload php8.3-fpm

On a booking platform like Adventure Third Pole Trek, peak season traffic can saturate workers if cron, queues, and web traffic share one pool. Splitting pools — www for HTTP, internal for CLI — is a pattern I've used on busy Laravel + Livewire apps.

Zombie and orphan cleanup

# List zombie processes
ps aux | awk '$8 ~ /Z/ { print }'

# Find parent of zombie PID 9912
ps -o ppid= -p 9912

# Restart parent service — zombies clear when parent calls wait()
sudo systemctl restart php8.3-fpm

Zombies consume almost no CPU or RAM. They still occupy a row in the process table. Thousands of zombies can block new forks — rare but catastrophic. Fix the parent, not the zombie child.

Stuck Process TriageHigh CPU or RAM?Check STAT colR, S, or D?State D?Check disk I/OState R/S?kill -TERMFix NFS / diskno signal helpsWait 30 secprocess exits?kill -9document reasonAlways correlate with journalctl and app logsPHP-FPM, MySQL 9.7, Redis 8.10 — check each layer
Decision tree for Linux process management and signals on production Ubuntu web servers

What are common process management mistakes on shared hosting and VPS servers?

Most failures I see are operational, not kernel bugs. Small teams running law-firm portals or WooCommerce shops on single VPS instances repeat the same patterns.

Running everything as root

Processes started by root and left daemonised become hard to trace. Cron jobs that call php artisan as root create log files owned by root. PHP-FPM then cannot write to them. Use dedicated users per app and align with Linux file permission basics.

Ignoring ulimit and pid_max

# Current limits for shell session
ulimit -a

# System-wide max PIDs
cat /proc/sys/kernel/pid_max

# Open files limit — critical for MySQL and nginx
ulimit -n

High-traffic eCommerce during festival season in Nepal can exhaust file descriptors when connection pooling is misconfigured. Raise limits in /etc/security/limits.conf and systemd unit files — not only in interactive shells.

Cron without flock or systemd timers

Overlapping cron invocations spawn duplicate queue workers or double billing scripts. Prefer systemd timers or wrap commands:

# Prevent overlapping schedule runs
* * * * * flock -n /tmp/artisan-schedule.lock /usr/bin/php /var/www/app/artisan schedule:run

See cron job scheduling patterns for timer-based alternatives that integrate cleanly with signal-aware unit files.

Skipping post-deploy PHP-FPM reload

After Deployer symlink swap, stale opcache serves old bytecode. New code paths never run. Workers keep old bootstrap. A reload costs seconds; a full restart drops active sessions. This belongs in every deploy checklist alongside testing and optimization gates.

For teams without in-house ops capacity, structured Linux system administration covers process tuning, systemd units, and incident response. Long-term maintainers often pair that with support and maintenance retainers so queue workers and PHP pools get reviewed before peak traffic.

Key Takeaways

  • Every running service is a process tree — map PIDs before you kill anything.
  • Send SIGTERM first; reserve SIGKILL for wedged processes after a documented wait.
  • Use systemctl with proper KillSignal and TimeoutStopSec for Laravel workers and PHP-FPM.
  • State D means disk or driver trouble — signals will not fix it.
  • Reload PHP-FPM after deploys; restart only when pool config changes require it.
  • Monitor zombie counts and file descriptor limits before they block new forks.

People Also Ask

What is the difference between a process and a thread on Linux?

A process is an independent program instance with its own PID and virtual memory space. Threads share the same PID's memory but have separate stack and register state. PHP-FPM uses multi-process workers, not threads — each worker is a separate process you can signal individually.

Why does kill -9 sometimes fail?

SIGKILL is handled directly by the kernel and cannot be blocked by user-space code. If kill -9 appears to fail, the PID may already have exited, you lack permission (try sudo), or the process is stuck in uninterruptible D state waiting on kernel I/O. In the D case, only fixing the underlying I/O problem resolves it.

How do I find which process is using a specific port?

Run sudo ss -tlnp 'sport = :443' or sudo lsof -i :443. Both show the PID and program name bound to the port. This is faster than guessing which nginx or Apache instance holds the socket when multiple sites share a server.

Does systemd replace kill and ps commands?

No. Systemd manages service lifecycle and sends signals on your behalf, but ps, top, and kill remain essential for debugging processes systemd did not start — manual scripts, orphaned cron children, and container sidecars. Treat systemd as the preferred controller for declared services and CLI tools for everything else.

Put Linux process management and signals into your runbook

Stable production servers depend on predictable process behaviour. Know your signal order: TERM, wait, then KILL. Configure systemd units for queue workers. Reload PHP-FPM after every deploy. Correlate ps output with application logs before you terminate PIDs. These habits prevent the 2 AM fire drills I still see on otherwise well-built Laravel and WordPress stacks.

Need help tuning PHP-FPM pools, writing systemd units, or cleaning up a overloaded VPS? Review the portfolio for production deployments, try the JSON formatter when debugging API worker payloads, or contact us for hands-on Linux process management and signals support on your server.

Frequently Asked Questions

Linux process management is how the kernel tracks every running program as a process with a unique PID, parent PID, state, and resource counters for CPU and memory. Signals are asynchronous notifications the kernel or other processes send to control that lifecycle — stop, reload, pause, or terminate. On production Ubuntu web servers running Laravel, WordPress, or PHP-FPM, understanding this model is what separates a clean graceful shutdown from corrupted uploads, orphaned workers, or a full process table that blocks new forks.

SIGTERM (15) asks a process to exit cleanly and can be caught. SIGKILL (9) forces immediate termination and cannot be ignored. Always try SIGTERM first.

Budget VPS hosts common in Nepal run Rs 1,500–3,000/month (~USD 11–22), where CPU contention between MySQL and PHP-FPM is routine.

SIGKILL is handled directly by the kernel and cannot be blocked by user-space code. If kill -9 appears to fail, the PID may already have exited, you lack permission (try sudo), or the process is stuck in uninterruptible D state waiting on kernel I/O.

Start with ps aux for a full human-readable list, or ps auxf for a parent-child tree view. Use pgrep -a php-fpm or pgrep -u www-data -a artisan to return PIDs matching a pattern without fragile grep. For live monitoring, top sorts by CPU and memory — press M for memory, P for CPU, k to send a signal. htop adds a clearer tree view and per-process thread counts. The ps columns that matter daily are %CPU, %MEM, RSS, and STAT. A worker stuck in D state usually means storage or NFS trouble, not application logic.

Processes move through several kernel states during their lifetime. R means running or ready in the CPU queue. S is interruptible sleep, waiting on I/O such as disk or network. D is uninterruptible sleep blocked on hardware — you cannot kill it with signals until the driver returns. T means stopped, often by SIGSTOP or a debugger. Z is a zombie: the process exited but the parent has not yet reaped it, so it still occupies a row in the process table. High RSS on a single PHP-FPM worker in S state often signals a memory leak in a long-running queue job rather than a kernel problem.

SIGHUP (1) reloads config without a full restart — useful for Apache graceful reloads and nginx after certificate renewal. SIGTERM (15) is the default for kill and systemctl stop; it is the preferred graceful stop. SIGKILL (9) is the last resort for wedged workers with no cleanup opportunity. SIGUSR1 (10) reopens nginx logs on some setups. SIGSTOP (19) and SIGCONT (18) pause and resume processes. For PHP-FPM after a Deployer symlink swap, systemctl reload sends SIGUSR2 to the master for a graceful worker reload instead of dropping in-flight HTTP requests mid-request.

systemd manages long-running services on modern Ubuntu through unit files with directives that map directly to signal behaviour. KillSignal sets which signal systemctl stop sends — default SIGTERM. TimeoutStopSec defines how long systemd waits before escalation. SendSIGKILL controls whether SIGKILL follows the timeout. KillMode=mixed sends TERM to the main process and KILL to stragglers in the cgroup. A typical Laravel queue worker unit sets Restart=always, KillSignal=SIGTERM, TimeoutStopSec=30, and SendSIGKILL=yes. After editing any unit file, run systemctl daemon-reload, then use systemctl status and journalctl -u servicename to verify exit status and last signal received.

A zombie process holds Z state in ps output. It has already exited but its parent has not called wait() to reap it. Zombies consume almost no CPU or RAM, yet they still occupy a slot in the process table. Thousands of them can block new forks — rare but catastrophic on shared VPS boxes. List them with ps aux and awk on STAT column Z, then find the parent with ps -o ppid= -p PID. Fix the parent, not the zombie child. Restarting the parent service — such as systemctl restart php8.3-fpm — clears zombies when the parent properly reaps children. Orphaned workers from crashed deploy scripts show this pattern weekly on servers I maintain.

Reload after deploys when you need opcache invalidation following a Deployer 7 symlink swap — stale opcache otherwise serves old bytecode and workers keep the old bootstrap. Run sudo systemctl reload php8.3-fpm, which sends SIGUSR2 to the php-fpm master for a graceful worker reload. That costs seconds and avoids dropping active sessions. Full restart sends SIGTERM to every worker mid-request and belongs only when pool configuration changes require it — for example after editing pm.max_children in www.conf. This reload step belongs in every deploy checklist alongside testing gates, not as an optional ops nicety.

Follow a repeatable sequence. Identify the offender with top or ps aux sorted by CPU. Note PID, user, and runtime. Inspect open files via ls -l /proc/PID/fd and read /proc/PID/status for state and parent. Correlate with PHP-FPM slow log, Laravel log, and MySQL slow query log. Send SIGTERM with kill -TERM PID or systemctl stop servicename, then wait 30 seconds and confirm the PID is gone via ps -p PID. If the process remains in D state, investigate disk I/O — signals will not fix uninterruptible sleep. Escalate to SIGKILL only after documented wait, then check data integrity. I use this flow on legal-tech portals and booking systems where downtime has direct revenue impact.

When all pm.max_children workers for a pool are busy, new HTTP requests queue or time out. Symptoms look like application failure but are pure process limits, not Laravel bugs. Count active workers with ps -eo pid,user,cmd filtered on php-fpm pool lines. Check pool config with grep on pm.max_children, start_servers, min_spare, and max_spare in www.conf. Graceful reload after config change via systemctl reload php8.3-fpm. On busy Laravel apps where cron, queues, and web traffic share one pool, peak season traffic can saturate workers entirely. Splitting pools — www for HTTP and internal for CLI — is a pattern I have used when booking platforms hit concurrent request ceilings.

nice and renice adjust CPU scheduling priority on a scale from -20 to 19, where a higher nice value means lower priority. Start a backup at lower priority with nice -n 10 before mysqldump so nightly dumps do not starve checkout flows. Lower an already-running PID with sudo renice +5 -p PID. On budget VPS hosts in Nepal at Rs 1,500–3,000/month (~USD 11–22), CPU contention between MySQL and PHP-FPM during festival-season eCommerce traffic is routine. Bumping backup and maintenance jobs to nice 10 or 15 keeps customer-facing PHP-FPM workers responsive without needing a larger instance.

Overlapping cron invocations spawn duplicate queue workers or double billing scripts — a common mistake on single-VPS setups running law-firm portals or WooCommerce shops. Wrap schedule:run with flock -n and a lock file so a second invocation exits immediately if the first is still running. Prefer systemd timers over bare cron where possible, since timer-based units integrate cleanly with signal-aware service definitions and Restart policies. Align cron and queue worker users with the application user — running php artisan as root creates log files PHP-FPM cannot write to later. These operational hygiene steps belong alongside ulimit checks and PHP-FPM reloads in any production checklist.

A process is an independent program instance with its own PID and virtual memory space. Threads share the same PID's memory but have separate stack and register state. PHP-FPM uses multi-process workers, not threads — each worker is a separate process you can signal individually with kill or inspect separately in ps and htop. That matters when one worker shows high RSS from a leaking queue job while siblings remain healthy. You terminate or renice a single PID without affecting the entire php-fpm master pool, which is why mapping the process tree with ps auxf before sending any signal is the first step in every production triage session I run on Ubuntu 22/24 hosts.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: