Reading and editing files safely

Read logs, edit config with drop-ins, validate first.

Beginner14 min · lesson 5 of 29

Most of your time on a server is spent reading: logs to find out what happened, configuration to find out what the machine was told to do. Now and then you change a configuration file, and a mistake there can stop a service or lock you out of the machine. This lesson covers the tools for reading files and logs, the bytes you should not let reach your terminal, and a routine for changing configuration that leaves the package's files alone and checks your change before it takes effect.

Reading files and logs

A few examples write practice files, so start by making a directory for them:

deploy@web01 · Ubuntu 26.04 LTS
$ mkdir -p ~/cmd-view

cat prints a whole file at once, which suits short files such as this one-line SSH setting from the Ubuntu cloud image:

deploy@web01 · Ubuntu 26.04 LTS
$ cat /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
PasswordAuthentication no

Logs are longer and more sensitive, so first check whether you may read them. On Ubuntu the main system log is /var/log/syslog and logins, sudo use and SSH events go to /var/log/auth.log.

deploy@web01 · Ubuntu 26.04 LTS
$ ls -l /var/log/syslog /var/log/auth.log
-rw-r----- 1 syslog adm 4578751 Sep 27 08:06 /var/log/auth.log -rw-r----- 1 syslog adm 2495222 Sep 27 08:06 /var/log/syslog
$ id
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),4(adm),27(sudo)

Both files belong to the syslog user, which the logging daemon runs as, and to the group adm, with mode rw-r-----: the group may read them and nobody else may. Ubuntu puts the first administrator account in adm, and deploy is in it, so it reads the logs without sudo. Adding someone to adm lets an analyst read logs without sudo, which is far less than root, but it is still broad: adm reads every system log on Ubuntu, including auth.log and the systemd journal, where passwords typed into the user-name prompt and secrets printed by applications turn up. Grant it only to people cleared to read all of that. On RHEL the equivalent files are /var/log/messages and /var/log/secure, readable by root only, so you read them with sudo:

deploy@rocky10 · Rocky Linux 10.2
$ ls -l /var/log/messages /var/log/secure
-rw-------. 1 root root 33350200 Sep 27 08:17 /var/log/messages -rw-------. 1 root root 608651 Sep 27 08:17 /var/log/secure

New lines are added at the bottom of a log, so tail is the command you want: it prints the last 10 lines, or as many as -n asks for. logger writes a line into the system log, which is a quick way to check that logging works:

deploy@web01 · Ubuntu 26.04 LTS
$ logger -t cmd-view "test message from deploy"
$ tail -n 3 /var/log/syslog
… 2026-09-27T08:06:52.833986+00:00 web01 cmd-view: test message from deploy

The two lines above yours are whatever else the machine logged a moment earlier, so they are left out here. Each line starts with a timestamp in RFC 3339 format (date, time to the microsecond, and the offset from UTC; Ubuntu 26.04 writes this format), then the host name, then the program that logged the line, then the message. head is the mirror image and prints the first lines; wc -l counts lines, which tells you how much you are dealing with before you open a file:

deploy@web01 · Ubuntu 26.04 LTS
$ head -n 3 /etc/ssh/sshd_config
# This is the sshd server system-wide configuration file. See # sshd_config(5) for more information.
$ wc -l /var/log/syslog /var/log/auth.log
20894 /var/log/syslog 36842 /var/log/auth.log 57736 total

For anything longer than a screen, use less. It opens even a very large file at once, because it reads only the part you are looking at. Space and b move a page forward and back, /word searches forward and n jumps to the next match, G goes to the end and g to the start, &word hides every line that does not contain word, and q quits. The lesson on logs shows the other half of Linux logging, the systemd journal.

Which reader for which job
You need to look inside a file
short, trusted file
cat
prints it all at once
long file, or search
less
pages through it; /word searches
newest lines, or watch it
tail -n N, tail -F
the end of a log, followed by name
untrusted bytes
cat -v or less
control codes shown, not obeyed
how big is it
wc -l
counts lines before you open it

Following a log as it grows

