vi editor tutorial: a hands-on practice walk-through

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 toPress
Start typing texti, then type
Stop typing, back to commandsEsc
Save and quit:wq then Enter
Quit without saving anything:q! then Enter
Undo the last changeu
Stuck right now? Press Esc twice, type :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.

The three vi modes and the keys that move between them vi starts in Normal mode. The keys i, a, o, A or O enter Insert mode and Esc returns to Normal mode. A colon enters Command-line mode; Enter runs the command and Esc cancels it, both returning to Normal mode. Insert mode what you type is text Normal mode keys are commands vi always starts here Command-line mode a line typed at the bottom i a o A O Esc : Enter runs it, Esc cancels
Three modes are enough for every exercise on this page. When in doubt, press Esc: it always brings you back to Normal mode.

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.

Note: The exercises refer to line numbers. Turn them on inside vi with :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
Note: :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.

Faster for this particular typo: the letters 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.

Why nothing was printed: vim only reports substitutions when it makes more than two, a threshold set by the 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
Common mistake: in a vi search pattern a dot means any character. 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.

On minimal systems: the vim documentation describes two undo behaviours. In the vim way, 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:

Taskvinano
Save:wCtrl+O then Enter
Quit, discarding changes:q!Ctrl+X then N
Go to line 1818GCtrl+/, 18, Enter
Search/textCtrl+W
Replace everywhere:%s/old/new/gCtrl+\, then A for All
Delete a lineddCtrl+K
Copy a lineyyAlt+6
PastepCtrl+U
Undo, redou, Ctrl+RAlt+U, Alt+E
Save a copy:w copy, keeps editing the originalCtrl+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: G is the last line while gg is the first, and P puts above the cursor while p puts below.
  • :wq always writes, even an unchanged file. ZZ and :x only 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 :s is silent.
  • In vi-compatible mode a second u undoes the undo instead of going further back.
  • Line numbers from :set number are 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, and vim -r filename recovers a crashed session.

Official documentation


  • 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.

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.