diff is the reference command to detect changes between two files. It is the engine behind git diff, patch, and most code review tools. In system administration, you use it to compare configurations before/after, check migrations and propagate changes between servers.
The fundamentals guide covers the basic syntax. This guide focuses on real uses: unified format, recursive comparison and the diff → patch workflow.
What you will learn
- Read diff's three output formats (normal,
-u,-y) - Compare two whole directories with
diff -r - Generate a patch file and apply it with
patch - Use the return codes in a checking script
In which context?
You are putting a server back into production after an intervention. You want to make sure the configuration is exactly the one in place before, not "roughly the same", exactly the same. Or you must propagate a fix to a configuration file from one environment to another, without overwriting the local adaptations. Or again you manage dozens of servers and you suspect one of them has a configuration that has drifted. diff is the trusted tool in these situations: it says precisely what changed, line by line, without ambiguity. It is also the engine of git diff: both produce the same unified format, with the same @@ headers and the same line prefixes, so knowing how to read one means knowing how to read the other.
diff's output formats
diff can present the same difference in three ways, and the choice is not cosmetic: only the unified format carries enough information to be replayed by patch. The examples below compare two versions of an nginx configuration, v1.conf and v2.conf. The file passed as the first argument is always the old one, the second the new one; swapping them swaps the direction of additions and deletions.
Normal format (default)
Without options, diff only shows the lines that change, with no context line around them. It is the most compact format, but it is unusable by patch as soon as the target file has moved.
diff v1.conf v2.conf2,3c2,3< server_name example.com;< listen 80;---> server_name example.com www.example.com;> listen 443 ssl;5,6c5,7< worker_processes 2;< access_log /var/log/nginx/access.log;---> worker_processes 4;> access_log /var/log/nginx/access.log combined;> error_log /var/log/nginx/error.log warn;Reading: 2,3c2,3 means "lines 2-3 of the left file change to become lines 2-3 of the right file". Lines prefixed with < come from the left file, those with > from the right file.
The three possible actions are a (add), d (delete) and c (change).
Unified format, the most useful
The -u option adds two header lines with the file names and their modification dates (to the nanosecond, with the time zone), then surrounds each change with three lines of context that did not change. It is this context that lets patch find where to apply the change even if the line numbers have moved.
diff -u v1.conf v2.conf--- v1.conf 2026-04-09 15:27:28.028159111 +0200+++ v2.conf 2026-04-09 15:27:31.614052218 +0200@@ -1,6 +1,7 @@ # Configuration nginx-server_name example.com;-listen 80;+server_name example.com www.example.com;+listen 443 ssl; root /var/www/html;-worker_processes 2;-access_log /var/log/nginx/access.log;+worker_processes 4;+access_log /var/log/nginx/access.log combined;+error_log /var/log/nginx/error.log warn;It is the format of git diff. Each block starts with @@, which gives the line numbers. Lines with no prefix are the context (unchanged), - what disappears, + what arrives.
The context (3 lines by default) locates the change even if the file was modified in the meantime. You can adjust it with -U:
diff -u --unified=5 v1.conf v2.conf # 5 lines of contextdiff -u --unified=0 v1.conf v2.conf # no contextSide-by-side format
The -y option shows the two files in two columns, identical lines included: the output is therefore as long as the file itself. It is for human reading on short files, never to produce a patch.
diff -y v1.conf v2.conf# Configuration nginx # Configuration nginxserver_name example.com; | server_name example.com www.example.com;listen 80; | listen 443 ssl;root /var/www/html; root /var/www/html;worker_processes 2; | worker_processes 4;access_log /var/log/nginx/access.log; | access_log /var/log/nginx/access.log combined; > error_log /var/log/nginx/error.log warn;The | marks a changed line, > a line added on the right, < a line removed on the right.
Compare two directories
Comparing two trees answers two different questions: which files exist on one side only, and which ones exist on both sides but differ. diff -r handles both in a single pass. Beware, it compares the content and nothing else: neither permissions, nor owner, nor dates are examined.
diff -r, recursive
The -r option descends into every subdirectory and compares the files with the same relative path.
diff -r dir_v1 dir_v2Only in dir_v2: extra.confdiff -r dir_v1/nginx.conf dir_v2/nginx.conf2,3c2,3< server_name example.com;...Only in dir_v1: script_v1.shOnly in dir_v2: script_v2.shdiff -r also reports the files present in only one of the two directories (Only in). It is useful to detect a configuration drift between two servers.
To get just the list of differences without the details:
diff -rq dir_v1 dir_v2Only in dir_v2: extra.confFiles dir_v1/nginx.conf and dir_v2/nginx.conf differOnly in dir_v1: script_v1.shOnly in dir_v2: script_v2.shIgnore some files
--exclude takes a file name pattern, not a path, and can be repeated as many times as needed.
diff -r --exclude='*.log' --exclude='*.pid' dir_v1 dir_v2Useful to compare application directories while ignoring the runtime files, which change at every start.
Return codes
diff returns an exit code usable in scripts:
| Code | Meaning |
|---|---|
0 | Identical files |
1 | Different files |
2 | Error (file not found, etc.) |
if diff -q reference_file.conf current_file.conf > /dev/null 2>&1; then echo "Configuration consistent"else echo "ALERT: drift detected" diff -u reference_file.conf current_file.conffiThe diff → patch workflow
The diff -u → patch workflow is used to propagate a change from one file to another, even when the two have diverged, or to distribute a fix.
-
Generate the patch
Terminal window diff -u v1.conf v2.conf > changes.patchThe
changes.patchfile contains all the changes in unified format. -
Check the patch before applying it
Terminal window patch --dry-run v1.conf < changes.patchThe
--dry-runoption simulates without modifying the file. Useful to validate before touching production. -
Apply the patch
Terminal window patch -b v1.conf < changes.patchThe
-b(--backup) option keeps the original version inv1.conf.orig. Without it,patchcreates no backup: the file is modified in place, with no safety net. It is a widespread mistake to believe the backup is automatic. -
Check the result
Terminal window diff v1.conf v2.conf # must be empty
Undo a patch
Rolling back requires the original patch file: without it, no way back is possible, unless you have the backup produced by -b.
patch -R v1.conf < changes.patchThe -R option applies the patch in reverse (rollback).
Multi-file patches
For a whole project:
# Generate (-N includes the files present on one side only)diff -ruN dir_v1/ dir_v2/ > project.patch
# Apply from the project rootpatch -p1 < project.patchUseful options in administration
On configuration files, the noise rarely comes from the substance: reworked indentation, an added empty line, carriage returns from a Windows editor. These options filter out this noise to keep only the real differences. Do not use them to generate a patch: a diff produced with -w or -B no longer reflects the file as it is, and patch will fail.
| Option | Effect |
|---|---|
-i | Ignores case |
-w | Ignores all whitespace |
-b | Ignores changes in the amount of whitespace, end of line included |
-Z | Ignores trailing whitespace only |
-B | Ignores empty lines |
--strip-trailing-cr | Ignores \r (Windows files) |
# Compare two configs ignoring comments and empty linesgrep -v '^#' v1.conf | grep -v '^$' > v1_clean.confgrep -v '^#' v2.conf | grep -v '^$' > v2_clean.confdiff v1_clean.conf v2_clean.confAdministration use cases
The three situations below cover most of the daily use of diff in operations: checking what an intervention really changed, confronting two servers meant to be identical, and measuring the gap between a server's real state and its versioned reference.
Check a configuration migration
The reference copy must be taken before any change: without it, there is nothing left to compare once the intervention is over.
# Before migration: back upcp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
# After migration: see what changeddiff -u /etc/nginx/nginx.conf.bak /etc/nginx/nginx.confCompare the config between two servers
diff only works on local files: you must therefore bring both versions back first. A remote cat is fine for a readable configuration file, but loses the permissions and the owner.
ssh server1 'cat /etc/nginx/nginx.conf' > nginx_server1.confssh server2 'cat /etc/nginx/nginx.conf' > nginx_server2.confdiff -u nginx_server1.conf nginx_server2.confDetect the drift of a configuration directory
The -q option only lists the files that differ, which gives a short report, usable in a periodic check. Remember to exclude the .git directory of the reference copy, otherwise it always shows up as Only in.
# Versioned reference in a Git repositorydiff -rq /etc/nginx/ ~/git/infra/nginx/Key points
diff -uis the standard format for patches and reviews: read it like agit diff.diff -rcompares whole directories and reports missing files.- The
diff -u → patch → patch -Rworkflow propagates or undoes a change in a controlled way. - The
0/1/2return code is usable in checking scripts. - Without
-u,patchlacks context: always usediff -uto generate a patch, anddiff -ruNfor a whole project.
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Basic regular expressions on Linux
A new full quiz with random questions
Work again only on the questions you missed