tail -f keeps the file open after printing the last lines and prints each new line as it is written; you stop it with Ctrl-C. Logs do not grow forever, though. logrotate renames them on a schedule (weekly for /var/log/syslog on Ubuntu, keeping four old copies) and a new, empty file takes the old name. The next two terminals follow a small application log, ~/cmd-view/app.log. While tail runs, a second session of deploy (another SSH login, or a second terminal on the console) types three commands a second or so apart: echo "request 2" >> ~/cmd-view/app.log adds a line, mv ~/cmd-view/app.log ~/cmd-view/app.log.1 rotates the log the way logrotate does, and echo "request 3, after rotation" > ~/cmd-view/app.log writes to the new file. Ctrl-C is pressed at the end of each terminal.

deploy@web01 · Ubuntu 26.04 LTS
$ rm -f ~/cmd-view/app.log* echo "request 1" > ~/cmd-view/app.log
$ tail -f ~/cmd-view/app.log
request 1 request 2 Interrupt
# Interrupt marks the moment Ctrl-C was pressed

tail -f follows the file it opened. After the rename that file is app.log.1, which never grows again, so the third request never appears and nothing tells you. tail -F follows the name instead:

deploy@web01 · Ubuntu 26.04 LTS
$ rm -f ~/cmd-view/app.log* echo "request 1" > ~/cmd-view/app.log
$ tail -F ~/cmd-view/app.log
request 1 request 2 tail: '/home/deploy/cmd-view/app.log' has become inaccessible: No such file or directory tail: '/home/deploy/cmd-view/app.log' has appeared; following new file request 3, after rotation Interrupt
# Interrupt marks the moment Ctrl-C was pressed

It notices the file disappear, opens the new one when it appears and carries on. Use -F whenever you leave a follow running for a while. Inside less, pressing F follows the end of the file in the same way as tail -f, and Ctrl-C returns you to scrolling.

Bytes that are instructions to your terminal

Your terminal does not only display text. Some bytes, control characters and escape sequences, are commands to it: move the cursor, erase a line, change colours or the window title. A log records whatever a client sent, so anyone who can get a line into a log can put those bytes there too. Here is a web access log line built the way such a request would be logged:

