A case study designing a production-grade Linux server: a unified setup flow from OS hardening, users & sudo, networking & DNS, backup & monitoring, to containers/VMs for application services — the final episode of the Learn Linux series.

After episode 29 where we built the high availability and monitoring stack — Keepalived with virtual IPs, Prometheus + node_exporter + Grafana, and Alertmanager — you now have the ability to keep services alive and measurable. Over the last 29 episodes, we built skills one by one: core commands, users & permissions, package management, systemd, storage, networking, SSH, firewalls, backups, containers, LDAP, all the way to HA and monitoring. Each is a standalone tool.
But a true Linux admin isn't judged by how many tools they know — they're judged by their ability to assemble all those tools into one whole, secure, production-ready system. That's the biggest question we haven't answered yet: how do you design a production-grade Linux server from scratch, with all the components we've learned working together as one unit?
Episode 30 is the final episode of the Learn Linux series. Here we no longer learn new commands; we assemble everything. You'll follow a case study building one production server from OS installation to running services, using every lesson from episode 0 to 29 in one unified flow. We close with a production readiness checklist and a summary of the entire series journey — a map that will accompany you as you start your career as a Linux System Administrator.
Let's take a real scenario: your company wants to run a small web application (myapp) on one server. The app needs nginx, a Node.js backend, and a PostgreSQL database. You're given a new Ubuntu 24.04 server with root access. Your task: make this server secure, measurable, backed up, and fit for production — not just "able to run the application".
Now, compare two approaches. The beginner approach: straight to apt install nginx, copy the code, run it. That works — until the server gets brute-forced, the database is lost, or you forget who has access to the server. The professional approach: follow a unified setup flow made of five stages. That's what we'll do.
Once the OS is installed, the first step isn't installing the application — it's shrinking the attack surface. The principle: the best attack is the one that's impossible to perform.
apt update && apt upgrade -y
apt install -y ufw fail2ban
rebootufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80,443/tcp
ufw enablessh-keygen -t ed25519 -C "admin@myapp" -f ~/.ssh/id_ed25519
ssh-copy-id root@SERVER_IP
nano /etc/ssh/sshd_configPermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Port 22systemctl restart ssh
systemctl enable --now fail2ban
fail2ban-client status sshdImportant
The order above isn't a coincidence: the firewall is enabled before changing the SSH config, and the ufw allow 22/tcp rule is placed before ufw enable. If the order is reversed, you can lock yourself out of the server. This is why a runbook (written procedure) is always safer than relying on memory — one missed step can mean losing full access.
With root login disabled, you need an administrative user to work. The least privilege principle from episodes 8 and 10 applies fully here: give rights according to need, no more.
adduser deploy
usermod -aG sudo deploy
# Copy the admin public key to the new user
su - deploy
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keyssudo visudodeploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload nginx
deploy ALL=(ALL) ALLTip
Note the pattern above: the specific commands (restart myapp, reload nginx) are given NOPASSWD so automated deploys don't stall asking for a password, while the last line gives full access for administrative work. In stricter production, the last line is dropped — the deploy user may only run explicitly allowed commands. The less an account can do, the smaller the damage a compromised account can cause.
The server needs a stable identity on the network. A clear hostname and a tidy /etc/hosts file prevent confusion later — especially when you must figure out "which server runs what".
hostnamectl set-hostname web-prod-01
echo "127.0.1.1 web-prod-01.myapp.local web-prod-01" >> /etc/hostsip addr show
ip route show
resolvectl statuscurl -I https://api.myapp.example.comThe application DNS (api.myapp.example.com → server IP) is managed at the registrar/DNS provider, not on the server. What matters on the server side: make sure reverse lookup and hostname are consistent, because many services (e.g. PostgreSQL and Postfix) are sensitive to mismatched hostnames.
A server that isn't backed up and monitored is a server "living on hope". From episode 26 we know backups must be tested; from episode 29 we know monitoring must exist before problems. Install both before running production load — not after.
install -m 755 /dev/stdin /usr/local/sbin/backup-server.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
restic -r /backups/restic-repo backup /etc /home /var/www
restic -r /backups/restic-repo forget --keep-daily 7 --prune
EOF
echo "30 2 * * * root /usr/local/sbin/backup-server.sh >> /var/log/backup.log 2>&1" \
> /etc/cron.d/backup-dailyinstall -m 755 node_exporter /usr/local/bin/
systemctl enable --now node_exportercurl -s http://web-prod-01:9100/metrics | grep node_boot_time_secondsWarning
One common mistake: installing monitoring after the problem appears. Yet metrics are only useful if there's a baseline — historical data before the incident. Without a baseline, you can't answer the question "is this load average of 5 normal?" because there's nothing to compare against. Install exporters and Prometheus on day one, not on the day of the problem.
The final stage is running the application itself. Based on episode 27, the isolation choice depends on the need: if the app needs its own kernel and OS, use a VM (KVM); if process-level isolation is enough, containers (Docker) are the lightweight, fast choice. For myapp, we'll use Docker with PostgreSQL whose data lives in a volume:
services:
web:
image: myapp:1.2.0
restart: unless-stopped
ports:
- "8080:8080"
environment:
DB_HOST: db
depends_on:
- db
db:
image: postgres:16
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:cd /opt/myapp && docker compose up -d
docker compose ps
systemctl enable --now dockerNote the restart: unless-stopped — this ensures containers come up along with the server on reboot, so services don't silently die just because of a brief power outage. Combine it with Restart=on-failure in systemd units for non-container services.
With the five steps done, the web-prod-01 server is now a complete production server: hardened, controlled access, configured networking, backed up and monitored, and the application running on top of the right isolation. This is the final product of the entire Learn Linux series.
After understanding the flow above, you need a tool you can reuse to assess a server's maturity. The following checklist is the minimum standard that must pass before you dare call a server "production":
| Area | Check Item | Reference |
|---|---|---|
| System | Updates & upgrades applied on schedule | Episode 11 |
| System | Timezone & NTP (chrony/systemd-timesyncd) accurate | Episode 14 |
| Security | SSH root login disabled, key auth active | Episode 18 |
| Security | Default-deny firewall only opens needed ports | Episode 19 |
| Security | Fail2ban/SELinux/AppArmor active | Episode 25 |
| Security | No users with weak passwords / uncontrolled access | Episode 8 |
| Access | Sudo restricted with least privilege | Episode 10 |
| Network | Hostname & DNS consistent, listening ports as expected | Episode 17 |
| Data | Backups scheduled and ever restore-tested | Episode 26 |
| Data | Database backed up with a logical dump | Episode 26 |
| Observability | node_exporter + Prometheus showing metrics | Episode 29 |
| Observability | Alerts active for full disk, high CPU, down targets | Episode 29 |
| Resilience | Service auto-restart (systemd Restart= / Docker restart:) | Episode 14 |
| Resilience | Rollback/restore procedure documented in the runbook | Episode 26 |
| Documentation | Runbook, topology, and owner contacts written | Episode 30 |
Note
Don't make this checklist a tool of judgment, but a tool of improvement. No server is perfect from day one — what exists are servers maintained toward perfection. Run this checklist periodically (e.g. every quarter), mark the items that don't pass, and make a plan to close the gaps. A good sysadmin never stops "perfecting" their servers.
Above all commands and tools, there's a mindset that distinguishes a professional Linux admin from just "someone who can type commands". These five principles are the essence of the 30 episodes we've been through:
Document before it's too late. A runbook recording why a server is configured that way is more valuable than a million how commands. Six months from now, "present you" will thank "past you" for writing notes.
Automate repetitive work. If you run the same command twice, write a script; three times, schedule it with cron/systemd timers (episode 22). Humans forget, machines don't.
Practice least privilege everywhere. Users, sudo, firewall, file permissions, containers — always grant the minimum access. Every extra right is an extra attack surface.
Test recovery, not just preparation. Backup without a restore test, failover without a simulation, and disaster recovery without a drill are unproven beliefs. Test periodically — that's the only way to ensure your systems are truly ready.
Learn from failure. Every incident is a lesson. Record what went wrong, its impact, and how to prevent it. A healthy team is one that can discuss failures without blaming people — and fix them systematically.
Congratulations — you've completed the 31-episode journey of the Learn Linux series! This is a real achievement, not just "already read". Let's recap the big map we've walked together, phase by phase:
| Phase | Episodes | Core Material |
|---|---|---|
| Phase 1 — Pre-Requisites & Fundamentals | 0–2 | Environment setup, Linux history & philosophy, system architecture & FHS |
| Phase 2 — Basic Operational & Essential Commands | 3–7 | Filesystem navigation, text editing, pipeline & redirection, text processing, symlinks & archives |
| Phase 3 — User, Permissions & Package Management | 8–12 | Users & groups, permissions & ownership, sudo, package managers, shell & environment |
| Phase 4 — Process, Storage & System Services | 13–16 | Process management, systemd & services, storage/LVM, boot process & kernel |
| Phase 5 — Networking, Firewall & Security | 17–21 | Networking & diagnostics, SSH & file transfer, firewall, file sharing & DNS, packet analysis |
| Phase 6 — Automation, Troubleshooting & Production Readiness | 22–30 | Cron & timers, logging, performance tuning, hardening, backup, containers, LDAP, HA & monitoring, and this production setup |
Look at what you've achieved. You started from just getting familiar with the terminal in episode 0, and now you can design a production-grade server that's hardened, backed up, monitored, and failover-ready. You no longer ask "what command for this?" — you ask "how should this system be designed?" And that, precisely, is the way of thinking of a System Administrator.
Let's remember the three foundations that hold everything up. The "everything is a file" philosophy that makes Linux so consistent and predictable. The least privilege principle that keeps systems safe in any situation. And the culture of automation and documentation that separates professional admins from mere users. These three aren't just episode material — they're ways of working that will accompany your career.
Your journey as a Linux System Administrator has just begun. The skills you've built are a rare and very valuable foundation in the job market — DevOps engineers, SREs, and cloud engineers all root their craft in the ability to manage Linux systems correctly. The next step is up to you: work on real projects on your own server, pursue certifications like the Linux Professional Institute (LPI) or RHCSA, build a home lab with Proxmox, and keep adding tooling around Linux — scripting, automation with Ansible, and Kubernetes are natural directions after this.
A Linux server never distinguishes who is typing commands in front of it — it treats everyone the same: it gives access as large as allowed, and forgives those who are careful. You have that carefulness now. Keep practicing, keep building, and keep writing notes. See you on your next Linux journey — and happy building as a Linux System Administrator!