In the previous lesson you put a lock on the front door. But srv-tramontana still has all its windows open: anybody who reaches the machine can talk to the application on 8080, and if tomorrow PostgreSQL listened on 0.0.0.0 instead of on localhost, to 5432 as well. The bot at 203.0.113.44 keeps calling 47 times a night and nobody is stopping it.
A firewall answers a question that comes before authentication: who is even entitled to speak to this server, and on which port? By the end of this lesson you will have a written and applied policy — only rate-limited SSH and HTTPS from outside, PostgreSQL only from the internal network, 8080 closed to the outside world — you will understand what lies underneath ufw, you will know how to write an nftables ruleset from scratch and you will have fail2ban automatically blocking whoever persists. And all of it without losing your SSH session, because the golden rule does not change: never close the door you are coming in through.
Contents
- What a firewall is and what it is not
- The Linux model: netfilter and its front ends
- Tables, chains, hooks and connection state
ufwin practice- Application profiles
- When
ufwis not enough:nftablesdirectly iptables→nftablesequivalences- Outbound rules and why almost nobody writes them
- Network
sysctlsettings with an impact on security fail2ban: from the log to the automatic block- Verifying from outside with
nmap - The Tramontana case: the policy and the report to Marta
- What a firewall is and what it is not
A firewall decides which packets enter, leave or cross a machine, according to rules you write. That is all it does, and it is worth being very clear about it:
| A firewall does | A firewall does not |
|---|---|
| Reduce the surface exposed to the network | Fix a badly configured service |
| Limit who can attempt to authenticate | Prevent an attack through the port you do leave open |
| Slow down scans and automated noise | Detect that they are already inside (that is 06-04) |
| Contain lateral damage after a compromise | Replace security updates |
Defence in depth consists precisely of not depending on a single layer: if an attacker gets past the firewall, they run into SSH with no passwords; if they get past SSH, they run into svc-tramontana with no shell and with ProtectSystem=strict; if they reach the disk, they run into the encrypted secrets from 06-05. No layer is sufficient on its own, and that is the whole idea.
- The Linux model: netfilter and its front ends
There is a single filtering engine in the kernel, netfilter. Everything else is a way of writing rules for it:
| Layer | What it is | Its status in Ubuntu 24.04 |
|---|---|---|
| netfilter | The engine, inside the kernel | What actually filters |
nftables |
The current language and the nft tool |
The native option: one command for IPv4, IPv6, ARP and bridge |
iptables |
The historical interface | Present as iptables-nft: it translates to nftables underneath |
ufw |
Uncomplicated Firewall, Ubuntu's front end | Installed by default, it writes nftables rules |
firewalld |
A front end with zones, typical of RHEL | Available, not common here |
The practical consequence: on Ubuntu 24.04, when you write ufw allow 22/tcp, ufw generates rules that end up in the same nftables engine you would see with nft list ruleset. They are not rival systems: they are two levels of abstraction over the same thing.
And the warning that saves whole afternoons: use one or the other, not both at once. If you enable ufw and also enable the nftables service with your own /etc/nftables.conf, you will have two competing sets of rules, a flush ruleset that wipes ufw's at every boot and behaviour that is impossible to reason about.
- Tables, chains, hooks and connection state
Four concepts and you can already read any ruleset:
- Table: the container. In
nftablesit is declared with a family:ip(IPv4),ip6,inet(both at once, which is what you will want almost always),arp,bridge,netdev. - Chain: an ordered set of rules hooked onto a kernel hook. Three matter:
input(packets destined for this machine, where you write nearly everything),output(packets it generates itself, the egress of section 8) andforward(packets passing through it, only if it is a router or a VPN → 08-04). - Default policy: what happens to a packet that matches no rule. There are only two philosophies, and one of them is the right one:
policy drop(an allowlist: everything is forbidden and what is needed is permitted) versuspolicy accept(a blocklist: everything is permitted and known bad things are forbidden). The blocklist is impossible to maintain, because it requires knowing in advance everything that can go wrong. - Connection state: netfilter remembers connections in progress (connection tracking). That lets you write
ct state established,related acceptand forget about return traffic.
That last point is the one that changes everything. Without stateful filtering you would have to open the high ports for the replies by hand, which amounts to filtering nothing at all. With state the logic is beautifully clean: new is a connection's first packet and that is where the decision is made; established belongs to a connection already accepted and is accepted without a second look; related is a secondary connection of another one (ICMP errors, FTP data) and is accepted too; and invalid matches no known connection, so it is dropped.
ufw in practice
ufw in practiceufw is what you will use 95% of the time. Always start by measuring:
The correct order of operations, and there is no other way of getting it right on a remote machine:
# 1. Default policies: deny incoming, allow outgoing
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw default deny routed
# 2. FIRST allow SSH. Without this, step 4 locks you out.
$ sudo ufw limit 22/tcp comment 'SSH with attempt limiting'
# 3. The remaining services
$ sudo ufw allow 443/tcp comment 'Public HTTPS'
$ sudo ufw allow from 10.0.2.0/24 to any port 5432 proto tcp comment 'PostgreSQL internal network'
# 4. NOW you can enable it
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startupThat warning in step 4 is not decorative: if you run ufw enable with the deny incoming policy and without having allowed SSH, the session is cut off there and then and the only way back to the machine is the VM console. Have that console open before you start.
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp LIMIT IN Anywhere # SSH with attempt limiting
443/tcp ALLOW IN Anywhere # Public HTTPS
5432/tcp ALLOW IN 10.0.2.0/24 # PostgreSQL internal network
22/tcp (v6) LIMIT IN Anywhere (v6)Four details that matter in this output:
ufwautomatically creates the equivalent IPv6 rule. That is exactly why you do not disable IPv6 "just in case": here it is covered.LIMITis notALLOW:ufw limitblocks an IP that opens 6 or more connections in 30 seconds. It is a cheap defence against brute force, althoughfail2ban(section 10) is far more precise.- The
commentis living documentation, anddeny (routed)makes it clear that this machine forwards nothing, consistent with theip_forward = 0of 06-01.
deny versus reject, which is a real difference and not a nuance:
| Action | What the kernel does | What the caller sees | When to use it |
|---|---|---|---|
deny (DROP) |
Discards silently | A timeout: they do not know whether the machine exists | Facing the Internet: it costs the scanner time |
reject |
Replies with ICMP port unreachable or a TCP RST | An immediate "closed" | The internal network: it saves your applications 30-second waits |
Managing existing rules means seeing them numbered, because order matters: the first one that matches wins.
$ sudo ufw status numbered
To Action From
-- ------ ----
[ 1] 22/tcp LIMIT IN Anywhere
[ 2] 443/tcp ALLOW IN Anywhere
[ 3] 5432/tcp ALLOW IN 10.0.2.0/24
$ sudo ufw delete 3
$ sudo ufw insert 1 deny from 203.0.113.44 comment 'manual bot block'Careful: when you delete rule 3, the following ones are renumbered. Always delete from highest to lowest or, better still, delete by specification (sudo ufw delete allow 443/tcp), which is idempotent and does not depend on numbers.
- Application profiles
ufw ships named profiles in /etc/ufw/applications.d/, which translate "OpenSSH" into "port 22/tcp":
$ sudo ufw app list
Available applications:
Nginx Full
OpenSSH
$ sudo ufw app info OpenSSH
Profile: OpenSSH — Ports: 22/tcp
$ sudo ufw allow OpenSSHAnd you can write your own, which is the clean way to document your application's ports:
[Tramontana]
title=Tramontana Bookings
description=Bookings web application (reverse proxy in 08-01)
ports=443/tcpSave it in /etc/ufw/applications.d/tramontana and reload it with sudo ufw app update Tramontana: if the port changes tomorrow, it changes in a single place.
- When
ufw is not enough: nftables directly
ufw is not enough: nftables directlyufw falls short when you need fine-grained rate limiting, address sets (set), packet marking, elaborate NAT or simply to read exactly what is there. Then you write nftables by hand, in /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set internal_networks {
type ipv4_addr ; flags interval ; elements = { 10.0.2.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
# Return traffic and rubbish: first of all, for efficiency
ct state established,related accept
ct state invalid drop
# Loopback: without this half the system and the SSH tunnels break
iif lo accept
# Essential ICMP (do not block ICMP wholesale: it breaks MTU discovery)
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
ip6 nexthdr ipv6-icmp accept
# SSH with rate limiting on new attempts, and public HTTPS
tcp dport 22 ct state new limit rate 6/minute burst 6 packets accept
tcp dport 443 accept
# PostgreSQL only from the internal network
ip saddr @internal_networks tcp dport 5432 accept
# Everything else falls to the policy, leaving a bounded trail
limit rate 5/minute log prefix "nft-drop-in: " level info
counter
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}Notes on the design, which is what you are here to learn:
table inetcovers IPv4 and IPv6 with the same rules. Withiptablesyou would have to duplicate everything inip6tables, and that is where people leave holes.policy droponinputandforward,acceptonoutput: an allowlist where it matters.- The order is deliberate: the most frequent at the top (
established), the exceptional at the bottom. Every packet walks the chain until it finds its rule. - Do not block all ICMP.
destination-unreachablecarries MTU discovery: without it you will have connections that hang when transferring large files and you will go mad looking for the cause. - The final
logcarries its ownlimit rate, because a scan can generate thousands of lines a second and fill your disk. Logging what is dropped is what makes a firewall diagnosable.
$ sudo nft -c -f /etc/nftables.conf && echo "syntax OK"
syntax OK
$ sudo systemctl enable --now nftables
$ sudo nft list ruleset | head -2
table inet filter {
set internal_networks {nft -c checks without applying: it is the netplan generate of firewalls, and it is always used. Persistence comes from the nftables service, which runs /etc/nftables.conf at every boot; without it, nft is as volatile as ip addr add.
iptables → nftables equivalences
iptables → nftables equivalencesThere are still mountains of documentation written in iptables. This table lets you read it:
iptables |
nftables |
|---|---|
-A INPUT -p tcp --dport 22 -j ACCEPT |
tcp dport 22 accept |
-P INPUT DROP |
policy drop in the chain definition |
-m state --state ESTABLISHED,RELATED -j ACCEPT |
ct state established,related accept |
-s 10.0.2.0/24 / -i lo -j ACCEPT |
ip saddr 10.0.2.0/24 / iif lo accept |
-j REJECT --reject-with icmp-port-unreachable |
reject with icmpx type port-unreachable |
iptables -L -n -v / iptables-save |
nft list ruleset |
Separate rules in iptables/ip6tables |
A single one in table inet |
- Outbound rules and why almost nobody writes them
Almost everybody writes output policy accept and forgets about it. It is convenient and it is a missed opportunity: egress rules do not stop people getting in, but they hugely limit what an attacker can do afterwards. Without free outbound access, they cannot download their second stage from an external server, they cannot exfiltrate bookings.csv to some arbitrary host and they cannot join a botnet.
chain output {
type filter hook output priority 0; policy drop;
ct state established,related accept
oif lo accept
udp dport { 53, 123 } accept # DNS and NTP
tcp dport { 53, 80, 443 } accept # DNS/TCP and apt repositories
ip daddr 10.0.2.0/24 accept # internal network
limit rate 5/minute log prefix "nft-drop-out: "
}Why almost nobody writes them, honestly: they break things in subtle, hard-to-diagnose ways. An apt update against a new mirror, a webhook, a certificate renewal... everything fails with timeouts nobody connects to the firewall. If you adopt them, do it by measuring first which outbound traffic the machine really uses (with logging on output and the policy at accept for a week) and only then changing the policy to drop.
- Network
sysctl settings with an impact on security
sysctl settings with an impact on securityThese parameters are not about performance — that is 07-03 — but about how the network stack behaves in the face of abuse:
$ sudo tee /etc/sysctl.d/60-network-security.conf > /dev/null <<'CONF'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
CONF
$ sudo sysctl --system | grep -A8 60-network-security| Parameter | What it prevents |
|---|---|
rp_filter = 1 |
Packets with a forged source that would not come back through the same interface (antispoofing) |
accept_redirects = 0 / send_redirects = 0 |
Somebody else's ICMP redirect rewriting your routing table, and you reporting topology to third parties |
accept_source_route = 0 |
Packets that dictate their own path, a classic evasion technique |
tcp_syncookies = 1 |
A SYN flood exhausting the half-open connection queue |
icmp_echo_ignore_broadcasts = 1 |
Being an amplifier for a smurf attack |
log_martians = 1 |
Logs impossible packets: an early sign that something is odd |
Careful with rp_filter = 1 (strict): on a machine with several interfaces and asymmetric routes it drops legitimate traffic. On srv-tramontana, with a single interface, it is safe; on a router or a machine with a VPN, use 2 (loose mode).
fail2ban: from the log to the automatic block
fail2ban: from the log to the automatic blockfail2ban closes the loop: it reads the logs, detects patterns of abuse and asks the firewall to block the IP for a while. It does not replace the firewall, it drives it.
$ sudo apt install -y fail2ban
$ sudo tee /etc/fail2ban/jail.local > /dev/null <<'CONF'
[DEFAULT]
# NEVER block yourself: internal network and the team's laptops
ignoreip = 127.0.0.1/8 ::1 10.0.2.0/24
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = ufw
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 24h
CONF
$ sudo systemctl enable --now fail2ban
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Total failed: 47
| `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
|- Currently banned: 1
`- Banned IP list: 203.0.113.44You edit jail.local, never jail.conf: the latter belongs to the package and the next update sweeps your changes away. The three figures that govern everything:
| Parameter | Meaning | Chosen value |
|---|---|---|
maxretry / findtime |
Failures tolerated and the window in which they are counted | 3 in 10 minutes |
bantime |
How long the block lasts | 24 h (-1 would be permanent) |
banaction |
How it blocks | ufw, so as not to create a third set of rules |
ignoreip |
Who is immune | Your network and your laptops |
ignoreip is the protection against the most humiliating mistake of all: mistyping your sudo password three times and having your own server block you. If it happens anyway:
$ sudo fail2ban-client set sshd unbanip 10.0.2.77
$ sudo fail2ban-client set sshd banip 203.0.113.44And the honesty this deserves: with PasswordAuthentication no from 06-02, fail2ban no longer protects you from anybody guessing anything, because there is nothing to guess. What it does is reduce noise, resource use and surface: fewer connections, fewer logs, fewer opportunities in the face of a future sshd bug. It is a hygiene measure, not the one that saves you.
- Verifying from outside with
nmap
nmapA firewall is not verified until you look at it from outside. From laptop-student, against your own VM:
$ nmap -Pn -p 22,443,5432,8080 10.0.2.15
Nmap scan report for srv-tramontana (10.0.2.15)
PORT STATE SERVICE
22/tcp open ssh
443/tcp open https
5432/tcp open postgresql
8080/tcp filtered http-proxyHow to read it: open answers, closed answers that nobody is there, and filtered means something is silently dropping the packet — precisely the effect of your deny policy. Port 8080 shows as filtered: objective achieved. 5432 comes out open because the scan comes from 10.0.2.77, which is inside the permitted internal network; repeated from outside that range it would come out filtered.
Legal warning, with no qualifications: scanning the ports of systems that are not yours without written authorisation is illegal in Spain and in practically every jurisdiction, and it can constitute a criminal offence even if you cause no damage. In this course every scan is run from laptop-student against srv-tramontana, both of which are yours and inside your lab. A tool being legal does not make every use of it legal: the difference between an audit and an attack is permission, and permission is documented in writing before you type anything.
- The Tramontana case: the policy and the report to Marta
The policy ends up like this, and it is decided before any command is touched:
| Service | Port | From where | Reason |
|---|---|---|---|
| SSH | 22/tcp | Anywhere, with limit |
Administration; hardened in 06-02 |
| HTTPS | 443/tcp | Anywhere | The public face; the proxy arrives in 08-01 |
| PostgreSQL | 5432/tcp | Only 10.0.2.0/24 |
Never from the Internet |
| Application | 8080/tcp | Nobody | It will sit behind the reverse proxy (08-01) |
| Everything else | — | Denied | It is an allowlist |
Diagnosis when "the service does not answer" and the cause is you: first look at whether the service is listening (ss -tulpn | grep 8080), then at whether the firewall is dropping it.
$ sudo journalctl -k --since "-15m" | grep 'UFW BLOCK' | tail -2
Aug 18 19:22:41 srv-tramontana kernel: [UFW BLOCK] IN=enp0s3 SRC=203.0.113.44 DST=10.0.2.15 PROTO=TCP SPT=44210 DPT=8080 SYN
Aug 18 19:23:02 srv-tramontana kernel: [UFW BLOCK] IN=enp0s3 SRC=198.51.100.9 DST=10.0.2.15 PROTO=TCP SPT=51882 DPT=23 SYNRead it from left to right: it came in through enp0s3, from SRC, towards DPT (the destination port). The first line is the bot looking for the application on 8080; the second, somebody trying Telnet in 2026. If the thing showing up blocked is your own legitimate service, you already know which rule is missing.
The report to Marta, following the course's structure — what it protects and what it does not:
Firewall applied on srv-tramontana on 18/08. What it protects: only SSH (with attempt limiting) and HTTPS are reachable from outside; the database only accepts connections from the internal network; the application's port 8080 is no longer reachable from the Internet; the IP 203.0.113.44, with 47 access attempts, is automatically blocked for 24 hours by fail2ban, and so will any other that tries. What it does NOT protect: it does not defend against a flaw in the web application itself, which is still reachable through the port that has to be open; it prevents nothing for anybody holding valid credentials; it does not detect that somebody is already inside (that is covered in the detection review); and it does not encrypt the traffic, which still travels in the clear until we install the certificate. Outstanding: TLS encryption and getting the database password out of the configuration file.
Security and compliance warning: in a real environment, any change to the firewall rules of a system that processes personal data must be planned with a maintenance window, be documented and be reviewed by the security officer; the exposure of services to the Internet is part of the risk analysis required by the GDPR. Scanning and testing techniques are applied exclusively to systems that are your own or with written authorisation.
Common Mistakes and Tips
ufw enablebefore allowing SSH. The classic mistake and the most expensive one. Always allow first, enable afterwards, and with the VM console open.- Mixing
ufwand/etc/nftables.conf. Two competing sets of rules and aflush rulesetthat wipes the other one at every boot. Pick one. - Blocking all ICMP. It breaks MTU discovery and causes connections that hang on large transfers. Allow at least
destination-unreachableandtime-exceeded. - Forgetting
iif lo accept. Half the machine stops working: SSH tunnels, databases over a local TCP socket, processes that talk to each other over127.0.0.1. - Rules with no
comment. In six months' time nobody will dare delete a rule when they do not know what it is for, and the ruleset will only grow. logwithoutlimit rate. A scan fills/var/login minutes, and running out of disk is a self-inflicted outage. And do not rely on the firewall alone for internal traffic: the application should be listening only on127.0.0.1anyway.- Tip: keep
/etc/nftables.confor the output ofufw status numberedin git, and add a check that the firewall is active tohealth_check.sh.
Exercises
- The full policy from scratch. Write the exact sequence of
ufwcommands to apply the policy in the table in section 12 on a remote machine, in the right order, including the checks before and after. - A rule
ufwdoes not express well. Marta asks that onlylaptop-luis(10.0.2.30) should be able to reach 8080 for testing, with a maximum of 10 new connections per minute. Write thenftablesrule and explain whyufwfalls short. - Self-inflicted block. Luis calls: he cannot get in over SSH from 10.0.2.77 and says "the server has thrown him out". Diagnose the problem, resolve it and propose the permanent fix.
Solutions
1. With the VM console open and a second SSH session active:
# Measure first
$ sudo ufw status verbose > /root/ufw-before.txt ; ss -tulpn | grep LISTEN
# Default policies
$ sudo ufw default deny incoming && sudo ufw default allow outgoing
$ sudo ufw default deny routed
# SSH FIRST, always; then the rest
$ sudo ufw limit 22/tcp comment 'SSH administration'
$ sudo ufw allow 443/tcp comment 'Public HTTPS'
$ sudo ufw allow from 10.0.2.0/24 to any port 5432 proto tcp comment 'PostgreSQL internal'
# Enable and verify
$ sudo ufw enable && sudo ufw status numbered
$ sudo diff -u /root/ufw-before.txt <(sudo ufw status verbose)And the verification that really closes the job, from laptop-student: open a new SSH session without closing the previous one, and run nmap -Pn -p 22,443,5432,8080 10.0.2.15 to check that 8080 shows as filtered. Notice that 8080 needs no rule at all: the deny incoming policy already covers it. In an allowlist, whatever is not named is forbidden.
2. The rule in nftables, inside the input chain and before the final log:
ufw falls short for two reasons: it only offers limit, with a fixed threshold (6 connections in 30 seconds) that cannot be adjusted without editing its internal templates, and it does not allow you to combine a specific source, a port, a connection state and a custom rate in a single rule. Here we need exactly that. The alternative within ufw would be sudo ufw allow from 10.0.2.30 to any port 8080 proto tcp, which gives you the source control but not the rate limiting.
An important detail: ct state new makes the rate apply only to new connections. Without it, you would also be limiting the packets of a transfer in progress and causing random cut-offs that would look like a network problem.
3. Diagnosis in three commands:
$ sudo fail2ban-client status sshd | grep 'Banned IP list'
`- Banned IP list: 203.0.113.44 10.0.2.77
$ sudo journalctl -u fail2ban --since "-1h" | grep 10.0.2.77
fail2ban.actions [1204]: NOTICE [sshd] Ban 10.0.2.77Luis has failed three times in ten minutes — probably with the wrong key, or without having loaded it into the agent — and the sshd jail has blocked him for 24 hours. Immediate unblocking and permanent fix:
$ sudo fail2ban-client set sshd unbanip 10.0.2.77
$ grep ignoreip /etc/fail2ban/jail.local
ignoreip = 127.0.0.1/8 ::1 10.0.2.0/24And here is the real lesson: ignoreip already included 10.0.2.0/24, so if the block happened it is because the file was edited without reloading the service, or because the line was written in jail.conf instead of in jail.local and a package update carried it away. The permanent fix is sudo fail2ban-client reload after every change, verifying with sudo fail2ban-client get sshd ignoreip, and adding that check to health_check.sh. The underlying root cause is that Luis is using a password where he should be using his ed25519 key: review his authorized_keys.
Conclusion
srv-tramontana no longer talks to just anybody. It has an allowlist policy that is written, applied in the right order and verified from outside with nmap: rate-limited SSH, open HTTPS, PostgreSQL restricted to the internal network and 8080 invisible from the Internet while it waits for the reverse proxy in Module 8. You know that underneath ufw there is nftables and that underneath nftables there is netfilter; you know how to write a complete inet ruleset with connection state, sets and rate limiting; you have tuned the sysctl settings that prevent spoofing, redirects and SYN floods; and fail2ban automatically blocks 203.0.113.44 and whoever comes next, without blocking you. The first of the three open incidents is now closed.
And now the uncomfortable part. Everything you have built in these three lessons is prevention, and prevention fails: it fails through a CVE that has no patch yet, through a leaked credential — like the one in that backup with 644 permissions — through a lapse in opening a rule "just for a moment", or because somebody with legitimate access does something they should not. The day it fails, your firewall will tell you nothing: it will go on obediently applying the rules while the attacker's connection shows up as established. The question stops being "how do I stop them getting in?" and becomes "how do I find out that they are already in?". That is lesson 06-04: Intrusion Detection Systems, where you will set up AIDE to watch file integrity, put auditd on the trail of every write to app.conf, hunt for the signs of compromise in the logs, run Lynis against your server and learn the incident response procedure — including the rule nobody likes hearing: a compromised server is reinstalled, not cleaned.
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
