Ravindra BagaleCourses & study guides मराठी Track your progress

Linux Complete Capstone: Linux Admin Labs to Build, Secure, Monitor and Fix a Production Server

This capstone puts the whole Linux Complete series together. You will take one fresh Rocky Linux 9 server and turn it into a production web server for a small company: build it (users, storage, packages, web app), secure it (SSH, firewall, SELinux, updates, auditing), monitor it (metrics, logs, alerts, health checks), back it up and restore it, and then fix six broken scenarios the way an on-call admin does. Every lab has exact commands and expected outputs, so you can check yourself step by step.

What you'll learn in this chapter

  • Build a production server from a fresh install with a written plan
  • Harden SSH, firewall, SELinux, sudo and updates
  • Set up metrics, logs, alerts and health checks
  • Automate backups and prove a restore
  • Fix six real break-and-fix scenarios
  • Write a runbook and a handover note

1. An everyday example: opening a new clinic

Why do we need this?

Dr. Kulkarni opens a small clinic in Kothrud. First the place is built: rooms, power, furniture, the reception computer. Then it is secured: locks, a CCTV camera, a cupboard for medicines with a key. Then it is monitored: a register at reception, a fridge thermometer for vaccines, a daily check. Patient files are photocopied every evening and kept at home. And when something breaks, a fuse, a leaking tap, the staff know whom to call and what to check first.

What is it?

Building the clinic = install, users, storage, packages and the app. Locks and CCTV = SSH keys, firewall, SELinux and auditd. Register and thermometer = metrics, logs and alerts. Evening photocopies = backups. Knowing whom to call = runbooks and break-fix skills.

How to do it (step by step)

The production server lifecyclePlan and documentBuild the baseSecure itDeploy the appMonitor and alertBack up and test restoreBreak, fix and improve
A server is never done: monitoring and break-fix feed improvements back into the plan.

2. Capstone brief and the build plan

Why do we need this?

Real admins never start typing commands straight away. A one-page plan avoids rework and makes the server repeatable.

What is it?

The brief: Shree Traders, a Pune distributor, needs an internal orders web app. Requirements: Rocky Linux 9 VM with 2 vCPU, 4 GB RAM, a 20 GB OS disk and a 10 GB data disk; nginx in front of a small Python app on port 8000; data on LVM at /srv/app; only SSH key logins; firewall open for 22 (office subnet only), 80 and 443; daily backups kept 14 days; alerts when disk passes 80 percent or the app health check fails. Write this plan first and keep it in /root/PLAN.md.

ItemChoiceWhy
OSRocky Linux 9 minimalStable RHEL clone, 10-year support
StorageLVM vg_data/lv_app XFS at /srv/appGrow later without downtime
Webnginx reverse proxy to 127.0.0.1:8000App never exposed directly
App runtimesystemd service as user appRestart on failure, logs in journal
AccessSSH keys, no root, admins group with sudoLeast privilege and audit trail
Backuprestic to backup host, 14 dailyEncrypted, deduplicated, tested
Monitoringnode_exporter plus health check timerMetrics and simple alerts

How to do it (step by step)

  1. Record the server facts at the start.
hostnamectl | egrep 'Static|Operating|Kernel'; nproc; free -h | awk '/Mem/{print $2}'; lsblk -dno NAME,SIZE

Output:

   Static hostname: localhost
  Operating System: Rocky Linux 9.4 (Blue Onyx)
            Kernel: Linux 5.14.0-427.el9.x86_64
2
3.6Gi
vda 20G
vdb 10G

What it means: Matches the plan: 2 CPUs, about 4 GB, and the extra 10 GB disk vdb.

  1. Set hostname, time zone and time sync.
sudo hostnamectl set-hostname orders01.shree.local; sudo timedatectl set-timezone Asia/Kolkata; sudo systemctl enable --now chronyd; chronyc tracking | grep -E 'Reference|System time'

Output:

Reference ID    : A29FC801 (time.cloudflare.com)
System time     : 0.000012 seconds fast of NTP time

What it means: Correct time matters for logs, TLS and backups.

  1. Patch fully before anything else and reboot into the new kernel.
sudo dnf -y upgrade >/dev/null; sudo needs-restarting -r; echo rc=$?

Output:

Core libraries or services have been updated since boot-up:
  * kernel
Reboot is required to fully utilize these updates.
rc=1

What it means: rc=1 means reboot needed; reboot now while the server is not yet live.

3. Build: users, sudo and storage

Why do we need this?