deploy@web01 · Ubuntu 26.04 LTS
$ printf "203.0.113.7 GET /shell.php?cmd=id 200\r\033[K198.51.100.4 GET /index.html 200\n" > ~/cmd-view/access.log
$ cat -v ~/cmd-view/access.log
203.0.113.7 GET /shell.php?cmd=id 200^M^[[K198.51.100.4 GET /index.html 200
$ file ~/cmd-view/access.log
/home/deploy/cmd-view/access.log: ASCII text, with CR, LF line terminators, with escape sequences

cat -v shows control bytes as visible codes: ^M is a carriage return, which moves the cursor back to the start of the line, and ^[[K is an escape sequence that erases from the cursor to the end of the line. With plain cat, your terminal carries both out, and the screen shows only 198.51.100.4 GET /index.html 200: the probe for shell.php from 203.0.113.7 is printed and then wiped before you can see it. file warns about escape sequences, and less shows control characters as caret codes by default. Read untrusted files with less or cat -v, and never with less -r, which passes them through raw.

Editors, and editing as root with sudoedit

Servers have no graphical editor, so you edit in the terminal. On Ubuntu the default is nano: you type normally, and the bottom two lines list the shortcuts, where ^ means Ctrl. Ctrl-O writes the file (press Enter to confirm the name) and Ctrl-X exits. vi is part of the POSIX standard and present on practically every Linux and Unix system; Ubuntu 26.04 ships vim and a smaller vim.tiny, while a minimal RHEL 10 install has only vi:

deploy@rocky10 · Rocky Linux 10.2
$ command -v nano vim vi
/usr/bin/vi

vi starts in normal mode, where keys are commands rather than text. Press i to insert text and Esc to return to normal mode, then type :wq and Enter to save and quit, or :q! to quit without saving. When a vi session stops making sense, Esc followed by :q! gets you out without changing the file.

Configuration files belong to root, and sudo nano /etc/... runs the whole editor as root, including anything it can do besides editing, such as running shell commands. sudoedit FILE (the same as sudo -e FILE, and supported by sudo-rs) is the safer habit. It copies the file to a temporary file you own, runs your own editor on the copy as you, and when you quit it copies the result back as root. It refuses to edit symbolic links and files in a directory you can write to, which blocks link tricks. It uses the editor named in the variable SUDO_EDITOR, VISUAL or EDITOR (the environment lesson explains variables); with none of them set, Ubuntu's sudo-rs opens /usr/bin/editor, which is nano. SUDO_EDITOR=vim sudoedit FILE picks another for one run.

Changing configuration: drop-in, validate, reload, verify

The filesystem lesson showed that packages own their files and your changes belong in /etc, preferably as a separate drop-in file. The SSH server is the example that matters most, because a mistake there can lock you out of the server. Its main file pulls in the drop-in directory:

deploy@web01 · Ubuntu 26.04 LTS
$ grep -n Include /etc/ssh/sshd_config
24:Include /etc/ssh/sshd_config.d/*.conf
$ ls -l /etc/ssh/sshd_config.d/
total 8 -rw-r--r-- 1 root root 20 Sep 26 19:49 10-acceptenv-colorterm.conf -rw-r--r-- 1 root root 26 Sep 18 16:38 60-cloudimg-settings.conf

grep -n prints the lines that contain a word, with their line numbers (the lesson on finding files and text covers it). The Include is on line 24, near the top, so sshd reads the drop-ins, in name order, before the rest of the main file. For each keyword sshd uses the first value it reads. Now compare what the main file says about password logins with what the server will actually do:

deploy@web01 · Ubuntu 26.04 LTS
$ grep -n PasswordAuthentication /etc/ssh/sshd_config
78:#PasswordAuthentication yes 102:# PasswordAuthentication. Depending on your PAM configuration, 106:# PAM authentication, then enable this but set PasswordAuthentication
$ sudo sshd -T | grep passwordauthentication
passwordauthentication no

Line 78 is a comment that shows the built-in default; it sets nothing. sudo sshd -T reads the configuration exactly as the server would and prints the value in force for every keyword (for a connection that matches no Match block; -C user=NAME,host=HOST,addr=ADDRESS shows what one particular login would get), and the pipe | hands that output to grep (the next lesson explains pipes). Password logins are off because 60-cloudimg-settings.conf says so, and since drop-ins are read first, uncommenting line 78 and writing yes there would change nothing. This is the most common reason for "I changed sshd_config and nothing happened".

To make a change, create your own drop-in with sudoedit /etc/ssh/sshd_config.d/10-cmd-view.conf. This one makes the server check on idle connections every five minutes (a harmless setting to practise with), with the comment on a line of its own:

/etc/ssh/sshd_config.d/10-cmd-view.conf
# Probe idle SSH clients every 5 minutes; drop them after 3 missed replies.
ClientAliveInterval 300

Before anything uses the file, test it with sudo sshd -t. Suppose the first attempt had a typo, ClientAlivInterval:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo sshd -t
/etc/ssh/sshd_config.d/10-cmd-view.conf: line 2: Bad configuration option: ClientAlivInterval /etc/ssh/sshd_config.d/10-cmd-view.conf: terminating, 1 bad configuration options

The test refuses the file, names the file and the line, and exits with status 255. Nothing has been applied, because a test never touches the running server.

After fixing the typo, the test prints nothing, which means the configuration is valid, and -T confirms the value that will be used:

deploy@web01 · Ubuntu 26.04 LTS
$ cat /etc/ssh/sshd_config.d/10-cmd-view.conf
# Probe idle SSH clients every 5 minutes; drop them after 3 missed replies. ClientAliveInterval 300
$ sudo sshd -t
$ sudo sshd -T | grep clientaliveinterval
clientaliveinterval 300
$ sudo systemctl reload ssh
$ ls -l /etc/ssh/sshd_config.d/10-cmd-view.conf
-rw-r--r-- 1 root root 100 Sep 27 08:07 /etc/ssh/sshd_config.d/10-cmd-view.conf

reload makes the running server read its configuration again, and connections that are already open, including yours, carry on. The file sudoedit created belongs to root with mode 0644, like the other drop-ins. On Ubuntu, the ssh.service unit also tests the configuration itself before every start and reload:

deploy@web01 · Ubuntu 26.04 LTS
$ systemctl cat ssh.service
# /usr/lib/systemd/system/ssh.service … [Service] … ExecStartPre=/usr/sbin/sshd -t ExecStart=/usr/sbin/sshd -D $SSHD_OPTS ExecReload=/usr/sbin/sshd -t ExecReload=/bin/kill -HUP $MAINPID …

So on Ubuntu a reload with a broken file fails and the old settings stay in force, but a restart stops the running server first, and the failed test then keeps it from starting again: new logins fail until you fix the file. RHEL's sshd.service has no test step, and its reload only sends the hangup signal. sshd answers that signal by starting itself again with the new configuration, and if the file is broken it cannot start and the listener is gone:

deploy@rocky10 · Rocky Linux 10.2
$ systemctl cat sshd.service
# /usr/lib/systemd/system/sshd.service … [Service] … ExecStart=/usr/sbin/sshd -D $OPTIONS ExecReload=/bin/kill -HUP $MAINPID …
Keep a second session open
When you change how the SSH server or sudo works on a remote machine, keep a second SSH session logged in and prefer reload to restart. Test the new configuration with a fresh login from a third window before you close anything. The same routine applies to sudo: sudo visudo -c checks the sudoers configuration, and the sudo lesson shows how to check a new file before installing it.
deploy@web01 · Ubuntu 26.04 LTS
$ sudo visudo -c
/etc/sudoers: parsed OK

Try this

With the lesson's drop-in in place, add a second one, /etc/ssh/sshd_config.d/90-cmd-view.conf, containing ClientAliveInterval 60, and predict what sudo sshd -T | grep clientaliveinterval will print before you run it. It prints 300, because sshd keeps the first value it reads and 10-cmd-view.conf sorts first. Then undo everything: remove both files with sudo rm, run sudo sshd -t and sudo systemctl reload ssh, and confirm that sudo sshd -T | grep clientaliveinterval reports clientaliveinterval 0, the default.

Takeaway

Read untrusted files with less or cat -v, and follow logs with tail -F. Change configuration by adding a drop-in, testing it (sshd -t, visudo -c), reloading rather than restarting, and confirming the value in force (sshd -T), with a second session open.

Quick check
01You set PasswordAuthentication yes on line 78 of /etc/ssh/sshd_config and reload ssh, but sudo sshd -T still prints passwordauthentication no. Why?
Incorrect — A reload makes sshd read its whole configuration again, main file and drop-ins alike.
Incorrect — Keywords are case-insensitive. sshd -T prints lowercase names, but the file can use either.
Incorrect — sshd settings apply to every new connection, whenever the account was created.
Correct — The Include on line 24 reads the drop-ins first, and 60-cloudimg-settings.conf sets it to no.
02A web log looks normal with cat, but cat -v shows ^M^[[K in the middle of one line. What does that mean?
Incorrect — ^M alone can be a Windows line ending, but ^[[K is an escape sequence that erases text, and it sits mid-line.
Incorrect — The bytes are deliberate. Deleting the log would also destroy the evidence of what was hidden.
Correct — The carriage return moved the cursor to the start of the line and the escape sequence erased what had been printed.
Incorrect — cat -v wraps nothing. It only makes control bytes that are really in the file visible.
03You changed an SSH drop-in on a remote Ubuntu 26.04 server. What should you do right before sudo systemctl reload ssh?
Incorrect — A restart stops the running server, and a broken file then keeps it down. It is the riskier command, not a preparation.
Correct — The test catches errors before anything is applied, and the spare session is your way back in if something still goes wrong.
Incorrect — daemon-reload re-reads unit files. sshd reads its own configuration files, and the reload tells it to.
Incorrect — A reload leaves existing sessions running. Logging out only removes your way to fix a mistake.

Related