Marta writes at 09:12: "Last night, around three, the website was throwing errors. What happened?". That question either has an exact answer or it has none, and the difference is made by what you did before it happened. A server remembers nothing: the only thing left of the early hours is whatever was written down somewhere, with the right timestamp, without having been rotated, truncated or lost in the reboot. This lesson is about that. And it settles, along the way, the first debt I announced when closing Module 4: /var/log/tramontana/access.log is 412 lines long and growing with nobody rotating it, and the day it fills /var it will take the whole application down with it.
Contents
- The two logging layers that coexist on Ubuntu
- syslog facilities and severities
journalctlin depth- Journal persistence
/etc/systemd/journald.confand controlling the size- The classic files in
/var/log - Writing to the log from your scripts with
logger logrotate: why and how- The problem of the open file after rotation
- Log centralisation
- What must NEVER end up in a log
- Tramontana case: rotation and the answer for Marta
- The two logging layers that coexist on Ubuntu
On a freshly installed Ubuntu 24.04 there are two logging systems running at the same time, and understanding the division of labour avoids a lot of confusion:
systemd-journald |
rsyslog |
|
|---|---|---|
| Format | Binary, structured, with fields and an index | Plain text, one line per event |
| Where it writes | /run/log/journal or /var/log/journal |
/var/log/syslog, auth.log, kern.log… |
| Queried with | journalctl |
grep, less, awk |
| Metadata | Unit, PID, UID, cgroup, executable, boot ID… | Whatever fits on the line |
| Rotation and remote sending | Internal, by size and time; systemd-journal-remote |
logrotate; native, mature remote sending (TCP/TLS) |
The real flow: everything a systemd-managed service writes to stdout and stderr is captured by journald, along with what arrives through the /dev/log socket and the kernel's messages. Journald stores it with its metadata and, in addition, forwards it to rsyslog (ForwardToSyslog=yes), which writes it into the same old text files. That is why the same message shows up in journalctl and in /var/log/syslog.
Why keep both? The journal is incomparably better for querying — it filters by unit, by priority, by boot, by field — and the text files are better for what you already know how to do with grep, awk and sed, for third-party tools and for remote sending. On a real server you live with both.
- syslog facilities and severities
That vocabulary comes from the 1980s and is still alive because the whole industry understands it. Every message carries a facility (where it comes from) and a severity (how much it matters).
| No. | Severity | When |
|---|---|---|
| 0-1 | emerg / alert |
The system is unusable / you must act immediately |
| 2 | crit |
A serious failure: disk, hardware |
| 3 | err |
The level you look at daily |
| 4-5 | warning / notice |
Something odd that is not breaking anything yet / a significant but normal event |
| 6-7 | info / debug |
The normal course of things / only while you are diagnosing |
Common facilities: auth and authpriv (authentication: sudo, sshd), cron, daemon (services), kern (the kernel), mail, syslog, and local0–local7, reserved for your applications. That last one is the practical key: when backup_tramontana.sh writes to the log, it will do so with a local0 facility and a matching severity, and that way you can filter your own messages without dragging in the system's.
journalctl in depth
journalctl in depthIt is the day-to-day tool and it is worth learning properly, because it replaces half a dozen greps.
journalctl # everything, paginated, from the oldest message
journalctl -e # go straight to the end (the most usual); -n 50, the last 50
journalctl -f # follow live, the journal's 'tail -F'; -r reverses the order
journalctl --no-pager # no pager: for scripts and for redirectingFiltering by unit, time and priority
journalctl -u tramontana.service -f # follow one unit live
journalctl -u tramontana.service -u postgresql.service # several at once
journalctl --since "1 hour ago"
journalctl --since yesterday --until "today 06:00"
journalctl --since "2026-08-18 03:00" --until "2026-08-18 03:30"
journalctl -p err # priority err or WORSE (0..3)
journalctl -p warning..err # a range of prioritiesThe time expressions accept yesterday, today, tomorrow, now, -30min, "2 days ago" and absolute dates. It is one of the weighty reasons for using the journal: answering "what happened between 3:00 and 3:30" with text files demands an awk with ranges; here it is two options.
By boot and by kernel
journalctl --list-boots # the boots that are kept; -b, only the current one
journalctl -b -1 # the PREVIOUS boot: what happened before the reboot
journalctl -k # the kernel only (like dmesg, but with history)
journalctl -k -b -1 -p err # kernel errors from the previous bootjournalctl -b -1 answers "the server rebooted on its own last night, why?", and it requires a persistent journal: exactly what the next section is about.
Structured fields
Each journal entry is not a line: it is a set of fields. Discover them with:
$ journalctl -u tramontana.service -n 1 -o verbose | head -9
Tue 2026-08-18 03:07:41.882145 CEST [s=9f2c...;i=3a1;b=7d4e...]
_UID=997
_COMM=tramontana
_EXE=/opt/tramontana/releases/3.2.1/bin/tramontana
_SYSTEMD_UNIT=tramontana.service
PRIORITY=3
SYSLOG_IDENTIFIER=tramontana
MESSAGE=db_timeout after 30s (active_connections=200)The fields beginning with _ are added by journald, and that is why they are trustworthy: an application cannot forge them. They are used as filters:
journalctl _PID=1284 ; journalctl _UID=997
journalctl _COMM=sudo # everything sudo has done; -t filters by SYSLOG_IDENTIFIER
journalctl _SYSTEMD_UNIT=tramontana.service _PID=1284 # they combine with ANDOutput formats and searching
journalctl -u tramontana.service -o cat # only the message, no date or host
journalctl -u tramontana.service -o short-iso # ISO 8601 date, sortable
journalctl -u tramontana.service -o json-pretty # to process with jq
journalctl -u tramontana.service --grep 'db_timeout' # regex over the MESSAGE
journalctl --disk-usage # how much space the journal takes$ journalctl -u tramontana.service --since "2026-08-18 03:00" --until "03:30" -p err -o short-iso | head -3
2026-08-18T03:07:41+0200 srv-tramontana tramontana[1284]: db_timeout after 30s (active_connections=200)
2026-08-18T03:11:02+0200 srv-tramontana tramontana[1284]: db_timeout after 30s (active_connections=200)
2026-08-18T03:14:55+0200 srv-tramontana tramontana[1284]: db_timeout after 30s (active_connections=200)Combining filters is the essence of the tool: unit + time window + priority + a reproducible format, in a single line.
- Journal persistence
This is the detail that surprises everybody the first time: by default, on many installations the journal is volatile. It lives in /run/log/journal, which is a tmpfs in RAM, and it is lost entirely on reboot. If the server went down in the early hours and you rebooted it, you have just destroyed the only evidence of why it went down.
If journalctl --list-boots shows only one line, that is the alarm signal. Enabling persistence is trivial and it is one of the first things to do on a new server:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald$ journalctl --list-boots
-1 7d4e1c... Mon 2026-08-17 08:14:02 CEST—Tue 2026-08-18 04:29:57 CEST
0 9a2b3f... Tue 2026-08-18 04:30:11 CEST—Tue 2026-08-18 12:03:44 CESTThe directory's permissions (2755 root:systemd-journal) are set by systemd-tmpfiles; that SGID from 05-02 is what lets the systemd-journal group — and adm — read the files created inside it.
/etc/systemd/journald.conf and controlling the size
/etc/systemd/journald.conf and controlling the sizeA persistent journal with no limits is a full /var waiting its turn. The limits are set here:
# /etc/systemd/journald.conf (or better, a file in /etc/systemd/journald.conf.d/)
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=500M
SystemKeepFree=1G
SystemMaxFileSize=50M
MaxRetentionSec=30day
MaxFileSec=1day
RateLimitIntervalSec=30s
RateLimitBurst=10000
ForwardToSyslog=yes| Directive | What it controls |
|---|---|
Storage |
persistent, volatile, auto (persistent if /var/log/journal exists) or none |
SystemMaxUse / SystemKeepFree |
The journal's total ceiling (10% of the partition by default) / the space it always leaves free on /var |
SystemMaxFileSize / MaxRetentionSec |
The size of each file before rotating / the maximum age that is kept |
RateLimitIntervalSec / RateLimitBurst |
How many messages per service and per interval are accepted |
That last pair deserves a serious warning: with the default values, a service that emits more than 10,000 messages in 30 seconds will see journald silently discard the rest, leaving only a note of how many it suppressed. Right in the middle of a storm of errors, which is when you need them most. If your application can be noisy during an incident, raise RateLimitBurst or disable it (RateLimitIntervalSec=0), accepting the cost in disk.
sudo cp -a /etc/systemd/journald.conf{,.bak-$(date +%F)} # and then edit
sudo systemctl restart systemd-journald && journalctl --disk-usage
sudo journalctl --vacuum-size=300M # trim now down to 300 MiB
sudo journalctl --vacuum-time=15d # or delete anything older than 15 days
- The classic files in
/var/log
/var/log| File | Content |
|---|---|
syslog / kern.log |
rsyslog's general drawer / kernel messages |
auth.log |
Authentication: sudo, sshd, su, password changes. The first one you look at |
dpkg.log and apt/history.log |
What was installed, when and who asked for it (05-03) |
wtmp / btmp / lastlog |
Sessions opened / failed attempts / last login per account |
The last three are binary: a cat on them spits out rubbish and can mess up your terminal. They are read with their own tools:
$ sudo lastb -n 3 # FAILED login attempts
admin ssh:notty 203.0.113.44 Tue Aug 18 02:14 - 02:14 (00:00)
root ssh:notty 203.0.113.44 Tue Aug 18 02:14 - 02:14 (00:00)
$ last -n 2 # successful logins
operator pts/0 10.0.2.1 Tue Aug 18 09:02 still logged inA string of lastb entries against root from a foreign IP is exactly the kind of signal you will learn to cut off with fail2ban in 06-03.
- Writing to the log from your scripts with
logger
loggerYour Module 4 scripts write to /var/log/tramontana/cron-backup.log because you did not know how to do anything else. logger sends the message to the same place as the rest of the system, with its priority and its tag:
logger -t backup "backup completed" # a tag and info
logger -t backup -p local0.err "encryption failed" # facility.severity
logger -t backup -s -p local0.warning "low space" # -s: also to stderrNow we modify log() and error() in lib/common.sh so that, as well as their file, they write to the journal:
# lib/common.sh — the version connected to the journal
readonly LOG_TAG="${LOG_TAG:-$(basename "${0%.sh}")}"
log() {
local message="$*"
printf '[%s] %s\n' "$(date --iso-8601=seconds)" "$message" >>"$LOG_FILE"
logger -t "$LOG_TAG" -p local0.info -- "$message"
}
error() {
local message="$*"
printf '[%s] ERROR: %s\n' "$(date --iso-8601=seconds)" "$message" >&2
logger -t "$LOG_TAG" -p local0.err -- "$message"
}The -- before the message stops a text starting with a hyphen being interpreted as an option, following the discipline from 04-03. And since the script now runs inside tramontana-backup.service (05-05), everything it writes to stdout also ends up in that unit's journal, with its metadata:
$ journalctl -u tramontana-backup.service --since today -o short-iso | tail -3
2026-08-18T04:20:07+0200 srv-tramontana backup[8842]: backup start (version=3.2.1)
2026-08-18T04:23:51+0200 srv-tramontana backup[8842]: sha256 verified
2026-08-18T04:23:51+0200 srv-tramontana backup[8842]: backup completed in 224sWith that, the question "was the backup taken last night?" is a command, not an investigation.
logrotate: why and how
logrotate: why and howAn unrotated log grows until it fills its partition. When /var fills up, the application cannot write, the database cannot commit transactions and the system itself stops recording the disaster. Rotation is not tidiness: it is availability.
logrotate runs daily via logrotate.timer (systemd, which you now know how to read), reads /etc/logrotate.conf and everything in /etc/logrotate.d/, and decides for each file whether it is time to rotate.
| Directive | What it does |
|---|---|
daily / weekly / monthly |
How often it rotates; rotate N, how many copies are kept |
size 100M / maxsize 100M |
Rotates on exceeding that size ignoring the schedule / rotates on the scheduled run or earlier if it exceeds it |
compress / delaycompress |
Compresses with gzip / from the second rotation onwards, leaving .1 uncompressed |
missingok / notifempty |
If it does not exist, no complaint / does not rotate if it is empty |
create MODE USER GROUP |
Creates the new file with those permissions |
su USER GROUP |
Which identity logrotate operates as in that directory |
sharedscripts |
Runs postrotate once even if the pattern matches several files |
postrotate … endscript / dateext |
Commands after rotating / names the copies with the date instead of .1 |
Always test before trusting, following the convention of simulating before acting:
sudo logrotate -d /etc/logrotate.d/tramontana # simulation: it touches nothing
sudo logrotate -f /etc/logrotate.d/tramontana # force the rotation now
cat /var/lib/logrotate/status | grep tramontana # when it was last rotated
- The problem of the open file after rotation
This connects with what you saw in 05-04 with lsof +L1. When logrotate does mv access.log access.log.1, the process that had it open carries on writing to the same inode: the new file stays at zero for ever and the old one keeps growing, now invisible. There are two solutions and you have to choose with judgement:
| Solution | How | Advantage | Drawback |
|---|---|---|---|
| A signal to the process | postrotate with kill -USR1 or systemctl reload |
No data loss, no copy | The application must know how to reopen its log |
copytruncate |
It copies the file and truncates the original to zero | It requires nothing from the application | The lines written between the copy and the truncation are lost, and it doubles the file on disk |
The rule: if the application knows how to reopen its log, use the signal; copytruncate is the last resort for software that does not cooperate.
- Log centralisation
With one server, journalctl is enough. With two it is not: correlating an error from the load balancer with another from the application by SSHing into each machine is unworkable, and besides, local logs disappear when the machine is compromised or lost, which is precisely when they are needed most. An overview of the options, without setting them up here:
- Remote rsyslog: the classic, lightweight route,
*.* @@server:6514over TCP with TLS. Mature and sufficient for many cases. systemd-journal-remote/-upload: it preserves the journal's structure across machines, withjournalctl -mto query several.- Observability stacks such as Loki + Promtail + Grafana, or Elasticsearch + Logstash + Kibana: search, dashboards and alerts, at the price of operating an infrastructure that consumes more resources than the service itself.
One principle does not change: the remote destination must be append-only for whoever sends to it. If an attacker who gets into srv-tramontana can delete the logs on the central server, centralisation contributes nothing forensically.
- What must NEVER end up in a log
Compliance warning (GDPR). Logs get copied, sent off the machine, kept for months and read by far more people than production data. They must never contain passwords, tokens, API keys, session cookies, card numbers or personal data of Tramontana's guests (full name, email, telephone, identity document). Logging
guest_id=1017is correct; loggingguest=Ana Pérez, [email protected]turns your log file into a file containing personal data, with all the obligations that drags along. The retention policy — how long each type of record is kept — is a legal decision before it is a technical one and must be validated by the data protection or compliance officer, bearing in mind that some records have legal minimum retention periods and others have maximums.
Concrete, cheap measures: review what your application writes at debug level before enabling it in production; check that the URLs you log do not carry tokens in the query string; restrict /var/log/tramontana to the adm group with the ACL from 05-02; and set the retention in logrotate and in journald.conf instead of leaving it to chance.
- Tramontana case: rotation and the answer for Marta
/etc/logrotate.d/tramontana
# /etc/logrotate.d/tramontana — rotation of the Tramontana Bookings logs
/var/log/tramontana/*.log {
daily
rotate 14
maxsize 100M
compress
delaycompress
missingok
notifempty
dateext
dateformat -%Y-%m-%d
create 0640 svc-tramontana adm
su root adm
sharedscripts
postrotate
/usr/bin/systemctl reload tramontana.service > /dev/null 2>&1 || true
endscript
}Every line has a reason: rotate 14 with daily gives two weeks of history, which is what was agreed with Marta; maxsize 100M protects against a spike — an attack or a loop of errors can generate 100 MiB in an afternoon and we do not want to wait until tomorrow —; create 0640 svc-tramontana adm reproduces exactly the permissions the application needs in order to write and the adm group needs in order to read; su root adm is mandatory when the directory does not belong to root, or logrotate refuses to act for safety reasons; sharedscripts avoids reloading the service five times (once per file); and the postrotate uses systemctl reload, which the application translates into reopening its log files, avoiding copytruncate and the loss of lines.
A mandatory verification before calling it good:
$ sudo logrotate -d /etc/logrotate.d/tramontana 2>&1 | tail -6
rotating pattern: /var/log/tramontana/*.log after 1 days (14 rotations)
considering log /var/log/tramontana/access.log
Now: 2026-08-18 12:20
Log needs rotating
rotating log /var/log/tramontana/access.log, log->rotateCount is 14
renaming /var/log/tramontana/access.log to /var/log/tramontana/access.log-2026-08-18$ sudo logrotate -f /etc/logrotate.d/tramontana && ls -l /var/log/tramontana/
-rw-r----- 1 svc-tramontana adm 0 Aug 18 12:21 access.log
-rw-r----- 1 svc-tramontana adm 41283 Aug 18 12:21 access.log-2026-08-18
$ sudo lsof +L1 | grep tramontana || echo "no deleted files in use: correct"
no deleted files in use: correctThat last command is the proof that the postrotate worked: if the application had not reopened the file, a (deleted) with NLINK 0 would show up.
The answer for Marta
Now the 03:00 question is answered in three commands:
$ journalctl --since "2026-08-18 02:50" --until "2026-08-18 03:30" -p err -o short-iso | head -2
2026-08-18T03:07:41+0200 srv-tramontana tramontana[1284]: db_timeout after 30s (active_connections=200)
2026-08-18T03:09:12+0200 srv-tramontana tramontana[1284]: db_timeout after 30s (active_connections=200)
$ journalctl --since "2026-08-18 02:50" --until "2026-08-18 03:30" -p err --no-pager | wc -l
15
$ awk '$2 >= "03:00" && $2 < "03:30" && $5 ~ /^5/' /var/log/tramontana/access.log-2026-08-18 | wc -l
9Fifteen errors in the small-hours window, all of them the same: db_timeout with active_connections=200, which is exactly the value of max_connections in /etc/tramontana/app.conf. The report for Marta, with the usual structure — what we know, what it protects and what it does not —: "Between 03:00 and 03:30 there were 15 errors of the same type: the application exhausted its limit of 200 database connections and the requests timed out after 30 s. Nine of them ended up returning a 5xx to users. There was no service outage and no confirmed bookings were lost. The logs now rotate daily and are kept for 14 days, so next time we will have the complete trace. What remains is to find out why the connections run out at that hour, and we will measure that in the next review."
sudo tee -a /opt/tramontana/HISTORY >/dev/null <<'END'
2026-08-18 Logs (operator)
- persistent journal (/var/log/journal), SystemMaxUse=500M, MaxRetentionSec=30day
- /etc/logrotate.d/tramontana: daily, rotate 14, maxsize 100M, reload in postrotate
- lib/common.sh also writes to the journal via logger (local0)
- Incident 03:00-03:30: 15 x db_timeout with active_connections=200. Analysis pending.
ENDCommon Mistakes and Tips
- Assuming the journal is persistent. If
journalctl --list-bootsshows only one boot, you are losing the logs at every reboot. Create/var/log/journaltoday. - Rotating without telling the process. The new file stays empty and the old one keeps growing invisibly.
lsof +L1gives it away; usepostrotatewithreloador, as a last resort,copytruncate. - Forgetting
suin alogrotate.dwhose directory is not root's: logrotate rejects it for safety reasons and silently stops rotating. Check it withlogrotate -d. rotate 0or a minimal retention "to save disk": on the day of the incident you will have nothing to look at. Agree the retention with whoever is going to need it.- Ignoring journald's rate limiting. In a storm of errors you can lose exactly the messages you are after. Review
RateLimitBurstif your application is noisy. catonwtmporbtmp. They are binary: they are read withlastandlastb. And do not log personal data or secrets: it is a leak in text-file format, replicated in every backup.- Tip: any script that runs on its own must be able to answer "did it run, and with what result?" with one command. If it cannot, it is missing logging.
Exercises
- The small-hours window. Write a single command that shows, from the previous boot, only the messages of priority
error worse fromtramontana.serviceproduced between 02:00 and 06:00, in ISO-dated format, and another that counts how many there were of each distinct message. - Rotation for a third-party log. A tool writes to
/var/log/analytics/events.logas theanalyticsuser, does not know how to reopen its file and generates about 30 MiB a day. Write itslogrotate.dfile justifying every directive, and explain what you lose with the chosen solution. - Access audit. With what you have seen, answer: how many failed login attempts were there yesterday, from which IP, and which users were attempted? Give the commands and say where each piece of data lives.
Solutions
1.
journalctl -b -1 -u tramontana.service -p err \
--since "2026-08-18 02:00" --until "2026-08-18 06:00" -o short-iso$ journalctl -b -1 -u tramontana.service -p err --since "2026-08-18 02:00" \
--until "2026-08-18 06:00" -o cat --no-pager | sort | uniq -c | sort -rn
15 db_timeout after 30s (active_connections=200)
3 backend unavailable (503)-o cat leaves only the text of the message, which is what makes it possible to group with sort | uniq -c; with the normal format, the timestamp would make every line unique. It is the same counting pattern from 03-05, now applied to the journal.
2.
# /etc/logrotate.d/analytics
/var/log/analytics/events.log {
daily
rotate 7
maxsize 50M
compress
delaycompress
missingok
notifempty
copytruncate
su analytics analytics
}daily with rotate 7 gives a week, which is enough for 30 MiB a day; maxsize 50M covers a spike without waiting until tomorrow; compress with delaycompress saves disk without compressing the file that may still be being written; su analytics analytics is essential because the directory is not root's; and copytruncate is the only viable option, since the tool does not know how to reopen its log and there is no signal to send it.
What you lose: the lines written between the copy and the truncation, which is a small but real window, and twice the disk space during that instant. create is not used because with copytruncate the original file is never renamed or recreated.
3.
$ sudo lastb -s yesterday | head -5 # /var/log/btmp: FAILED attempts
admin ssh:notty 203.0.113.44 Mon Aug 17 23:41 - 23:41 (00:00)
root ssh:notty 203.0.113.44 Mon Aug 17 23:41 - 23:41 (00:00)
$ sudo lastb -s yesterday --time-format notime | awk '{print $3}' | sort | uniq -c | sort -rn
47 203.0.113.44
2 10.0.2.1
$ sudo journalctl -u ssh.service --since yesterday --until today --grep 'Failed password' | wc -l
49Failed attempts live in two complementary places: /var/log/btmp (binary, read with lastb, storing the user, terminal, IP and time) and ssh.service's journal, which additionally gives the exact message and allows text filtering. Forty-seven attempts from a single IP in one night is not an absent-minded user: it is a brute-force attack, and blocking it automatically is the subject of 06-03.
Conclusion
srv-tramontana now has a memory. You know that on Ubuntu two layers of logging coexist — journald, binary and structured, and rsyslog, in plain text — and why the journal forwards to syslog instead of replacing it; you handle syslog's eight severities and its facilities, including local0–local7 reserved for your own things; and you really do squeeze journalctl: by unit, live with -f, by time window with --since/--until, by priority with -p, by boot with -b -1, by kernel with -k, by structured fields such as _PID and _COMM, with --grep and with the short-iso, cat and json-pretty formats.
You have made the journal persistent — without that, a reboot destroys the evidence — and you have capped it in journald.conf with SystemMaxUse and MaxRetentionSec, knowing that rate limiting can swallow messages right in the middle of a storm of errors. You recognise the classic files in /var/log and you read wtmp and btmp with last and lastb. Your scripts write to the journal through logger from lib/common.sh, and tramontana-backup.service answers "was the backup taken last night?" all by itself.
And the debt from Module 4 is paid: /etc/logrotate.d/tramontana exists, it has been tested in simulation with logrotate -d, it rotates daily keeping fourteen days, it compresses, it recreates the file with 0640 svc-tramontana adm and it reloads the service in postrotate so that no process carries on writing into an orphaned inode. You know what must never end up in a log and that retention is a legal decision before it is a technical one.
The other half of Marta's question remains. You know what happened at three in the morning — fifteen db_timeouts with the 200 connections exhausted — but not why, nor whether the server was tight on CPU, memory or disk at that moment, nor what to compare today's readings against. In System Monitoring and Performance Tuning you will learn the USE method applied to the four resources, you will really interpret the load average, vmstat, free -h, iostat -xz and sar, you will know when the OOM killer has acted and why, you will establish a baseline for srv-tramontana, and you will apply a numbered diagnostic procedure to Marta's complaint that "the website is slow in the mornings" — where you will discover that the backup and those 200 connections have more to do with each other than they appear to.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