Shared root logins and data on the OS disk are the two most common causes of messy, unsafe servers.

What is it?

Create named admin accounts in an admins group with sudo, a locked service account app that owns the application, and put application data on its own LVM volume so it can grow and be backed up separately. Use XFS, mount by UUID and verify with mount -a before rebooting.

How to do it (step by step)

  1. Create admins and the service account.
sudo groupadd admins; sudo useradd -m -G admins ravi; sudo useradd -m -G admins asha; sudo useradd -r -m -d /srv/app -s /sbin/nologin app; echo '%admins ALL=(ALL) ALL' | sudo tee /etc/sudoers.d/admins; sudo visudo -cf /etc/sudoers.d/admins

Output:

%admins ALL=(ALL) ALL
/etc/sudoers.d/admins: parsed OK

What it means: Named people, a group rule, and a service user that cannot log in.

  1. Create LVM on the data disk.
sudo pvcreate /dev/vdb; sudo vgcreate vg_data /dev/vdb; sudo lvcreate -n lv_app -L 6G vg_data; sudo mkfs.xfs -q /dev/vg_data/lv_app; sudo lvs vg_data

Output:

  Physical volume "/dev/vdb" successfully created.
  Volume group "vg_data" successfully created
  Logical volume "lv_app" created.
  LV     VG      Attr       LSize
  lv_app vg_data -wi-a----- 6.00g

What it means: 4 GB is left free in the VG to grow later.

  1. Mount it persistently and fix ownership and SELinux label.
u=$(sudo blkid -s UUID -o value /dev/vg_data/lv_app); echo "UUID=$u /srv/app xfs defaults,nodev,nosuid 0 0" | sudo tee -a /etc/fstab; sudo mount -a; sudo chown app:app /srv/app; sudo restorecon -Rv /srv/app; findmnt /srv/app

Output:

UUID=8c2e... /srv/app xfs defaults,nodev,nosuid 0 0
TARGET   SOURCE                       FSTYPE OPTIONS
/srv/app /dev/mapper/vg_data-lv_app xfs    rw,nosuid,nodev,relatime

What it means: nodev and nosuid add safety on a data mount.

4. Build: deploy the app with systemd and nginx

Why do we need this?

Running an app by hand in a terminal dies when you log out and does not restart after a crash or reboot.

What is it?

Install Python and nginx, put the app in /srv/app, run it as a systemd service under the app user on 127.0.0.1:8000 with Restart=on-failure and basic hardening, and put nginx in front on port 80. SELinux needs the httpd_can_network_connect boolean so nginx can proxy.

How to do it (step by step)

  1. Install packages and a tiny app.
sudo dnf -y install nginx python3 >/dev/null; sudo -u app tee /srv/app/app.py >/dev/null <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
    def do_GET(self):
        body = b'ok' if self.path == '/health' else b'Shree Traders orders'
        self.send_response(200); self.end_headers(); self.wfile.write(body)
HTTPServer(('127.0.0.1', 8000), H).serve_forever()
PY
ls -l /srv/app/app.py

Output:

-rw-r--r--. 1 app app 297 Oct 26 11:20 /srv/app/app.py

What it means: A stand-in app with a /health endpoint for monitoring.

  1. Create the systemd unit with hardening.
sudo tee /etc/systemd/system/orders.service >/dev/null <<'U'
[Unit]
Description=Shree orders app
After=network-online.target
[Service]
User=app
WorkingDirectory=/srv/app
ExecStart=/usr/bin/python3 /srv/app/app.py
Restart=on-failure
RestartSec=3
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/srv/app
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
U
sudo systemctl daemon-reload; sudo systemctl enable --now orders; curl -s 127.0.0.1:8000/health

Output:

ok

What it means: The app now starts at boot, restarts on crash and cannot write outside /srv/app.

  1. Put nginx in front.
sudo tee /etc/nginx/conf.d/orders.conf >/dev/null <<'N'
server { listen 80; server_name orders01.shree.local;
  location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; } }
N
sudo setsebool -P httpd_can_network_connect on; sudo nginx -t && sudo systemctl enable --now nginx; curl -s localhost/

Output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Shree Traders orders

What it means: nginx -t before reload, and the boolean is persistent with -P.

5. Secure: SSH, firewall and SELinux

Why do we need this?

A public server gets SSH password-guessing attempts within minutes of going online.

What is it?

Use SSH keys only, disable root login and passwords, allow only the admins group, and test from a second session before closing the first. Open only 22 for the office subnet and 80/443 for everyone with firewalld rich rules. Keep SELinux enforcing and check for denials.

