Home / Learn Linux / Lesson 17
Signals and jobs: kill, bg, fg and nohup
Stop, pause and resume programs, and keep them running after you log out.
- Lesson
- 17 of 30
- Time
- About 35 minutes
On this page
- Signals: messages to a process
- kill: send a signal by PID
- Killing by name: pkill, killall and pidof
- Jobs: foreground and background
- Keep it running after you log out
- Priority: nice and renice
- Common mistakes
- Practice
- Cheat sheet
Signals: messages to a process
You talk to running processes by sending them signals: small numbered messages like "please stop", "stop now" or "reload your settings". A program can choose how to react to most of them. kill -l lists them all, but you'll only use a handful:
| Signal | Number | Means | Sent by |
|---|---|---|---|
SIGTERM | 15 | Please finish up and exit | kill (the default) |
SIGKILL | 9 | Stop immediately. Can't be caught or ignored | kill -9 |
SIGINT | 2 | Interrupt | Ctrl+C |
SIGHUP | 1 | Your terminal hung up. Many services read it as "reload your config" | Closing a terminal, kill -HUP |
SIGTSTP | 20 | Pause | Ctrl+Z |
SIGCONT | 18 | Carry on | fg, bg |
The difference between TERM and KILL is the most important thing on this page. TERM lets a program save its work, close its files and remove its temporary data. KILL pulls the plug, so a database killed with -9 may have to repair itself on the next start. Always try TERM first.
kill: send a signal by PID
Despite the name, kill just sends a signal, and TERM is the default:
$ sleep 600 & [1] 189512 $ ps -o pid,cmd -p $! PID CMD 189512 sleep 600 $ kill $! $ ps -o pid,cmd -p $! PID CMD [1]+ Terminated sleep 600
Bash reported that the job was Terminated, and ps no longer finds it.
Here's why TERM matters. This little program catches TERM and cleans up before it exits, the way a well-written service does (lesson 29 explains trap):
$ bash -c 'trap "echo cleaning up; rm -f /tmp/work.lock; exit" TERM; touch /tmp/work.lock; while true; do sleep 1; done' & [1] 189567 $ sleep 1; ls /tmp/work.lock /tmp/work.lock $ kill $! $ sleep 1; ls /tmp/work.lock cleaning up ls: cannot access '/tmp/work.lock': No such file or directory [1]+ Done bash -c 'trap "echo cleaning up; rm -f /tmp/work.lock; exit" TERM; touch /tmp/work.lock; while true; do sleep 1; done'
It got the message and removed its lock file. Now the same thing with KILL:
$ bash -c 'trap "echo cleaning up; rm -f /tmp/work.lock; exit" TERM; touch /tmp/work.lock; while true; do sleep 1; done' & [1] 189628 $ sleep 1; ls /tmp/work.lock /tmp/work.lock $ kill -9 $! $ sleep 1; ls /tmp/work.lock [1]+ Killed bash -c 'trap "echo cleaning up; rm -f /tmp/work.lock; exit" TERM; touch /tmp/work.lock; while true; do sleep 1; done' /tmp/work.lock
No cleanup, and the lock file was left behind. Next time that program starts, it might refuse to run because it thinks another copy is still going. That's the real cost of kill -9.
Use kill -9 only when a process ignores TERM for a few seconds. And remember you can only signal your own processes. Anyone else's needs sudo.
Try it
- Start
sleep 900 &, find its PID withpgrep, and stop it with TERM. - What does
kill -l 15print?
Answer
$ sleep 900 & [1] 189689 $ pgrep -a sleep 189689 sleep 900 $ kill $(pgrep sleep) $ kill -l 15 TERM [1]+ Terminated sleep 900
Killing by name: pkill, killall and pidof
Looking up PIDs gets tedious. These work with names instead:
| Command | Does |
|---|---|
pidof name | Print the PIDs of a program |
pkill name | Signal every process whose name matches (a pattern, like pgrep) |
pkill -u user | Signal everything one user is running |
killall name | Signal every process with exactly this name |
$ sleep 700 & [1] 189744 $ sleep 800 & [2] 189745 $ pidof sleep 189745 189744 $ killall sleep [1]- Terminated sleep 700 $ pidof sleep; echo $? [2]+ Terminated sleep 800 1
Careful with pkill: its pattern matches anywhere in the name, so pkill ssh would also hit sshd, the SSH server, and you'd lock yourself out of a remote server. Check what matches with pgrep -a pattern first, the same habit as ls before rm.
Jobs: foreground and background
A command normally runs in the foreground: your terminal waits until it finishes. Add & to run it in the background, and you get your prompt back straight away. Bash calls each of these a job, and numbers them:
$ sleep 500 & [1] 189801 $ sleep 600 & [2] 189802 $ jobs [1]- Running sleep 500 & [2]+ Running sleep 600 & $ kill %1 %2
%1 and %2 refer to jobs by number, and work anywhere a PID would, like the kill %1 %2 that cleaned them up.
Ctrl+Z, bg and fg
Ctrl+Z pauses the foreground job and gives you the prompt back. Then bg lets it carry on in the background, and fg brings it back to the foreground:
$ sleep 300 ^Z [1]+ Stopped sleep 300 $ jobs [1]+ Stopped sleep 300 $ bg %1 [1]+ sleep 300 & $ jobs [1]+ Running sleep 300 & $ kill %1
That's the rescue for a common mistake: you started something long without &, and you need your terminal back. Press Ctrl+Z, then bg. You saw the same T state in ps in lesson 16; it means stopped.
| Command | Does |
|---|---|
cmd & | Start in the background |
jobs, jobs -l | List jobs (with PIDs) |
Ctrl+Z | Pause the foreground job |
bg %n | Continue job n in the background |
fg %n | Bring job n to the foreground |
kill %n | Signal job n |
Try it
- Start
sleep 400in the foreground, pause it, then let it continue in the background, and check withjobs. - Bring it back to the foreground and stop it with
Ctrl+C.
Answer
$ sleep 400 ^Z [1]+ Stopped sleep 400 $ bg [1]+ sleep 400 & $ jobs [1]+ Running sleep 400 & $ fg sleep 400 ^C $ jobs
jobs prints nothing at the end, because there are no jobs left.
Keep it running after you log out
Background jobs belong to your shell. When you close the terminal or your SSH connection drops, the shell sends them SIGHUP and they usually die. To keep something running:
nohup command &starts it immune to SIGHUP, and saves its output innohup.out.disown %1tells Bash to forget a job that's already running, so it isn't sent SIGHUP.
$ mkdir ~/nohup-lab $ cd ~/nohup-lab $ nohup bash -c 'for i in 1 2 3; do echo "step $i"; sleep 1; done' & [1] 189963 $ sleep 4 nohup: ignoring input and appending output to 'nohup.out' [1]+ Done nohup bash -c 'for i in 1 2 3; do echo "step $i"; sleep 1; done' $ cat nohup.out step 1 step 2 step 3
For anything you'll come back to, use tmux instead. It's a terminal that keeps running on the server, so you can detach, log out, log back in later and reattach to exactly where you left off:
$ tmux new -s workInside tmux, run your long job, then press Ctrl+B followed by D to detach. Log out if you like. Later:
$ tmux ls $ tmux attach -t work
And for something that must always run, like a web app, neither of these is right. That's what services are for (lesson 26): systemd starts them at boot and restarts them when they crash.
Priority: nice and renice
When several processes want the CPU, the nice value decides who gets more. It runs from -20 (greedy) to 19 (very polite), and 0 is normal. Start a heavy job politely with nice, so it doesn't slow down everything else:
$ nice -n 5 sleep 200 & [1] 190021 $ ps -o pid,ni,cmd -p $! PID NI CMD 190021 5 sleep 200 $ renice -n 15 -p $! 190021 (process ID) old priority 5, new priority 15 $ renice -n 5 -p $! renice: failed to set priority for 190021 (process ID): Permission denied $ ps -o pid,ni,cmd -p $! PID NI CMD 190021 15 sleep 200 $ kill %1
Anyone can make their own processes nicer. But going back the other way, even to where it was, counts as asking for more CPU, and that needs sudo. So does any negative number.
Common mistakes
- **Reaching for
kill -9first.** It skips cleanup. TERM, wait a few seconds, then KILL. - **
pkillmatching too much.** Check withpgrep -afirst.pkill sshcan kill your own connection. - Long jobs started over SSH without tmux or nohup. If the connection drops, the job dies. Start important work inside tmux.
- Forgetting a job is paused. A
Ctrl+Z'd job does nothing until youbgorfgit.jobsshowsStopped. - **
killon someone else's process withoutsudo.** You'll getOperation not permitted.
Practice
- Start three
sleepcommands in the background with different times, list them with their PIDs, then kill the second one by job number.
Answer
$ sleep 301 & [1] 190078 $ sleep 302 & [2] 190079 $ sleep 303 & [3] 190080 $ jobs -l [1] 190078 Running sleep 301 & [2]- 190079 Running sleep 302 & [3]+ 190080 Running sleep 303 & $ kill %2 $ jobs [1] Running sleep 301 & [2]- Terminated sleep 302 [3]+ Running sleep 303 &
- Start
yes > /dev/nullin the foreground, pause it, check its state withps, then kill it.
Answer
$ yes > /dev/null ^Z [1]+ Stopped yes > /dev/null $ ps -o pid,stat,cmd -C yes PID STAT CMD 190133 T yes $ kill %1 [1]+ Stopped yes > /dev/null $ jobs [1]+ Terminated yes > /dev/null
T is the stopped state. A stopped process can't act on TERM until it runs again, so when you kill a stopped job with %1, Bash sends CONT right after the TERM, and it exits. The first message still says Stopped because Bash reported the job's state before the signal landed; jobs shows the result. With a plain PID (kill 1234) Bash doesn't do that, and the TERM waits until something continues the process. kill -9 works either way, because KILL is handled by the kernel, not the process.
- Start
sleep 1000as a background job, then usepkillto stop everysleepprocess you own, checking first what will match.
Answer
$ sleep 1000 & [1] 190187 $ pgrep -a -u ubuntu sleep 190187 sleep 1000 $ pkill -u ubuntu sleep $ pgrep -a -u ubuntu sleep; echo $? [1]+ Terminated sleep 1000 1
- Run a 3-second loop with
nohupin the background in~/nohup-lab, appending tonohup.out, and read the result.
Answer
$ cd ~/nohup-lab $ nohup bash -c 'echo "run 2 started"; sleep 3; echo "run 2 done"' & [1] 190243 $ sleep 4 nohup: ignoring input and appending output to 'nohup.out' sleep 4 [1]+ Done nohup bash -c 'echo "run 2 started"; sleep 3; echo "run 2 done"' $ tail -n 2 nohup.out run 2 started run 2 done
- What's the difference between
kill 1234,kill -15 1234andkill -TERM 1234?
Answer
None. All three send SIGTERM to PID 1234.
Cheat sheet
| Command | What it does |
|---|---|
kill PID, kill -TERM PID | Ask a process to exit (signal 15) |
kill -9 PID, kill -KILL PID | Force it (no cleanup) |
kill -HUP PID | Hang up, often "reload config" |
kill -l | List signals |
pidof name, pgrep -a pattern | Find PIDs |
pkill pattern, pkill -u user, killall name | Signal by name or user |
cmd &, jobs, %n | Background, list, refer to a job |
Ctrl+Z, bg, fg | Pause, continue in background, bring to front |
Ctrl+C | Interrupt the foreground job |
nohup cmd & | Survive logout (output in nohup.out) |
disown %n | Detach a running job from the shell |
tmux new -s name, Ctrl+B D, tmux attach -t name | A terminal that survives disconnects |
nice -n 10 cmd, renice -n 5 -p PID | Run politely, change politeness |
← Processes: ps, top and htopNetwork basics: ip, static IPs and troubleshooting →