vi is the editor you are most likely to find already installed: on Linux servers, on ESXi hosts, in rescue shells, usually at the exact moment a config file needs fixing. It is also the editor that traps people: they open it, press a few keys, and cannot get out.
Reading a list of vi commands does not fix that. Your fingers need to have done it. So this page is a practice session rather than a reference: you build one realistic config file, then work through twelve exercises on it, from opening and quitting safely to search and replace, moving blocks and undoing mistakes.
At the end, one diff tells you whether you did every exercise correctly. The same file and the same twelve tasks are in the nano practice walk-through, so you can learn both editors on identical ground. Every keystroke here was run against a real vim and the result compared byte for byte.
Applies to: vim on any Linux distribution; most of the keys also work in the minimal vi on ESXi and BusyBox systems, and the vi and vim cheat sheet lists the exceptions
Quick answer
If you only need to survive the next five minutes, these are the only keys that matter. Everything else on this page builds on them.
| You want to | Press |
|---|---|
| Start typing text | i, then type |
| Stop typing, back to commands | Esc |
| Save and quit | :wq then Enter |
| Quit without saving anything | :q! then Enter |
| Undo the last change | u |
:q! and press Enter. You are out, and the file on disk is exactly as it was when you last saved it.
The one idea behind vi: modes
In most editors every key you press becomes text. vi splits that into modes. In Normal mode, where vi starts, every key is a command: dd deletes a line, u undoes, j moves down. In Insert mode keys become text, as you expect. In Command-line mode, entered with a colon, you type a whole command at the bottom of the screen, such as :w to save.
Every confusing moment in vi comes from being in a different mode than you think. If you type :wq and the letters appear in your file, you are in Insert mode. If you type a word and random things happen to the file, you are in Normal mode. The bottom line of the screen tells you which: it shows -- INSERT -- while you are inserting, and nothing while you are in Normal mode.
Before the first exercise
Four things set up the practice session. Each takes one command.
One. Check which vi you have. On most Linux distributions vi is vim; on a minimal image it can be a cut-down build. The first line tells you.
# which program actually runs when you type vi
readlink -f "$(command -v vi)"
vi --version | head -1
/usr/bin/vim.basic
VIM - Vi IMproved 9.1 (2024 Jan 02, compiled May 05 2026 09:14:36)
That output is from Ubuntu 24.04. Any vim version works for this page. If the first line does not start with VIM, for example if you get a BusyBox banner or an error instead, you are on a minimal vi; the core exercises still work, and the notes on undo explain the one difference that matters.
Two. Make a folder so nothing you do touches a real system file.
mkdir -p ~/editor-practice && cd ~/editor-practice
Three. Create the practice file. Paste the whole block into your terminal at once: it writes a small web server config with deliberate problems in it, a typo, a duplicate line and settings that need changing. It repeats the folder step, so this one block is all you need to start over later.
mkdir -p ~/editor-practice && cd ~/editor-practice
cat > practice.conf <<'EOF'
# web01 site configuration - practice file for vi and nano
# Owner: ops-team Reviewed: 2026-01-15
server_name web01.example.local;
listen 8080;
root /var/www/html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Backend pool
backend app01.example.local:8080;
backend app02.example.local:8080;
backend app03.example.local:8080;
backend app03.example.local:8080;
# Maintenance contacts
contact alice@example.local;
contact bob@exmaple.local;
contact carol@example.local;
# Deprecated - remove before go-live
debug_mode on;
legacy_tls TLSv1.0;
EOF
wc -l practice.conf
23 practice.conf
Four. Learn the emergency exit before you need it. Whatever happens in any exercise, Esc, then :q! and Enter, throws away your unsaved changes and leaves vi. Then run the step three block again to get a fresh practice file and start over.
:set number and Enter; turn them off again with :set nonumber. They are only displayed, never saved into the file.
Twelve exercises
Do them in order. Each one starts from where the previous one finished, and exercise 12 saves the result that the final comparison checks.
1. Open a file and get out without changing it
The task: look at a config file and leave, which is most of what people do in vi on a production box.
vi practice.conf
:q Enter quit; works because you changed nothing
Now do it the way it usually goes wrong. Open the file again, press dd (which deletes a line in Normal mode), then try to quit:
E37: No write since last change (add ! to override)
vi refuses because the file in memory differs from the file on disk. The ! is the override the message mentions: :q! means quit and discard. Type it now and check the file is untouched:
wc -l practice.conf # still 23 lines
2. Create a new file, type in it, save and quit
The task: write a short note from scratch. Naming a file that does not exist yet is how you create it.
vi notes.txt
i Insert mode; the bottom line shows -- INSERT --
(type three lines) Server web01 rebuilt on 2026-09-24
Owner: ops-team
Next review: 2026-12-01
Esc back to Normal mode
:wq Enter write the file and quit
"notes.txt" [New] 3L, 75B written
[New] means vi created the file, and 3L, 75B is three lines and 75 bytes. Confirm it from the shell:
cat notes.txt
:wq saves and quits even when nothing changed. ZZ (capital Z, twice, in Normal mode) and :x save only if something changed, which keeps the file’s modification time untouched when you only looked.
3. Move around the practice file
The task: jump straight to a line, to the end and back to the top, without holding an arrow key down.
vi practice.conf
18G go to line 18 (the one with bob's address)
G go to the last line
gg go to the first line
G with a number in front goes to that line; on its own it goes to the end. :18 followed by Enter does the same as 18G. You can also open the file at a line straight from the shell with vi +18 practice.conf, which is handy when an error message names a line number.
Inside a line, 0 and $ go to its start and end, w and b move one word forward and back, and h j k l move left, down, up and right. The arrow keys work too.
4. Fix a typo
The task: bob’s address on line 18 says exmaple. Fix it without retyping the line.
18G go to line 18
/exmaple Enter search for the typo; the cursor lands on its first letter
cw change word: removes "exmaple" and enters Insert mode
example Esc type the correct word, then back to Normal mode
cw is one of vi’s building blocks: an action (c, change) followed by a target (w, to the end of the word). The same pattern gives you dw to delete a word and c$ to change everything to the end of the line.
m and a are simply swapped. Instead of cw, after the search press l twice to move onto the m, then xp: x cuts the character, p puts it back after the next one. Two keystrokes for any pair of transposed letters.
5. Delete a line
The task: lines 13 and 14 both list app03. Remove the duplicate.
14G go to line 14
dd delete the whole line
A number in front repeats the action, so 3dd deletes three lines, and dG deletes from here to the end of the file. The deleted text is not gone yet: vi keeps it so p can put it somewhere else, which is exactly how exercise 9 moves a block.
6. Copy a line and change the copy
The task: add a fourth backend, app04, by copying the app03 line instead of typing it.
13G go to the app03 line
yy yank (copy) the line
p put the copy below; the cursor moves onto it
:s/app03/app04/ Enter on this line only, replace app03 with app04
:s is the substitute command, and without a range it works on the current line only, which is exactly what is needed here. Exercise 8 uses the same command on the whole file.
report option, which defaults to 2. A one-line :s that worked prints nothing at all, so look at the line rather than waiting for a message.
7. Search forward, backward and for the next match
The task: find a setting without scrolling for it.
/access_log Enter search forward for access_log
n next match in the same direction
N next match in the opposite direction
?TLS Enter search backward for TLS
There is only one access_log in the file, so pressing n wraps around the end and lands on the same line. vim says so at the bottom of the screen:
search hit BOTTOM, continuing at TOP
The backward search wraps the other way and prints search hit TOP, continuing at BOTTOM. Neither is an error; it tells you that you have seen every match.
8. Replace one occurrence, then replace all
The task, part one: the site moves to port 8443. Change the listen line, but not the four backend lines, which also contain 8080 and must stay on 8080.
gg top of the file
:%s/8080/8443/gc Enter whole file (%), every match on a line (g), confirm (c)
y the first match is the listen line: yes
q quit before touching the backends
With the c flag, vim stops at each match, highlights it and asks:
replace with 8443 (y/n/a/q/l/^E/^Y)?
y replaces this one, n skips it, a replaces this and all the rest, q stops. l is the one worth remembering: it replaces this match and stops, which does part one in a single key.
The task, part two: the domain changes from example.local to corp.local everywhere. No confirmation this time.
:%s/example\.local/corp.local/g Enter
8 substitutions on 8 lines
example.local without the backslash also matches exampleXlocal or example-local. On this file the result happens to be the same, which is exactly why the habit is dangerous: escape the dot as \. whenever you mean a literal dot, such as in a hostname or an IP address.
9. Move a block of lines
The task: the team wants the contact list at the top of the file. Move the # Maintenance contacts block, its three lines and the blank line under it, to just below the header.
16G go to the '# Maintenance contacts' line
5dd delete five lines: the comment, three contacts, the blank line
3G go to line 3, the blank line under the header
p put the five lines below it
5 fewer lines
5 more lines
There is no separate cut command in vi: dd removes the lines and keeps them, p puts them back wherever the cursor is. P puts above the cursor instead of below.
10. Delete everything to the end of the file
The task: the # Deprecated section at the bottom must go before go-live. Remove it, together with the blank line above it.
20G go to the blank line above '# Deprecated'
dG delete from here to the end of the file
4 fewer lines
dG is the same action-plus-target pattern as cw: delete, to the last line. d$ deletes from the cursor to the end of the line, and dgg deletes to the first line.
11. Undo and redo
The task: make a mistake on purpose and take it back.
dd delete the current line, the mistake
u undo: the line comes back
Ctrl+R redo: the line is deleted again
u undo again: the line is back, and the file is correct
vim prints what each step did, with a change number and how long ago it happened:
1 more line; before #10 0 seconds ago
The number after # counts changes in this session and will differ if you made extra edits along the way. vim keeps a long undo history, so pressing u repeatedly keeps going back through it.
uu undoes two changes. In the vi-compatible way, used by vim when it runs in compatible mode, uu undoes the undo, so the second press brings your change back. Tested on vim 9.1 in compatible mode: dd, u, u left the line deleted. If u seems to toggle, that is why; use Ctrl+R to move forward instead.
12. Save, save a copy, and quit
The task: save your work, keep a copy of it, and leave.
:w Enter write practice.conf
:w practice.conf.bak Enter write a copy under another name
:q Enter quit
"practice.conf" 19L, 550B written
"practice.conf.bak" [New] 19L, 550B written
:w newname writes the copy and leaves you editing the original, practice.conf. If you want to carry on editing the copy instead, :saveas practice.conf.bak writes it and switches to it. nano does the opposite by default, which the nano walk-through shows as its own exercise.
Check your work
This block writes what practice.conf should look like after all twelve exercises and compares it with yours. Paste it into the shell, in the practice folder:
cat > expected.conf <<'EOF'
# web01 site configuration - practice file for vi and nano
# Owner: ops-team Reviewed: 2026-01-15
# Maintenance contacts
contact alice@corp.local;
contact bob@corp.local;
contact carol@corp.local;
server_name web01.corp.local;
listen 8443;
root /var/www/html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Backend pool
backend app01.corp.local:8080;
backend app02.corp.local:8080;
backend app03.corp.local:8080;
backend app04.corp.local:8080;
EOF
diff -u expected.conf practice.conf && echo "All twelve exercises done correctly"
All twelve exercises done correctly
diff prints nothing when the two files are identical, and the message only appears when it succeeds. If anything differs, it prints the differing lines instead. A - line is what the answer expects, a + line is what your file has. This is the output when exercise 8 replaced every 8080 instead of only the listen port:
--- expected.conf
+++ practice.conf
@@ -13,7 +13,7 @@
error_log /var/log/nginx/error.log;
# Backend pool
-backend app01.corp.local:8080;
-backend app02.corp.local:8080;
-backend app03.corp.local:8080;
-backend app04.corp.local:8080;
+backend app01.corp.local:8443;
+backend app02.corp.local:8443;
+backend app03.corp.local:8443;
+backend app04.corp.local:8443;
Read it as: the backends should still say 8080, yours say 8443. Fix the four lines in vi, save, and run the diff line again until it prints the message. For a one-line check, the SHA-256 of a correct file is:
sha256sum practice.conf
5416913f3944c95556a29f2fc87acd0c8ba2ec61c168d0783678473bea451343 practice.conf
To practise again from the start, re-run step three of the setup and you have a fresh file.
Files you are not allowed to write
Real config files live under /etc and belong to root. Open one as a normal user and vi warns you the moment you make your first change:
W10: Warning: Changing a readonly file
It lets you keep editing. The problem arrives when you try to save:
E45: 'readonly' option is set (add ! to override)
Adding ! does not help, because the file system says no, not vi:
E212: Can't open file for writing
All three messages were produced by opening a root-owned file as an unprivileged user. The clean way to avoid them is to start with the right permissions:
# edits a temporary copy as you, then writes it back as root when you quit
sudoedit /etc/nginx/nginx.conf
# or run the editor itself as root
sudo vi /etc/nginx/nginx.conf
If you are already stuck with unsaved changes, do not throw them away. Save them somewhere you can write, quit, and copy the file into place:
:w /tmp/nginx.conf.new Enter
:q! Enter
sudo cp /tmp/nginx.conf.new /etc/nginx/nginx.conf
Hidden gems
Ctrl+[ is the same as Esc. They send the same character, and the vim documentation lists CTRL-[ as an alternative to Esc for leaving Insert mode. Useful on keyboards where Esc is awkward to reach.
. repeats your last change. After a dd, pressing . deletes another line. After a cw, it repeats the whole change including the text you typed. It is the fastest way to apply the same fix in several places: fix it once, then n, ., n.
A range turns one substitution into many. % means the whole file, but any range works: :11,14s/8080/8443/ changes only lines 11 to 14, the backends, and :.,$s/old/new/ works from the current line to the last. That is the precise way to do exercise 8, part one, without a confirmation prompt: :5s/8080/8443/.
vim ships its own interactive tutorial. Type vimtutor in the shell. The vim user manual describes it as a 30-minute tutorial in two chapters that teaches the basics hands-on, which makes it a good second session after this page.
The same twelve tasks in nano
The nano walk-through uses the identical practice file and the identical answer, so you can compare the two editors key for key. The main differences are here:
| Task | vi | nano |
|---|---|---|
| Save | :w | Ctrl+O then Enter |
| Quit, discarding changes | :q! | Ctrl+X then N |
| Go to line 18 | 18G | Ctrl+/, 18, Enter |
| Search | /text | Ctrl+W |
| Replace everywhere | :%s/old/new/g | Ctrl+\, then A for All |
| Delete a line | dd | Ctrl+K |
| Copy a line | yy | Alt+6 |
| Paste | p | Ctrl+U |
| Undo, redo | u, Ctrl+R | Alt+U, Alt+E |
| Save a copy | :w copy, keeps editing the original | Ctrl+O with a new name, switches to the copy |
nano treats a dot in a search as a plain dot unless you switch regular expressions on, so the escaping in exercise 8 is only needed in vi.
Where this matters
A server with nothing else installed. Minimal cloud images, containers and rescue shells can ship vi and no other editor, and installing one needs working networking, which is sometimes exactly what you are trying to fix.
ESXi hosts. The editor in the ESXi shell is a minimal vi. The vi and vim cheat sheet has a section on what the ESXi and BusyBox builds leave out.
Fixing a config after an SSH session drops you in. sudo vi +42 /etc/ssh/sshd_config, cw, :wq: exercises 3, 4 and 12 in one breath, with sudo because the file belongs to root. The connecting to a Linux server from Windows article covers getting to that prompt from a Windows machine.
Git commit messages and crontab. git commit and crontab -e open an editor for you and wait until you save and quit. With export EDITOR=vi in your shell profile that editor is vi, and exercise 2 is exactly that workflow. Ubuntu 24.04 falls back to nano when nothing is set.
Bulk edits across a config. Changing a hostname, a port or an IP address on every line is exercise 8, part two, and it is the same keystrokes whether the file has twenty lines or two thousand.
Tips and limitations
- When something unexpected happens, press Esc before anything else. It gets you to Normal mode from almost any state.
- Commands are case sensitive:
Gis the last line whileggis the first, andPputs above the cursor whilepputs below. :wqalways writes, even an unchanged file.ZZand:xonly write when something changed.- In a search or substitution pattern, a dot matches any character. Escape it as
\.for a literal dot. - vim reports a substitution count only when it makes more than two substitutions, so a successful one-line
:sis silent. - In vi-compatible mode a second
uundoes the undo instead of going further back. - Line numbers from
:set numberare display only; they are never written into the file. - If vim opens with
E325: ATTENTION, a swap file already exists: either another session is editing the same file, or an earlier session crashed. Read the whole message before choosing; the vim manual shows its process ID line marked(still running)when the other session is alive, andvim -r filenamerecovers a crashed session.
Official documentation
- Vim: change.txt, including :substitute and its flags
- Vim: undo.txt, including the two ways of undo
- Vim: usr_11.txt, recovering from a crash and the E325 swap file message
- vi: The Open Group Base Specifications (POSIX)
Related tools
- Port Checker: exercise 8 moves a listen port from 8080 to 8443; on a real server the next step is checking that the new port answers.
Related guides
- nano editor tutorial: a hands-on practice walk-through: the same practice file and the same twelve exercises in the editor that shows its shortcuts on screen.
- vi and vim cheat sheet: the lookup page for after this one, including visual mode, block selection and ranges.
- Connecting to a Linux server from Windows: getting to the prompt where you will actually use vi.
- Linux commands cheat sheet: the index of the Linux cluster, including what to use when a command is missing.
- Linux for Windows admins cheat sheet: Windows habits translated, useful alongside your first vi sessions on a server.
Five cheat sheets, one PDF
Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.