ControlSettingCheck
Root loginPermitRootLogin nosshd -T | grep permitrootlogin
PasswordsPasswordAuthentication nosshd -T | grep passwordauth
WhoAllowGroups adminssshd -T | grep allowgroups
Firewall22 from 10.20.0.0/24 onlyfirewall-cmd --list-all
SELinuxEnforcinggetenforce
Brute forcefail2ban sshd jailfail2ban-client status sshd

How to do it (step by step)

  1. Harden sshd with a drop-in file.
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null <<'S'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowGroups admins
MaxAuthTries 3
S
sudo sshd -t && sudo systemctl reload sshd; sudo sshd -T | egrep 'permitrootlogin|passwordauthentication|allowgroups'

Output:

permitrootlogin no
passwordauthentication no
allowgroups admins

What it means: sshd -t first; keep your current session open while testing a new one.

  1. Restrict SSH to the office subnet and open web ports.
sudo firewall-cmd --permanent --remove-service=ssh; sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.20.0.0/24 service name=ssh accept'; sudo firewall-cmd --permanent --add-service={http,https}; sudo firewall-cmd --reload; sudo firewall-cmd --list-all | egrep 'services|rule'

Output:

  services: cockpit dhcpv6-client http https
	rule family="ipv4" source address="10.20.0.0/24" service name="ssh" accept

What it means: Add the rich rule before removing ssh in real life so you never lock yourself out.

  1. Confirm SELinux and look for denials.
getenforce; sudo ausearch -m AVC -ts today 2>&1 | tail -1; sudo dnf -y remove cockpit-ws >/dev/null; sudo firewall-cmd --permanent --remove-service=cockpit; sudo firewall-cmd --reload

Output:

Enforcing
<no matches>
success
success

What it means: No denials, and the unused cockpit service is removed to shrink the attack surface.

6. Secure: updates, fail2ban, auditing and TLS

Why do we need this?

Most real breaches use known, already-patched bugs, and without an audit trail you cannot even tell what happened.

What is it?

Turn on automatic security updates with dnf-automatic, add fail2ban for SSH, add auditd rules for sudoers and SSH config changes, and enable HTTPS. For an internal name use a company CA or a self-signed certificate for the lab; for a public name use Let's Encrypt with certbot.

How to do it (step by step)

  1. Automatic security updates.
sudo dnf -y install dnf-automatic >/dev/null; sudo sed -i 's/^upgrade_type.*/upgrade_type = security/; s/^apply_updates.*/apply_updates = yes/' /etc/dnf/automatic.conf; sudo systemctl enable --now dnf-automatic.timer; systemctl list-timers dnf-automatic.timer --no-pager | sed -n 2p

Output:

Tue 2026-10-27 06:12:41 IST 18h left - - dnf-automatic.timer dnf-automatic.service

What it means: Security patches apply daily; kernels still need a planned reboot.

  1. fail2ban and audit rules.
sudo dnf -y install epel-release >/dev/null && sudo dnf -y install fail2ban >/dev/null; printf '[sshd]\nenabled = true\nmaxretry = 4\nbantime = 1h\n' | sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null; sudo systemctl enable --now fail2ban; printf -- '-w /etc/sudoers -p wa -k priv\n-w /etc/sudoers.d/ -p wa -k priv\n-w /etc/ssh/sshd_config.d/ -p wa -k sshcfg\n' | sudo tee /etc/audit/rules.d/50-admin.rules >/dev/null; sudo augenrules --load >/dev/null; sudo auditctl -l | wc -l

Output:

3

What it means: Three watches loaded; any edit to sudo or SSH config is now recorded with the user's login id.

  1. Enable HTTPS with a lab certificate.
