Code Safari

Chapter 109·Intermediate·9 min read

Processes, Signals & Ports

A plain-English guide to running programs on Linux — what a process is and its PID, listing them with ps and top, foreground vs background jobs, stopping a program with signals and kill, and how network ports work so a server can answer requests. Plus fixing the classic 'port already in use' error.

July 17, 2026

So far the system has been static — files sitting on disk, waiting. This chapter brings it to life. When you run a program, the machine springs into activity: processes claiming memory, competing for the CPU, and opening network ports to talk to the world. Being able to see that activity, and to intervene when something misbehaves, is the core of running software on a real server. We'll end by demystifying the single most common error a developer meets: "port already in use."

From program to process

A program on disk — python, nginx, your compiled app — is just a file, a recipe sitting there doing nothing. The moment you run it, the operating system's kernel reads that recipe and creates a process: a live, running instance of the program, with its own slice of memory, its own owner (a user, per the last chapter), and a unique identification number called a PID (process ID).

The distinction matters. One program can become many processes — open three copies of an editor and there are three processes, each with its own PID, all born from the same file. The PID is how you refer to a specific running instance when you want to inspect or stop it. Everything in this chapter is really about finding the right PID and doing something to it.

Seeing what's running: ps and top

Two commands show you the processes on a machine. ps (process status) takes a one-time snapshot. The incantation you'll memorise is ps aux — list every process, from every user, with detail:

$ ps aux
USER     PID   %CPU  %MEM  COMMAND
raghav   4821   0.0   0.1  /usr/bin/zsh
raghav   9134  12.3   2.4  python app.py
root      612   0.1   0.5  /usr/sbin/nginx

Each row is a process: who owns it, its PID, how much CPU and memory it's using, and the command that started it. To find one specific program, pipe it into grep — exactly the composition from chapter four:

$ ps aux | grep nginx

ps is a still photo. For a live view, use top — a full-screen dashboard that refreshes every couple of seconds, showing which processes are working hardest, sorted by CPU use. It's the first thing you run when a server feels slow: top tells you instantly what's eating the machine. Press q to quit. Many people install htop, a friendlier, colourful version with mouse support — same idea, nicer view.

Foreground and background

When you run a command normally, it runs in the foreground: it takes over your shell, and your prompt doesn't come back until the command finishes. That's fine for something quick like ls, but not for a program meant to run for hours — a web server, a long data job.

Two tools change this. Add & to the end of a command to launch it in the background — it starts, and your prompt returns immediately so you can keep working while it runs:

$ python long_job.py &
[1] 9134

The [1] 9134 tells you it's background job 1, with PID 9134. And if you started something in the foreground and then realised it'll take a while, press Ctrl-Z to pause it, then type bg to resume it in the background. (Ctrl-C, by contrast, doesn't background a program — it stops it, which is really the next topic: sending a signal.)

Command runs, holds your shell
Ctrl-Z pauses it
bg resumes it in background
Prompt is yours again
Moving a running command into the background

Stopping a process: signals

You don't just "close" a Linux process — you send it a signal, a small standardised message that asks or tells it to do something. Two signals matter most, and the difference between them is worth genuinely understanding.

  • SIGTERM (terminate) is a polite request to shut down. The program receives it and can finish what it's doing — save its work, close files cleanly, disconnect from the database — and then exit gracefully. This is the default, and the right first choice.
  • SIGKILL (kill) is a non-negotiable order. The kernel stops the process instantly; the program gets no chance to react, save, or clean up. It always works, but it can leave half-written files or a messy state behind.

You send them with kill, giving it the PID:

$ kill 9134           # send SIGTERM — ask nicely
$ kill -9 9134        # send SIGKILL — force it (the -9 is SIGKILL)

The Ctrl-C you press to stop a foreground program? That sends SIGTERM's cousin (SIGINT, "interrupt") to whatever's running. Same idea — a signal asking the program to stop.

Ports: numbered doors for the network

A running server is only useful if the outside world can reach it — and that's what a port is for. When a program wants to receive network traffic, it claims a port: a numbered channel on the machine, from 1 to 65535. Think of the server's network address as a building and ports as its numbered doors — a request arrives addressed to a specific door, and whichever program is listening on that port receives it.

Some port numbers are conventions everyone agrees on:

  • 80 — plain web traffic (HTTP)
  • 443 — secure web traffic (HTTPS)
  • 22 — SSH, remote login (the next chapter)
  • 5432 — PostgreSQL; 3306 — MySQL
  • 3000 / 8080 / 5173 — common defaults for local development servers

So when your dev server prints "running on http://localhost:3000," it means: this process has claimed port 3000 on your own machine, and it's waiting there for browser requests. Here, a crucial rule: only one program can listen on a given port at a time. Two servers both wanting port 3000 can't coexist — and that collision is the source of the error you're about to meet.

The classic error: "port already in use"

Every developer hits this. You start your app and it refuses to launch:

Error: listen EADDRINUSE: address already in use :::3000

Decoded, it's simple: some process is already listening on port 3000, so I can't have it. Usually it's a previous run of your own app that didn't shut down properly and is still holding the port. The fix is a two-step you'll use often — find who's on the port, then stop them.

To find the culprit, lsof (list open files — and in Unix, a network port counts as an open file) will name the process holding a port:

$ lsof -i :3000
COMMAND   PID     USER   NAME
node     9134   raghav   TCP *:3000 (LISTEN)

There it is: PID 9134 is squatting on port 3000. Now stop it with the signal you just learned, and the port frees up:

$ kill 9134

Restart your app and it claims the freshly vacated port. (ss -tlnp or the older netstat -tlnp list all listening ports at once, which is how you audit "what's actually accepting connections on this server?")

App won’t start: EADDRINUSE
lsof -i :3000 — who’s there?
kill <PID> — stop it
Restart — port is free
Freeing a port that's already in use

The daily rhythm of running software

Notice how the pieces connect: a process has a PID, you find that PID with ps/top/lsof, and you act on it with a signal via kill. Ports are just the network side of the same story — processes claiming numbered channels, and the conflicts that arise when two want the same one. This loop — start it, check it's alive, stop it if it misbehaves, free the port — is the literal day-to-day of keeping applications running on a server.

There's one thing still missing, though. Everything here assumed you're on the machine. But the servers your software runs on live in data centres you'll never physically visit. The next chapter is how you get there: SSH, the encrypted tunnel that drops a shell on a computer anywhere in the world, as if you were sitting in front of it.

Processes, Signals & Ports | Code Safari