In 03-06 I taught you ps, kill and nohup, and I told you in as many words that none of that is any use for managing a real service. In 03-07 you set up the nightly backup with cron and I warned you that something better existed. This is the lesson where both promises are settled, and it is the heart of the module. Today Tramontana Bookings is started by hand: if srv-tramontana reboots, the application does not come back; if process 1284 dies, nobody brings it up; deploy.sh changes the symbolic link and restarts nothing; and the 04:20 backup depends on a cron line with no real overlap control and no decent logging. By the end you will have tramontana.service written, hardened and enabled, and backup_tramontana.sh turned into a service with its timer.
Contents
- What an init system is and what systemd solves
- PID 1, cgroups and the unit types
systemctl: querying, controlling and enabling- Where the units live and what precedence they have
- Writing a
.serviceunit section by section - Hardening the unit and capping it with cgroups v2
- Targets and runlevels
- Timers: the replacement for cron
- Analysing the boot and launching transient jobs
- Tramontana case: the service, the timer and the complete deployment
- What an init system is and what systemd solves
The kernel boots, mounts the root filesystem and runs one process: init, with PID 1, from which everything else descends. Its job is to bring the system up, keep the services alive and shut it down in an orderly fashion.
| Aspect | SysV init | systemd |
|---|---|---|
| Boot | Sequential /etc/init.d/* scripts |
Parallel, guided by declared dependencies |
| Description | Shell code that reinvents starting, stopping and reloading | A declarative file of a few lines |
| Supervision and tracking | None; PID files that lie | Automatic restart with a policy and limits; cgroups, which the kernel does know about |
| Logging / tasks | Each one to its own file; cron separately | A structured journal (05-06); integrated timers |
| Isolation | None | Namespaces, ProtectSystem, capabilities |
The practical consequence of cgroups is enormous: if your application launches five children and one is orphaned, systemctl stop kills them all, because the kernel accounts for them in the same control group. With a PID file, that orphan carried on writing while you believed you had stopped the service.
- PID 1, cgroups and the unit types
$ ps -p 1 -o comm= ; systemd-cgls --no-pager | sed -n '4,5p'
systemd
│ ├─tramontana.service
│ │ └─1284 /opt/tramontana/app/bin/tramontana --config ...Everything in systemd is a unit, and the file's suffix says what type it is:
| Type | What it is for | Example |
|---|---|---|
.service |
A managed process: start, stop, supervise | tramontana.service |
.socket / .path |
Activates the service on the first connection / when a file changes | ssh.socket |
.target / .timer |
A synchronisation point grouping units / fires another unit at a given time | multi-user.target, tramontana-backup.timer |
.mount / .slice |
A mount point (one per fstab line) / a group that shares out CPU, memory and I/O |
srv-tramontana-backups.mount |
systemctl: querying, controlling and enabling
systemctl: querying, controlling and enabling$ systemctl status tramontana.service
● tramontana.service - Tramontana Bookings (web application)
Loaded: loaded (/etc/systemd/system/tramontana.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-08-18 04:31:02 CEST; 7h 12min ago
Main PID: 1284 (tramontana)
Tasks: 12 (limit: 4571)
Memory: 214.8M (max: 512.0M available: 297.1M)
CGroup: /system.slice/tramontana.service
└─1284 /opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
Aug 18 11:02:17 srv-tramontana tramontana[1284]: db_timeout after 30s (connections=200)You have to read the whole output:
Loaded: which file it comes from and whether it isenabled(it starts on its own when the machine boots) ordisabled. A service can beactiveanddisabled: it works now, but it will not come back after a reboot.Active:active (running)for a daemon,active (exited)for aoneshotthat finished cleanly,failedwith the exit code and the signal if it died.Tasks,Memory,CGroup: the cgroup's real consumption and the complete process tree, children included, not an estimate. The last lines are journal log, which you will squeeze in 05-06.
sudo systemctl start|stop|restart tramontana.service
sudo systemctl reload-or-restart tramontana.service # reloads (if there is ExecReload) or restarts
systemctl is-active|is-enabled|is-failed tramontana.service # scripts: code 0/3
systemctl --failed ; systemctl list-units --type=service --state=runningenable is literally a symbolic link
This is where the circle closes with 02-06:
$ sudo systemctl enable tramontana.service
Created symlink /etc/systemd/system/multi-user.target.wants/tramontana.service → /etc/systemd/system/tramontana.service.
$ sudo systemctl mask tramontana.service
Created symlink /etc/systemd/system/tramontana.service → /dev/null.enable creates the link inside the .wants of the target named in [Install] and disable deletes it: nothing more. That is why enable does not start the service (for that, enable --now) and disable does not stop it. mask links the unit to /dev/null, so it cannot be started at all — not by hand, not as a dependency, not by a socket — which is what you want during maintenance; unmask reverses it.
| State | Starts at boot? | By hand? |
|---|---|---|
enabled / disabled |
Yes / no | Yes in both cases |
masked / static |
No / only as a dependency (it has no [Install]) |
No, in no way / yes |
sudo systemctl daemon-reload is mandatory after creating or editing any unit file: if you skip it, systemd carries on working with the previous version in memory and you will go mad. And reloading the configuration does not restart the service: that is a separate restart.
systemctl cat tramontana.service # the effective unit, with its drop-ins
systemctl show tramontana.service -p Restart -p User -p MemoryMax
systemctl list-dependencies tramontana.service
sudo systemctl edit tramontana.service # creates a DROP-IN without touching the originaledit opens /etc/systemd/system/tramontana.service.d/override.conf, where you write only the section and the directives you want to change. A drop-in is better than editing the original unit: it survives an update of the package that brought it, it makes explicit what you changed and it is removed by deleting one file. A warning about list directives (ExecStart, Environment): to replace them you have to empty them first with a blank ExecStart=, or they accumulate.
- Where the units live and what precedence they have
In decreasing order of priority: /etc/systemd/system/ (where you write), /run/systemd/system/ (transient units, in RAM) and /lib/systemd/system/ (the packages' ones). Your units go in /etc/systemd/system/; the packages' units are not touched, they are adjusted with drop-ins. A file with the same name in /etc completely replaces the one in /lib.
- Writing a
.service unit section by section
.service unit section by section[Unit]: what it is and who it relates to
This is where everybody gets tangled up, because there are two independent axes: the ordering (when it starts relative to another unit) and the dependency (whether it needs that unit to exist).
| Directive | Axis | Meaning |
|---|---|---|
After= / Before= |
Ordering | "Start me after/before X", without requiring X to exist |
Wants= |
Weak dependency | It tries to start X; if X fails, I start anyway |
Requires= / BindsTo= |
Strong dependency | If X fails, I do not start; with BindsTo, I also stop if X stops |
Conflicts= |
Exclusion | X and I cannot be active at the same time |
The classic mistake is putting Requires=postgresql.service and believing it guarantees the database will be ready before you. It does not guarantee it: Requires without After starts both in parallel, so you need both directives. As a rule use Wants=: if the remote logging service fails, you would rather your application started anyway.
[Service]: how it runs
Type= |
When it is considered started | When to use it |
|---|---|---|
simple / exec |
When ExecStart is launched / when the execve() succeeds |
A foreground process; exec also detects a non-existent binary |
forking |
When the parent exits and leaves the child | Classic daemons; requires PIDFile= |
oneshot |
When the process finishes | Backups and migrations; with RemainAfterExit=yes it stays marked as active |
notify |
When the process reports via sd_notify() |
Services that know how to say "I am ready now" |
A modern service in Go, Java or Python is almost always simple or exec: do not send it to the background with & and do not use nohup, because systemd already takes care of that. The other day-to-day directives:
ExecStartPre=/usr/bin/test -r /etc/tramontana/app.conf # if it fails, it does not start
ExecStart=/opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
ExecReload=/bin/kill -HUP $MAINPID # what 'systemctl reload' does
ExecStop=/opt/tramontana/app/bin/tramontana --clean-shutdown
User=svc-tramontana
Group=tramontana
Environment=TRAMONTANA_ENVIRONMENT=production
EnvironmentFile=-/etc/tramontana/environment # the '-': if it does not exist, carry on
Restart=on-failure
RestartSec=5
StartLimitBurst=5
TimeoutStopSec=30
KillMode=mixedRestart=:no(the default),on-failure(a non-zero code or a fatal signal: the right option for an application),always,on-abnormal.RestartSec+StartLimitBurst+StartLimitIntervalSec: restarts after 5 s, and if it does so 5 times within 300 s it gives up and staysfailed. Without that limit, an application that will not start enters an infinite loop that eats CPU and fills the log; to bring it back,systemctl reset-failed.TimeoutStopSecandKillMode=mixed: how long to wait after the SIGTERM before the SIGKILL, and making sure the latter reaches the whole cgroup. It is the "SIGTERM → wait → verify →-9" sequence from 03-06, automated.
[Install]: when to start on its own
It usually has one line, WantedBy=multi-user.target, and it is what enable reads to know which .wants directory to create the link in. A unit with no [Install] cannot be enabled: it will show up as static.
- Hardening the unit and capping it with cgroups v2
It is one of the most valuable things about systemd, and it is free: a few lines leave your service with far less attack surface than you would achieve by hand.
| Directive | What it does |
|---|---|
NoNewPrivileges=yes |
The process cannot gain privileges: it cancels SUID and the setcap from 05-02 |
PrivateTmp= / PrivateDevices= / ProtectHome= / ProtectKernelTunables= |
A /tmp of its own; only basic devices; /home and /root invisible; /proc/sys read-only |
ProtectSystem=strict + ReadWritePaths= |
The whole FS read-only except the minimal list you open up |
RestrictAddressFamilies= |
Which socket families it may use (AF_INET AF_INET6 AF_UNIX) |
CapabilityBoundingSet= / AmbientCapabilities= |
The capability ceiling (empty = none) / the ones actually granted, e.g. CAP_NET_BIND_SERVICE |
LockPersonality=yes, MemoryDenyWriteExecute=yes |
They close known exploitation routes |
systemd-analyze security tramontana.service scores the result: the unit we will write at the end of the lesson gets 2.9 OK, against the 9.6 UNSAFE it got before hardening. A practical note: ProtectSystem=strict will break your service the first time, because it writes somewhere you have not declared. Do not disable it: look at the log, add the path to ReadWritePaths and try again. That exercise forces you to know exactly where your application writes.
The resource limits, which systemd applies with cgroups v2, belong to the same [Service] section:
MemoryMax=512M # hard: on exceeding it, the OOM killer acts INSIDE the service
MemoryHigh=384M # from here on it is pressured to release, without being killed
CPUQuota=150% # at most 1.5 of the 2 cores; TasksMax=64 contains a fork bomb
IOWeight=50 # relative I/O priority$ systemd-cgtop --iterations=1 | sed -n '3p'
system.slice/tramontana.service 12 2.8 214.8M 12.0K 840.0KThat MemoryMax stops a memory leak taking the database and SSH itself down with it: on exceeding it, the kernel kills inside the service's cgroup and Restart=on-failure brings it back up.
- Targets and runlevels
| Target | What it is |
|---|---|
multi-user.target (runlevel 3) |
A complete networked system with no graphical environment: the normal one on a server; graphical.target adds the desktop |
rescue.target / emergency.target |
Single user with local filesystems / just a shell with / read-only: where a broken fstab sends you |
There is also network-online.target, the synchronisation point that says usable networking exists. systemctl get-default says which is the default one, set-default changes it and sudo systemctl isolate rescue.target switches target right now, cutting off the services that are not needed.
- Timers: the replacement for cron
A timer is a unit that activates another one: by convention, tramontana-backup.timer fires tramontana-backup.service (the same name, a different suffix).
| Aspect | cron | systemd timer |
|---|---|---|
| Syntax | Five cryptic fields | A readable, verifiable OnCalendar= |
| A missed run (machine powered off) | It is lost | Persistent=true runs it at the next boot |
| Logging and environment | Whatever you redirect; a minimal PATH |
The journal (journalctl -u); the unit's environment |
| Overlap and dependencies | flock by hand; no dependencies |
Not relaunched if it is still active; After=, Requires= and the hardening from section 6 |
| Seeing what is scheduled | crontab -l per user |
systemctl list-timers, global |
# /etc/systemd/system/tramontana-backup.timer
[Unit]
Description=Daily backup of Tramontana Bookings
[Timer]
OnCalendar=*-*-* 04:20:00
Persistent=true
RandomizedDelaySec=180
Unit=tramontana-backup.service
[Install]
WantedBy=timers.targetAlways verify the expression before trusting it:
$ systemd-analyze calendar '*-*-* 04:20:00'
Next elapse: Wed 2026-08-19 04:20:00 CEST (From now: 17h left)Other ways of firing: OnBootSec=5min (after the machine boots), OnUnitActiveSec=1h (every hour since the last run) and OnStartupSec=. And readable expressions: daily, weekly, Mon..Fri 08:00, *-*-01 03:00 (the 1st of every month).
$ systemctl list-timers --all | tail -1
Wed 2026-08-19 04:20:00 CEST 17h left Tue 2026-08-18 04:21:43 CEST tramontana-backup.timerPersistent=true is the main reason to migrate from cron: if the server was powered off at 04:20, the backup runs as soon as it boots instead of being lost until the following day.
- Analysing the boot and launching transient jobs
$ systemd-analyze
Startup finished in 3.412s (kernel) + 11.807s (userspace) = 15.219s
$ systemd-analyze blame | head -1
6.104s [email protected]
$ systemd-analyze critical-chain tramontana.service | head -3
tramontana.service +412ms
└─postgresql.service @5.201s +6.104s
$ sudo systemd-run --unit=purge --property=MemoryMax=256M /home/operator/scripts/purge_releases.sh --dry-runblame sorts by duration; critical-chain shows the chain that really delays the start of one particular unit, which is what matters: a slow service nobody depends on delays nothing. And systemd-run launches a one-off as a transient unit, recorded in the journal and with its limits applied, unlike a nohup.
- Tramontana case: the service, the timer and the complete deployment
tramontana.service
# /etc/systemd/system/tramontana.service
[Unit]
Description=Tramontana Bookings (web application)
Documentation=https://intranet.tramontana.example/runbook
Wants=network-online.target
After=network-online.target postgresql.service
[Service]
Type=exec
User=svc-tramontana
Group=tramontana
UMask=0027
WorkingDirectory=/opt/tramontana/app
Environment=TRAMONTANA_ENVIRONMENT=production
ExecStartPre=/usr/bin/test -r /etc/tramontana/app.conf
ExecStart=/opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StartLimitBurst=5
TimeoutStopSec=30
KillMode=mixed
# Hardening
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log/tramontana /opt/tramontana/shared/uploads
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
CapabilityBoundingSet=
MemoryMax=512M
[Install]
WantedBy=multi-user.targetDecisions worth understanding: Type=exec because the application stays in the foreground and that way a non-existent binary is detected at start-up; User=svc-tramontana with Group=tramontana, the identities from 05-01, and UMask=0027 by the course's convention; ReadWritePaths with only the log and the uploads, because everything else — /opt/tramontana included — must be immutable as far as the application is concerned; and an empty CapabilityBoundingSet= because it listens on 8080 (if it moved to 80 you would add AmbientCapabilities=CAP_NET_BIND_SERVICE, replacing the setcap from 05-02, which was lost on every deployment).
$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana.service
$ systemctl is-active tramontana.service && curl -sf -o /dev/null -w '%{http_code}\n' http://10.0.2.15:8080/health
active
200The backup timer
# /etc/systemd/system/tramontana-backup.service
[Unit]
Description=Backup of Tramontana Bookings
RequiresMountsFor=/srv/tramontana/backups
[Service]
Type=oneshot
User=operator
Group=tramontana
UMask=0027
ExecStart=/home/operator/scripts/backup_tramontana.sh -q
TimeoutStartSec=30min
Nice=10
IOSchedulingClass=idle
ProtectSystem=strict
ReadWritePaths=/srv/tramontana/backups /var/log/tramontanaType=oneshot because the script finishes, and no [Install] because it is not enabled: the timer fires it, and that is why it will show up as static. RequiresMountsFor= is this unit's jewel: if the LVM volume from 05-04 is not mounted, the backup does not run instead of merrily writing into the empty directory on /, a silent failure you would only discover on the day of the restore. Nice=10 and IOSchedulingClass=idle make the backup give way to the application, something that will turn out to matter in 05-07.
$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana-backup.timer
$ sudo systemctl start tramontana-backup.service # test it NOW, without waiting for 04:20
$ systemctl status tramontana-backup.service | sed -n '3p'
Active: inactive (dead) since Tue 2026-08-18 11:41:09 CEST; 8s ago
$ sudo sed -i.bak-$(date +%F) '/backup_tramontana.sh/s/^/#MIGRATED TO TIMER /' /etc/cron.d/tramontanainactive (dead) after a oneshot is success: the script finished with code 0; a failed would show the exit code you defined in 04-03. And that final sed withdraws the cron line, which is the half of the job people forget: leaving both things active means two simultaneous backups fighting over the flock every night.
deploy.sh finally restarts
The debt from Module 4: after the atomic ln -sfn, the script checked the health against a process still running the previous release, because the binary was already loaded in memory. Now:
ln -sfn "releases/${version}" "${TRAMONTANA_BASE}/app.new"
mv -T "${TRAMONTANA_BASE}/app.new" "${TRAMONTANA_BASE}/app"
sudo systemctl restart tramontana.service || die 74 "systemctl restart failed"
for _ in 1 2 3 4 5 6; do # wait until it is up before measuring the health
systemctl is-active --quiet tramontana.service && break; sleep 2
done
if ! curl -fsS --max-time 5 "http://127.0.0.1:8080/health" >/dev/null; then
error "version ${version} is not responding; rolling back"
ln -sfn "releases/${previous}" "${TRAMONTANA_BASE}/app.new"
mv -T "${TRAMONTANA_BASE}/app.new" "${TRAMONTANA_BASE}/app"
sudo systemctl restart tramontana.service
die 75 "rollback to ${previous} completed"
fiThe sudo systemctl restart works without an interactive password thanks to the /etc/sudoers.d/tramontana rule you wrote in 05-02: this is where the three lessons fit together. And systemctl is-active --quiet in a loop avoids the classic mistake of checking the health before the process has opened the port.
Common Mistakes and Tips
- Forgetting
daemon-reload. You edit the unit, restart and nothing changes: if you have touched a unit file,daemon-reloadbefore anything else. Andenabledoes not start the service, it only creates the link: useenable --now. Requires=withoutAfter=. It guarantees no ordering at all: both units start in parallel. PreferWants=unless you genuinely cannot function without the other one. And when you migrate to a timer, comment out the cron line in the same change or you will have two simultaneous runs.- Sending the process to the background with
&ornohupinExecStart. systemd loses sight of it and considers it dead: let it run in the foreground. Restart=alwayswithoutStartLimitBurst. An application that will not start enters a loop that eats CPU and fills the disk with logs. And do not give in toProtectSystem=strict: the failure tells you where your application writes, so add the path toReadWritePathsinstead of removing the protection.- Editing a package's unit in
/lib/systemd/system. The next update carries off your work: use a drop-in in/etc/systemd/system/<unit>.d/. - Tip: keep your units in git alongside the scripts. A unit is production code and is reviewed as such.
Exercises
- Diagnosing a service that will not start.
tramontana.serviceisfailed. List, in order, the five commands you would run to find the cause, and say what each one tells you. - A timer with recovery. Write the
.service+.timerpair that runs/home/operator/scripts/purge_releases.shevery Monday at 05:10, recovers if the server was powered off, does not overlap with the backup and is logged. Verify the calendar expression. - A drop-in instead of an edit. The supplier asks that Tramontana start with
TRAMONTANA_MAX_THREADS=8and a 768 MiB memory limit, without modifyingtramontana.service. Do it and demonstrate that it has taken effect.
Solutions
1.
systemctl status tramontana.service # 1. State, exit code and last lines
journalctl -u tramontana.service -n 50 # 2. The complete log of the failed start
systemctl cat tramontana.service # 3. The EFFECTIVE unit, with its drop-ins
sudo -u svc-tramontana /opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
systemd-analyze verify /etc/systemd/system/tramontana.service # 5. Syntax and referencesThe fourth command launches the application by hand as its user: is it the app that is failing, or the unit?
It is the step that saves the most time. If it works by hand and not through systemd, the culprit is usually the hardening — a path missing from ReadWritePaths — or the environment, because the unit does not inherit your variables.
2.
# /etc/systemd/system/tramontana-purge.service
[Unit]
Description=Purge of old Tramontana releases
After=tramontana-backup.service
Conflicts=tramontana-backup.service
[Service]
Type=oneshot
User=operator
Group=tramontana
ExecStart=/home/operator/scripts/purge_releases.sh
TimeoutStartSec=15min
ProtectSystem=strict
ReadWritePaths=/opt/tramontana/releases /var/log/tramontana
# /etc/systemd/system/tramontana-purge.timer
[Unit]
Description=Weekly purge of releases
[Timer]
OnCalendar=Mon 05:10
Persistent=true
RandomizedDelaySec=300
Unit=tramontana-purge.service
[Install]
WantedBy=timers.target$ systemd-analyze calendar 'Mon 05:10' | grep 'Next elapse'
Next elapse: Mon 2026-08-24 05:10:00 CEST
$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana-purge.timerConflicts= plus After= guarantee they do not coincide, the logging is automatic in the journal and there is no need for flock: systemd does not relaunch a unit that is already active.
3. With sudo systemctl edit tramontana.service, which creates the drop-in:
### /etc/systemd/system/tramontana.service.d/override.conf
[Service]
Environment=TRAMONTANA_MAX_THREADS=8
MemoryMax=768M$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ systemctl show tramontana.service -p MemoryMax -p Environment
MemoryMax=805306368
Environment=TRAMONTANA_ENVIRONMENT=production TRAMONTANA_MAX_THREADS=8systemctl show gives the effective value (in bytes) and systemctl cat would show the drop-in at the end of the unit without the original having been touched. And Environment= is cumulative: the new variable is added to the ones the unit already defined.
Conclusion
srv-tramontana now governs itself. You know what an init is and what systemd contributes over SysV — parallel dependency-driven boot, real supervision, cgroups that do not lie, structured logging and isolation —; you know the unit types and you handle systemctl from end to end: you read a status line by line, you control with start/restart/reload, you tell enabled from active and disable from mask, you know that enable is literally creating a symbolic link and that daemon-reload is mandatory after touching any unit file. You adjust with drop-ins instead of editing other people's units.
You write a unit from scratch and you understand the two things everybody trips over: the difference between ordering (After/Before) and dependency (Wants/Requires/BindsTo), and which Type= matches each way of starting. You harden with NoNewPrivileges, ProtectSystem=strict and ReadWritePaths, you check it with systemd-analyze security, you cap it with MemoryMax, CPUQuota and TasksMax while watching it with systemd-cgtop, you handle targets, you analyse the boot with blame and critical-chain and you launch transient jobs with systemd-run. And the debts are settled: tramontana.service exists, hardened, with automatic restart and limits, and it starts on its own after a server reboot; backup_tramontana.sh is now tramontana-backup.service fired by a timer at 04:20 with Persistent=true and RequiresMountsFor on the volume from 05-04, with the cron line already withdrawn; and deploy.sh does the systemctl restart it was missing, leaning on the sudoers rule from 05-02. What is missing is the system's memory. Your service already writes to the journal, but you do not know how to query it; /var/log/tramontana/access.log is 412 lines long and growing with nobody rotating it, which was the first debt I announced when closing Module 4 and is still unpaid; and when Marta asks "what happened last night at 03:00?", right now you would not be able to answer precisely. In System Logs: journald and syslog you will see the two logging layers that coexist on Ubuntu, you will squeeze journalctl with all its filters, you will make the journal persistent, you will write a correct /etc/logrotate.d/tramontana tested in simulation, you will connect lib/common.sh to the journal via logger, and you will learn what must never end up written in a log.
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