sudo openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj '/CN=orders01.shree.local' -keyout /etc/pki/tls/private/orders.key -out /etc/pki/tls/certs/orders.crt 2>/dev/null; sudo tee -a /etc/nginx/conf.d/orders.conf >/dev/null <<'N'
server { listen 443 ssl; server_name orders01.shree.local;
  ssl_certificate /etc/pki/tls/certs/orders.crt; ssl_certificate_key /etc/pki/tls/private/orders.key;
  location / { proxy_pass http://127.0.0.1:8000; } }
N
sudo nginx -t 2>&1 | tail -1; sudo systemctl reload nginx; curl -sk https://localhost/health

Output:

nginx: configuration file /etc/nginx/nginx.conf test is successful
ok

What it means: For production use a real CA; the steps in nginx stay the same.

7. Monitor: metrics, logs, health checks and alerts

Why do we need this?

Users should never be the first to tell you the site is down or the disk is full.

What is it?

Expose metrics with node_exporter for Prometheus, keep the journal persistent and size-limited, rotate app logs, and add a small systemd timer that checks the health URL and disk usage every minute and sends an alert (here to a webhook) when a threshold is crossed. Test the alert by actually breaking something.

How to do it (step by step)

  1. Persistent, size-limited journal.
sudo mkdir -p /etc/systemd/journald.conf.d; printf '[Journal]\nStorage=persistent\nSystemMaxUse=500M\n' | sudo tee /etc/systemd/journald.conf.d/size.conf >/dev/null; sudo systemctl restart systemd-journald; journalctl --disk-usage

Output:

Archived and active journals take up 24.0M in the file system.

What it means: Logs survive reboots and cannot fill the disk.

  1. A health check script and timer.
sudo tee /usr/local/bin/healthcheck >/dev/null <<'H'
#!/usr/bin/env bash
set -uo pipefail
msg=''
curl -fsS -m 5 http://127.0.0.1/health >/dev/null || msg+='app health failed; '
use=$(df --output=pcent /srv/app | tail -1 | tr -dc 0-9)
(( use >= 80 )) && msg+="disk /srv/app ${use}%; "
if [[ -n $msg ]]; then logger -t healthcheck "ALERT $msg"; curl -fsS -m 5 -d "text=orders01: $msg" "${ALERT_URL:-http://10.20.0.5/hook}" >/dev/null || true; fi
H
sudo chmod 755 /usr/local/bin/healthcheck
printf '[Unit]\nDescription=health\n[Service]\nType=oneshot\nExecStart=/usr/local/bin/healthcheck\n' | sudo tee /etc/systemd/system/healthcheck.service >/dev/null
printf '[Unit]\nDescription=health every minute\n[Timer]\nOnCalendar=minutely\n[Install]\nWantedBy=timers.target\n' | sudo tee /etc/systemd/system/healthcheck.timer >/dev/null
sudo systemctl daemon-reload; sudo systemctl enable --now healthcheck.timer; systemctl is-active healthcheck.timer

Output:

active

What it means: Every minute it checks the app and the disk and alerts only on problems.

  1. Test the alert by stopping the app.
sudo systemctl stop orders; sleep 65; journalctl -t healthcheck -n 1 --no-pager -o cat; sudo systemctl start orders

Output:

ALERT app health failed;

What it means: The alert fired. An alert you have never seen fire is an alert you cannot trust.

8. Backup and restore

Why do we need this?

A backup that has never been restored is only a hope. Disks fail, people delete things, and ransomware encrypts data.

What is it?

Back up /srv/app, /etc and a list of installed packages with restic to a separate backup host over SFTP, encrypted. Run it nightly with a systemd timer, keep 14 daily and 4 weekly snapshots, and restore to a temporary folder every month to prove it works. Follow 3-2-1: three copies, two kinds of media, one off site.

How to do it (step by step)

  1. Initialise the repository.
sudo dnf -y install restic >/dev/null; export RESTIC_REPOSITORY=sftp:backup@10.20.0.9:/backups/orders01 RESTIC_PASSWORD_FILE=/root/.restic-pass; sudo -E restic init | tail -1

Output:

created restic repository 3f9a1c22be at sftp:backup@10.20.0.9:/backups/orders01

What it means: Keep the password file safe and a copy offline; without it the backup is useless.

  1. Run a backup and apply retention.
rpm -qa | sort | sudo tee /root/pkglist.txt >/dev/null; sudo -E restic backup /srv/app /etc /root/pkglist.txt --tag nightly -q; sudo -E restic forget --keep-daily 14 --keep-weekly 4 --prune -q; sudo -E restic snapshots --compact | tail -2

Output:

3c1d7e2a  2026-10-26 11:48:03  orders01  nightly  /etc, /root/pkglist.txt, /srv/app
1 snapshots

What it means: Then wrap these lines in a script with a nightly systemd timer.

  1. Prove a restore.
sudo -E restic restore latest --target /tmp/restore --include /srv/app/app.py -q; diff /srv/app/app.py /tmp/restore/srv/app/app.py && echo restore-ok

Output:

restore-ok

What it means: Identical file restored. Write the date in the runbook each time you test.

9. Fix: six production break-and-fix scenarios

Why do we need this?

Interviewers and on-call shifts both test whether you can find the cause quickly and safely, not whether you remember every flag.

What is it?

Ask a partner to break your server with the commands below while you look away, then fix it using the method: symptom, scope, recent changes, logs, resources, fix, verify, write down. Each scenario shows the break, the clue and the fix.

ScenarioSymptomRoot cause
1502 Bad GatewayApp service stopped or crashed
2502 with app runningSELinux boolean turned off
3Writes fail, disk fullHuge log file in /srv/app
4Cannot SSH from officeFirewall rich rule removed
5Server boots to emergency modeBad fstab line
6Site slow, load highRunaway CPU process

How to do it (step by step)

  1. Scenario 1 and 2: 502 Bad Gateway.
curl -sI localhost | head -1; systemctl is-active orders; getsebool httpd_can_network_connect; sudo tail -1 /var/log/nginx/error.log

Output:

HTTP/1.1 502 Bad Gateway
active
httpd_can_network_connect --> off
... connect() to 127.0.0.1:8000 failed (13: Permission denied) while connecting to upstream

What it means: App is up, but permission denied plus the boolean off points to SELinux. Fix: setsebool -P httpd_can_network_connect on. If the app were inactive, check journalctl -u orders and start it.

  1. Scenario 3: disk full.
df -h /srv/app | tail -1; sudo du -xah /srv/app | sort -h | tail -2; sudo lsof +L1 | tail -1

Output:

/dev/mapper/vg_data-lv_app  6.0G  6.0G   20K 100% /srv/app
5.7G	/srv/app/debug.log
6.0G	/srv/app

What it means: Truncate instead of deleting if a process holds it: sudo truncate -s 0 /srv/app/debug.log. Then add logrotate, or grow: lvextend -r -L +2G /dev/vg_data/lv_app.

  1. Scenarios 4, 5 and 6: SSH, emergency mode and high load.
sudo firewall-cmd --list-rich-rules | wc -l; findmnt --verify 2>&1 | tail -1; ps -eo pid,user,%cpu,cmd --sort=-%cpu | head -2

Output:

0
   1 parse errors, 0 errors, 0 warnings
    PID USER  %CPU CMD
   4121 app   99.3 python3 -c while True: pass

What it means: No rich rule: re-add it from the console. fstab parse error: fix the line from the emergency shell after mount -o remount,rw /. Runaway process: confirm with the owner, then kill 4121 and find why it started.

10. Runbook, handover and final checklist

Why do we need this?

A server only one person understands is a risk. A runbook lets anyone on the team handle it at 2 a.m.

What is it?

Write /root/RUNBOOK.md with: what the server does, how to reach it, services and ports, where data and logs live, how to deploy, how to restore, the six scenarios and their fixes, and who to call. Then run a final checklist script that checks every requirement from the plan and prints PASS or FAIL.

How to do it (step by step)

  1. A final checklist script.
cat > check.sh <<'C'
#!/usr/bin/env bash
ok(){ printf '%-28s %s\n' "$1" "$( eval "$2" >/dev/null 2>&1 && echo PASS || echo FAIL)"; }
ok 'SELinux enforcing' '[[ $(getenforce) == Enforcing ]]'
ok 'root ssh disabled' 'sshd -T | grep -q "permitrootlogin no"'
ok 'password ssh disabled' 'sshd -T | grep -q "passwordauthentication no"'
ok 'app healthy' 'curl -fs localhost/health'
ok 'https healthy' 'curl -fsk https://localhost/health'
ok 'data mounted' 'findmnt /srv/app'
ok 'backup timer' 'systemctl is-active restic-backup.timer'
ok 'health timer' 'systemctl is-active healthcheck.timer'
ok 'auto updates' 'systemctl is-active dnf-automatic.timer'
ok 'no failed units' '[[ -z $(systemctl --failed --no-legend) ]]'
C
sudo bash check.sh

Output:

SELinux enforcing            PASS
root ssh disabled            PASS
password ssh disabled        PASS
app healthy                  PASS
https healthy                PASS
data mounted                 PASS
backup timer                 PASS
health timer                 PASS
auto updates                 PASS
no failed units              PASS

What it means: Every line maps back to a requirement in PLAN.md.

  1. Reboot and run it again.
sudo systemctl reboot
# after login
sudo bash check.sh | grep -c PASS

Output:

10

What it means: The real proof: everything comes back on its own after a reboot.

  1. Write the handover note.
cat /root/HANDOVER.txt

Output:

orders01.shree.local (10.20.0.21), Rocky 9.4. nginx :80/:443 -> orders.service :8000 (user app). Data /srv/app on vg_data/lv_app (6G, 4G free). SSH keys only, admins group, office subnet. Backups restic nightly 01:30 to 10.20.0.9, last restore test 2026-10-26 OK. Alerts via healthcheck.timer. Runbook /root/RUNBOOK.md.

What it means: Five lines a new teammate can act on immediately.

11. Real examples from many industries

Why do we need this?

The same Linux skill shows up in every kind of company. Seeing it in many places helps you remember it.

What is it?

Each row is a made-up team. It shows who uses this and how.

How to do it (step by step)

Team and cityHow they use itCommand
Ravi, distributor IT, PuneBuilds the orders server for Shree Traderssystemctl enable --now orders
Asha, hospital, NashikUses the same plan for a lab reports serverrestic snapshots
Liam, SaaS startup, MelbourneTurns the checklist into an Ansible roleansible-playbook site.yml --check
Aiko, school board, KyotoHardens SSH and firewall for a results portalsshd -T | grep allowgroups
Carlos, retail chain, Mexico CityRuns monthly restore drillsrestic restore latest --target /tmp/r
Snehal, sugar factory, BaramatiAdds disk alerts after a full-disk outagedf --output=pcent /srv/app

12. Common errors and how to fix them

Why do we need this?

Everyone hits these errors. Knowing the cause saves hours.

What is it?

Read the exact message first. Then match it to a row below.

How to do it (step by step)

Error you seeWhy it happensHow to fix
502 Bad GatewayApp down or SELinux blocks nginx proxyCheck the service, then the boolean.
setsebool -P httpd_can_network_connect on
Permission denied (publickey)Key not in authorized_keys or wrong permsFix .ssh permissions and context.
chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys; restorecon -Rv ~/.ssh
orders.service: Failed with result 'exit-code'App crashed or path wrongRead the journal.
journalctl -u orders -n 30 --no-pager
Read-only file system on /srv/appProtectSystem=strict without ReadWritePathsAdd ReadWritePaths.
systemctl edit orders
No space left on deviceLogs filled the data volumeTruncate, rotate or extend.
lvextend -r -L +2G /dev/vg_data/lv_app
Fatal: unable to open config file (restic)Repository or password env missingExport both variables.
export RESTIC_REPOSITORY=... RESTIC_PASSWORD_FILE=/root/.restic-pass
nginx: [emerg] cannot load certificateWrong path or SELinux label on certCheck path and restorecon.
restorecon -Rv /etc/pki/tls
You are in emergency modeBad /etc/fstab lineRemount rw and fix fstab.
mount -o remount,rw /; vi /etc/fstab
Connection timed out on SSHFirewall rich rule missingRe-add it from the console.
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.20.0.0/24 service name=ssh accept'

13. Troubleshooting step by step

Why do we need this?

When the production server misbehaves, follow the same order every time and write down what you find.

What is it?

Go top to bottom. Stop at the first step that shows the problem.

How to do it (step by step)

  1. Is it up and reachable?
curl -sI https://orders01.shree.local -k | head -1
  1. Which service is unhappy?
systemctl --failed; systemctl status orders nginx --no-pager | grep Active
  1. What do the logs say?
journalctl -p err -b --no-pager | tail -20
  1. Resources: CPU, memory, disk.
uptime; free -h; df -h
  1. Security layers: SELinux and firewall.
ausearch -m AVC -ts recent; firewall-cmd --list-all
  1. What changed recently?
dnf history | head -5; ausearch -k priv -ts today | tail -3

14. Lab 1: Build and secure orders01 end to end

Why do we need this?

On a fresh Rocky 9 VM, follow sections 1 to 5 and finish with all security checks passing.

What is it?

A full hands-on lab. Run every step on a test Linux machine (an EC2 instance, a VM, or WSL).

How to do it (step by step)

  1. Write PLAN.md and record facts.
wc -l /root/PLAN.md; hostname

Output:

24 /root/PLAN.md
orders01.shree.local
  1. Users, sudo and LVM.
getent group admins; findmnt -no SOURCE /srv/app

Output:

admins:x:1001:ravi,asha
/dev/mapper/vg_data-lv_app
  1. App and nginx.
curl -s localhost/; systemctl is-enabled orders nginx

Output:

Shree Traders orders
enabled
enabled
  1. SSH and firewall hardening.
sshd -T | grep -c -E 'permitrootlogin no|passwordauthentication no'; firewall-cmd --list-services

Output:

2
dhcpv6-client http https
  1. Updates, fail2ban, audit, TLS.
systemctl is-active dnf-automatic.timer fail2ban auditd; curl -sk https://localhost/health

Output:

active
active
active
ok

15. Lab 2: Monitor, back up, break and fix

Why do we need this?

Add monitoring and backups, then have a partner run all six break scenarios; fix each within 10 minutes and log it.

What is it?

A full hands-on lab. Run every step on a test Linux machine (an EC2 instance, a VM, or WSL).

How to do it (step by step)

  1. Health timer and alert test.
systemctl stop orders; sleep 65; journalctl -t healthcheck -n1 -o cat; systemctl start orders

Output:

ALERT app health failed;
  1. Backup and restore test.
restic snapshots --compact | tail -1; restic restore latest --target /tmp/r -q && ls /tmp/r/srv/app

Output:

1 snapshots
app.py
  1. Partner breaks: SELinux boolean off.
curl -sI localhost | head -1; getsebool httpd_can_network_connect

Output:

HTTP/1.1 502 Bad Gateway
httpd_can_network_connect --> off
  1. Fix and verify.
setsebool -P httpd_can_network_connect on; curl -sI localhost | head -1

Output:

HTTP/1.1 200 OK
  1. Partner breaks: fills the disk. Fix and log.
truncate -s 0 /srv/app/debug.log; df --output=pcent /srv/app | tail -1; echo '2026-10-26 12:30 disk full, truncated debug.log, added logrotate' >> /root/INCIDENTS.log

Output:

  2%
  1. Final checklist after reboot.
bash check.sh | grep -c PASS

Output:

10

Ravindra Bagale's Tip

Do this capstone twice. The first time, follow every step. The second time, build the server from your own PLAN.md and RUNBOOK.md without looking at this page. If you can do that, you can confidently say in an interview that you have built, secured, monitored and fixed a production Linux server, and explain every choice.

16. Frequently asked questions

Q. Can I use Ubuntu instead of Rocky?

Yes. Swap dnf for apt, firewalld for ufw and SELinux for AppArmor; the plan stays the same.

Q. Do I need a cloud account?

No. A local VM in VirtualBox, KVM or UTM is enough. A second small VM acts as the backup host.

Q. Why a self-signed certificate?

Only for the lab. In production use Let's Encrypt or your company CA.

Q. Should I use Prometheus and Grafana?

Yes, for real fleets. The health timer here is the minimum that works on one server.

Q. How long does the capstone take?

About 4 to 6 hours the first time, under 2 hours the second time.

Q. Can I put this on my CV?

Yes, as a project. Push PLAN.md, RUNBOOK.md and scripts to a Git repo with no secrets.

Q. What if I lock myself out of SSH?

Use the VM console, fix the firewall or sshd file, and always keep one session open while testing.

Q. Should I automate this with Ansible?

Yes, as the next step. Turn each section into a role and rebuild the server with one command.

17. Interview questions and answers

Beginner level

Q. Why put app data on a separate volume?

To grow it and back it up separately, and to stop it filling the OS disk.

Q. Why run the app as a service user?

Least privilege: a compromised app cannot act as root.

Q. What does nginx -t do?

Tests the config before reload.

Q. Why test a restore?

Only a restore proves the backup works.

Mid level level

Q. How do you avoid locking yourself out when hardening SSH?

sshd -t, keep a session open, test a new login, have console access.

Q. Why does nginx get 502 with SELinux enforcing?

httpd_can_network_connect is off.

Q. How do you alert without a full monitoring stack?

A systemd timer running a check script that logs and calls a webhook.

Q. What does ProtectSystem=strict do?

Makes the filesystem read-only for the service except ReadWritePaths.

Senior level

Q. How would you turn this into a fleet of 50 servers?

Ansible roles or images, config in Git, central metrics and logs, staged patching.

Q. What would you change for high availability?

Two app servers behind a load balancer, shared or replicated data, health-based failover.

Q. How do you prove compliance?

OpenSCAP scans, audit logs, a checklist script in CI and stored reports.

Q. How do you decide RPO and RTO?

With the business: how much data loss and downtime is acceptable, then design backups to meet it.

Try at home

  1. Build orders01 on a home VM from start to finish, then run check.sh after a reboot.
  2. Ask a friend to break it with any two scenarios and time your fixes.
  3. Publish your PLAN.md, RUNBOOK.md and scripts in a Git repo, without secrets.

Practice Tasks

About this scenario

Amravati Agro Market is a agriculture business in Amravati. Anjali Thakre looks after its IT and cloud work. They count money in INR and run their AWS resources in the ap-south-1 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.

0 / 22

Easy Hostname

Why: BIZ is putting a new production server live.

What to do: Anjali Thakre is putting a Amravati Agro Market server live. Set its hostname to web02.shree.local.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Time zone

Why: BIZ is putting a new production server live.

What to do: Set the time zone to Europe/London for a UK branch server.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Physical volume

Why: BIZ is putting a new production server live.

What to do: Initialise /dev/vdc as an LVM physical volume.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Make XFS

Why: BIZ is putting a new production server live.

What to do: Create an XFS filesystem on /dev/vg_data/lv_app.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Test nginx

Why: BIZ is putting a new production server live.

What to do: Test nginx and dump the full merged configuration.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Test sshd

Why: BIZ is putting a new production server live.

What to do: Test sshd config for errors, naming the main config file explicitly.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Ban status

Why: BIZ is putting a new production server live.

What to do: Unban IP 10.0.0.9 from the sshd jail in fail2ban.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Journal size

Why: BIZ is putting a new production server live.

What to do: Shrink the journal to at most 200M.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Run checklist

Why: BIZ is putting a new production server live.

What to do: Run the final checklist script check.sh with a trace, as root.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Reboot needed?

Why: BIZ is putting a new production server live.

What to do: After patching, list services that need a restart.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Service user

Why: BIZ is putting a new production server live.

What to do: Create a system user shop with home /srv/shop and no login shell.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Check sudoers

Why: BIZ is putting a new production server live.

What to do: Check the main sudoers file and all included files for syntax errors, quietly.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Reload units

Why: BIZ is putting a new production server live.

What to do: After editing orders.service, make systemd re-read units and then restart orders.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Effective sshd

Why: BIZ is putting a new production server live.

What to do: Print the effective sshd configuration and keep only the permitrootlogin line.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Web services

Why: BIZ is putting a new production server live.

What to do: Permanently allow only the https service in firewalld.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Load audit rules

Why: BIZ is putting a new production server live.

What to do: Search the audit log for events with key sshcfg.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium List timers

Why: BIZ is putting a new production server live.

What to do: List all systemd timers, including inactive ones.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Snapshots

Why: BIZ is putting a new production server live.

What to do: List only restic snapshots tagged nightly.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Proxy boolean

Why: BIZ is putting a new production server live.

What to do: Allow nginx to connect to a database port, persistently, in SELinux.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Restore latest

Why: BIZ is putting a new production server live.

What to do: Restore the latest restic snapshot into /tmp/restore-test.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Free space now

Why: BIZ is putting a new production server live.

What to do: Empty /srv/app/access.log in place without deleting it.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Emergency remount

Why: BIZ is putting a new production server live.

What to do: After rd.break, remount /sysroot read-write.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

In short

  1. Plan first, then build.
  2. Named users, sudo, data on LVM.
  3. systemd for the app, nginx in front.
  4. Keys only SSH, tight firewall, SELinux enforcing.
  5. Alerts tested by breaking things.
  6. Backups proven by restore, and a runbook for everyone.

Revision, comparison and interview cards

Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.

Server lifecycle. Plan, then build. Secure and deploy. Monitor and back up. Break, fix, improve.
Revision card: Server lifecycle · Download PNG · Open card page
Hardening checklist. Keys only SSH. No root login. Tight firewall. SELinux enforcing.
Revision card: Hardening checklist · Download PNG · Open card page
nohup app & vs systemd service. Manual background. / Managed service.
Comparison: nohup app & vs systemd service · Download PNG · Open card page
restic backup vs LVM snapshot. Copy elsewhere. / Point in time, same disk.
Comparison: restic backup vs LVM snapshot · Download PNG · Open card page
502 behind nginx?. Is the app up? SELinux proxy boolean. Read error.log.
Interview card: 502 behind nginx? · Download PNG · Open card page
Is the backup good?. Only a restore proves it. Restore and compare. Log the test date.
Interview card: Is the backup good? · Download PNG · Open card page
Avoid SSH lockout?. Test config first. Keep a session open. Have console access.
Interview card: Avoid SSH lockout? · Download PNG · Open card page
Boots to emergency mode?. Usually bad fstab. Remount rw. Fix and verify.
Interview card: Boots to emergency mode? · Download PNG · Open card page

More cards: Ravi Sir's Cards.