linux-admin
Use this skill when managing Linux servers, writing shell scripts, configuring systemd services, debugging networking, or hardening security. Triggers on bash scripting, systemd units, iptables, firewall, SSH configuration, file permissions, process management, cron jobs, disk management, and any task requiring Linux system administration.
infra linuxsysadminshellsystemdnetworkingsecurityWhat is linux-admin?
Use this skill when managing Linux servers, writing shell scripts, configuring systemd services, debugging networking, or hardening security. Triggers on bash scripting, systemd units, iptables, firewall, SSH configuration, file permissions, process management, cron jobs, disk management, and any task requiring Linux system administration.
linux-admin
linux-admin is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex. Managing Linux servers, writing shell scripts, configuring systemd services, debugging networking, or hardening security.
Quick Facts
| Field | Value |
|---|---|
| Category | infra |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill linux-admin- The linux-admin skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
A production-focused Linux administration skill covering shell scripting, service management, networking, and security hardening. This skill treats every Linux system as a production asset - configuration is explicit, changes are auditable, and security is a constraint from the start, not an afterthought. Designed for engineers who need to move confidently between writing a deploy script, debugging a network issue, and locking down a fresh server.
Tags
linux sysadmin shell systemd networking security
Platforms
- claude-code
- gemini-cli
- openai-codex
Related Skills
Pair linux-admin with these complementary skills:
Frequently Asked Questions
What is linux-admin?
Use this skill when managing Linux servers, writing shell scripts, configuring systemd services, debugging networking, or hardening security. Triggers on bash scripting, systemd units, iptables, firewall, SSH configuration, file permissions, process management, cron jobs, disk management, and any task requiring Linux system administration.
How do I install linux-admin?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill linux-admin in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support linux-admin?
This skill works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Linux Administration
A production-focused Linux administration skill covering shell scripting, service management, networking, and security hardening. This skill treats every Linux system as a production asset - configuration is explicit, changes are auditable, and security is a constraint from the start, not an afterthought. Designed for engineers who need to move confidently between writing a deploy script, debugging a network issue, and locking down a fresh server.
When to use this skill
Trigger this skill when the user:
- Writes or debugs a bash script (especially anything running in CI, cron, or production)
- Creates or modifies a systemd service, timer, socket, or target unit
- Configures or audits SSH daemon settings and access controls
- Debugs a networking issue (routing, DNS, firewall, port connectivity)
- Sets up or modifies iptables/nftables/ufw firewall rules
- Manages file permissions, ownership, ACLs, or setuid/setgid bits
- Monitors or investigates running processes (CPU, memory, open files, syscalls)
- Sets up cron jobs or scheduled tasks
- Manages disk space, log rotation, or filesystem mounts
Do NOT trigger this skill for:
- Container orchestration specifics (Kubernetes networking, Docker Compose config) - use a Docker/K8s skill instead
- Cloud provider IAM, VPC routing, or managed service configuration - those are cloud platform concerns, not OS-level Linux administration
Key principles
Principle of least privilege - Every process, user, and service should run with the minimum permissions required. Use dedicated service accounts (not root), restrict file permissions to exactly what is needed, and audit sudo rules regularly.
Automate repeatable tasks - If you run a command twice, script it. Scripts should be idempotent - running them again should produce the same result, not break things. Store scripts in version control.
Log everything that matters - Structured logs, audit logs (auditd), and systemd journal entries are your incident response safety net. Log authentication events, privilege escalations, and configuration changes. Log rotation prevents disk exhaustion.
Immutable servers when possible - Prefer rebuilding servers from a known-good image over patching in place. Use configuration management (Ansible, cloud-init) to define state declaratively. Manual "snowflake" servers drift and fail unpredictably.
Test in staging - Every script, service unit, and firewall rule change should be validated in a non-production environment first. Use
--dry-run,bash -n, andiptables --checkto validate before applying.
Core concepts
File permissions
Linux permissions have three layers (owner, group, others) and three bits (read, write, execute). Octal notation is the authoritative form.
Octal Symbolic Meaning
0 --- no permissions
1 --x execute only
2 -w- write only
4 r-- read only
6 rw- read + write
7 rwx read + write + execute
# Common patterns
chmod 600 ~/.ssh/id_rsa # private key: owner read/write only
chmod 644 /etc/nginx/nginx.conf # config: owner rw, others read
chmod 755 /usr/local/bin/script # executable: owner rwx, others rx
chmod 700 /root/.gnupg # directory: only owner can enterSpecial bits:
setuid (4xxx): executable runs as file owner, not caller. Dangerous on scripts.setgid (2xxx): new files in directory inherit group. Useful for shared dirs.sticky (1xxx): only file owner can delete in a directory (e.g.,/tmp).
Process management
Key signals for process control:
| Signal | Number | Meaning |
|---|---|---|
| SIGTERM | 15 | Polite shutdown - process should clean up |
| SIGKILL | 9 | Immediate kill - kernel enforced, unblockable |
| SIGHUP | 1 | Reload config (many daemons re-read on SIGHUP) |
| SIGINT | 2 | Interrupt (Ctrl+C) |
| SIGUSR1/2 | 10/12 | Application-defined |
niceness runs from -20 (highest priority) to 19 (lowest). Use nice -n 10 cmd for
background tasks and renice to adjust running processes.
systemd unit hierarchy
Targets (grouping) -> multi-user.target, network.target
Services (.service) -> long-running daemons, oneshot tasks
Timers (.timer) -> scheduled execution (replaces cron)
Sockets (.socket) -> socket-activated services
Mounts (.mount) -> filesystem mounts managed by systemd
Paths (.path) -> filesystem change triggersDependency directives: Requires= (hard), Wants= (soft), After= (ordering only).
After=network-online.target is the correct way to wait for network connectivity.
Networking stack
Key tools and their roles:
| Tool | Layer | Purpose |
|---|---|---|
ip addr / ip link |
L2/L3 | Interface state, IP addresses, routes |
ip route |
L3 | Routing table inspection and management |
ss -tulpn |
L4 | Listening ports, socket state, owning process |
iptables -L -n -v |
L3/L4 | Firewall rules, packet counts |
dig / resolvectl |
DNS | Name resolution debugging |
traceroute / mtr |
L3 | Path tracing, hop-by-hop latency |
tcpdump |
L2-L7 | Packet capture for deep inspection |
Common tasks
Write a robust bash script
Always use the safety triplet at the top of every non-trivial script.
#!/usr/bin/env bash
set -euo pipefail
# -e: exit on error
# -u: treat unset variables as errors
# -o pipefail: pipeline fails if any command in it fails
# Cleanup on exit - runs on success, error, and signals
TMPDIR_WORK=""
cleanup() {
local exit_code=$?
[[ -n "$TMPDIR_WORK" ]] && rm -rf "$TMPDIR_WORK"
exit "$exit_code"
}
trap cleanup EXIT INT TERM
# Argument parsing with defaults and validation
usage() {
echo "Usage: $0 [-e ENV] [-d] <target>"
echo " -e ENV Environment (default: staging)"
echo " -d Dry-run mode"
exit 1
}
ENV="staging"
DRY_RUN=false
while getopts ":e:dh" opt; do
case $opt in
e) ENV="$OPTARG" ;;
d) DRY_RUN=true ;;
h) usage ;;
:) echo "Option -$OPTARG requires an argument." >&2; usage ;;
\?) echo "Unknown option: -$OPTARG" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && { echo "Error: target required" >&2; usage; }
TARGET="$1"
# Use mktemp for safe temp directories
TMPDIR_WORK=$(mktemp -d)
# Log with timestamps
log() { echo "[$(date '+%Y-%m-%dT%H:%M:%S')] $*"; }
log "Starting deploy: env=$ENV target=$TARGET dry_run=$DRY_RUN"
# Dry-run wrapper
run() {
if [[ "$DRY_RUN" == true ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
run rsync -av --exclude='.git' "./" "deploy@${TARGET}:/opt/app/"
log "Deploy complete"Create a systemd service unit
A service + timer pair for a scheduled task (replacing cron):
# /etc/systemd/system/db-backup.service
[Unit]
Description=Database backup
After=network-online.target postgresql.service
Wants=network-online.target
# Prevent starting if PostgreSQL is not running
Requires=postgresql.service
[Service]
Type=oneshot
User=backup
Group=backup
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/backups/db
PrivateTmp=true
ExecStart=/usr/local/bin/db-backup.sh
StandardOutput=journal
StandardError=journal
# Retry on failure
Restart=on-failure
RestartSec=60
[Install]
WantedBy=multi-user.target# /etc/systemd/system/db-backup.timer
[Unit]
Description=Run database backup daily at 02:00
Requires=db-backup.service
[Timer]
# Run at 02:00 every day
OnCalendar=*-*-* 02:00:00
# Run immediately if last run was missed (e.g., server was down)
Persistent=true
# Randomize start within 5 minutes to avoid thundering herd
RandomizedDelaySec=300
[Install]
WantedBy=timers.target# Deploy and enable
sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
# Inspect
systemctl status db-backup.timer
systemctl list-timers db-backup.timer
journalctl -u db-backup.service -n 50Configure SSH hardening
Edit /etc/ssh/sshd_config with these settings:
# /etc/ssh/sshd_config - production hardening
# Use SSH protocol 2 only (default in modern OpenSSH, make it explicit)
Protocol 2
# Disable root login - use a dedicated admin user with sudo
PermitRootLogin no
# Disable password authentication - key-based only
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
# Disable X11 forwarding unless needed
X11Forwarding no
# Limit login window to prevent slowloris-style attacks
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 10
# Only allow specific groups to SSH
AllowGroups sshusers admins
# Restrict ciphers, MACs, and key exchange to modern algorithms
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
# Use privilege separation
UsePrivilegeSeparation sandbox
# Log at verbose level to capture key fingerprints on auth
LogLevel VERBOSE
# Set idle timeout: disconnect after 15 minutes of inactivity
ClientAliveInterval 300
ClientAliveCountMax 3# Validate before restarting
sudo sshd -t
# Restart sshd (keep current session open until verified)
sudo systemctl restart sshd
# Verify from a NEW session before closing the old one
ssh -v user@hostNever close your existing SSH session until you have verified a new session works. A broken sshd config can lock you out of the server permanently.
Debug networking issues
For detailed networking debugging workflow and firewall configuration (ufw and iptables), see references/networking-and-firewall.md.
Manage disk space
# Check disk usage overview
df -hT
# -h: human readable -T: show filesystem type
# Find large directories (top 10, depth-limited)
du -h --max-depth=2 /var | sort -rh | head -10
# Interactive disk usage explorer (install ncdu first)
ncdu /var/log
# Find large files
find /var -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -rh
# Check journal size and truncate if needed
journalctl --disk-usage
sudo journalctl --vacuum-size=500M # keep last 500MB
sudo journalctl --vacuum-time=30d # keep last 30 days# /etc/logrotate.d/myapp - custom log rotation
/var/log/myapp/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
systemctl reload myapp 2>/dev/null || true
endscript
}# Test logrotate config without running it
logrotate --debug /etc/logrotate.d/myapp
# Force a rotation run
logrotate --force /etc/logrotate.d/myappMonitor processes
# Overview: CPU, memory, load average
top -b -n 1 -o %CPU | head -20 # batch mode, sort by CPU
htop # interactive, colored, tree view
# Find what a process is doing
pid=$(pgrep -x nginx | head -1)
# Open files and network connections
lsof -p "$pid" # all open files
lsof -p "$pid" -i # only network connections
lsof -i :8080 # what process owns port 8080
# System calls (strace) - use when a process behaves unexpectedly
strace -p "$pid" -f -e trace=network # network syscalls only
strace -p "$pid" -f -c # count syscall frequency (summary)
strace -c cmd arg # profile syscalls of a new command
# Memory inspection
cat /proc/"$pid"/status | grep -E 'Vm|Threads'
cat /proc/"$pid"/smaps_rollup # detailed memory breakdown
# Check zombie/defunct processes
ps aux | awk '$8 == "Z" {print}'
# Kill process tree (all children too)
kill -TERM -"$(ps -o pgid= -p "$pid" | tr -d ' ')"Error handling
| Error | Likely cause | Resolution |
|---|---|---|
Permission denied (publickey) on SSH |
Wrong key, wrong user, or sshd config restricts access | Check ~/.ssh/authorized_keys permissions (must be 600), verify AllowGroups in sshd_config, run ssh -v for detail |
Unit not found in systemctl |
Unit file not in a searched path or daemon not reloaded | Run systemctl daemon-reload, verify unit file path with systemctl show -p FragmentPath |
Job for X failed. See journalctl -xe |
Service exited non-zero at startup | Run journalctl -u service-name -n 50 --no-pager to see startup errors |
RTNETLINK answers: File exists when adding route |
Route already exists in the routing table | Check with ip route show, delete conflicting route with ip route del, then re-add |
iptables: No chain/target/match by that name |
Missing kernel module or typo in chain name | Load module with modprobe xt_conntrack, check spelling of built-in chains (INPUT, OUTPUT, FORWARD) |
| Script exits unexpectedly with no error message | set -e triggered on a command that returned non-zero |
Add ` |
Gotchas
set -esilently swallows exit codes in conditionals -if cmd; thenorcmd || truesuppress the exit code and bypassset -e. This is expected behavior but surprises people when a critical command fails without aborting the script. Use explicit exit code checks (rc=$?; if [[ $rc -ne 0 ]]; then) when a failure must be detected inside a conditional.Restarting sshd locks you out if config is invalid - Always run
sshd -tto validate config before restarting. Then restart sshd and verify from a new terminal session before closing the old one. A brokensshd_configor missingauthorized_keysfile after a restart leaves the server completely inaccessible.iptablesrules are not persistent across reboots by default - Rules applied viaiptablescommands are in-memory only. On reboot, they vanish. Useiptables-save > /etc/iptables/rules.v4and installiptables-persistent, or useufwwhich handles persistence automatically.systemd
After=is ordering-only, not a dependency -After=network.targetdoes not guarantee the network is actually up; it only means the service starts after that target is reached. UseAfter=network-online.targetcombined withWants=network-online.targetif the service genuinely needs a routed network connection at start.duanddfdisagree when deleted files are held open - A process that deleted a large log file but still has an open file descriptor causesdfto show the disk as full whiledushows free space. Find the culprit withlsof +L1(lists open files with zero link count) and restart or signal the process to release the handle.
References
For detailed guidance on specific security domains, read the relevant file from
the references/ folder:
references/security-hardening.md- SSH, firewall, user management, kernel hardening params, and audit logging checklistreferences/networking-and-firewall.md- Network debugging workflow (top-down), ufw and iptables firewall rule configuration
Only load the references file when the current task requires it - it is detailed and will consume context.
References
networking-and-firewall.md
Networking Debugging and Firewall Configuration
Debug networking issues
Follow this workflow top-down:
# 1. Check interface state and IP assignment
ip addr show
ip link show
# 2. Check routing table
ip route show
# Expected: default route via gateway, local subnet route
# 3. Test gateway reachability
ping -c 4 $(ip route | awk '/default/ {print $3}')
# 4. Test DNS resolution
dig +short google.com @8.8.8.8 # direct to external resolver
resolvectl query google.com # use system resolver (systemd-resolved)
cat /etc/resolv.conf # check configured resolvers
# 5. Check listening ports and owning processes
ss -tulpn
# -t: TCP -u: UDP -l: listening -p: process -n: no name resolution
# 6. Test specific port connectivity
nc -zv 10.0.0.5 5432 # check if port is open
timeout 3 bash -c "</dev/tcp/10.0.0.5/5432" && echo open || echo closed
# 7. Trace the path
traceroute -n 8.8.8.8 # ICMP path tracing
mtr --report 8.8.8.8 # continuous path with stats (better than traceroute)
# 8. Capture traffic for deep inspection
# Capture all traffic on eth0 to/from a host on port 443
sudo tcpdump -i eth0 -n host 10.0.0.5 and port 443 -w /tmp/capture.pcap
# Quick view without saving
sudo tcpdump -i eth0 -n port 53 # watch DNS queries liveSet up firewall rules
Using ufw for simple servers, raw iptables for complex setups:
# --- ufw approach (recommended for most servers) ---
# Reset to defaults
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (do this BEFORE enabling to avoid lockout)
sudo ufw allow 22/tcp comment 'SSH'
# Web server
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Allow specific source IP for admin access
sudo ufw allow from 192.168.1.0/24 to any port 5432 comment 'Postgres from internal'
# Enable and verify
sudo ufw --force enable
sudo ufw status verbose# --- iptables approach for precise control ---
# Flush existing rules
iptables -F
iptables -X
# Default policies: drop everything
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established/related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (rate-limit to prevent brute force)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH --rsource
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH --rsource -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP/HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# Save rules
iptables-save > /etc/iptables/rules.v4 security-hardening.md
Linux Security Hardening Reference
Opinionated, production-grade security hardening checklist for Linux servers. Apply these in order: SSH first (so you don't lock yourself out), then firewall, then user management, then kernel hardening, then audit logging. When in doubt, be more restrictive.
1. SSH Hardening
sshd_config settings
# /etc/ssh/sshd_config
# Authentication
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Session limits
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 10
MaxStartups 10:30:60
# Forwarding (disable unless needed)
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
# Idle timeout (disconnect after ~15 min idle)
ClientAliveInterval 300
ClientAliveCountMax 3
# Access control
AllowGroups sshusers admins
# Modern algorithms only
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
# Logging
LogLevel VERBOSE
SyslogFacility AUTHSSH key management
# Generate a strong key pair (ed25519 preferred over RSA)
ssh-keygen -t ed25519 -C "user@hostname-$(date +%Y-%m)" -f ~/.ssh/id_ed25519
# If RSA is required by legacy systems, use 4096-bit
ssh-keygen -t rsa -b 4096 -C "user@hostname" -f ~/.ssh/id_rsa
# Deploy a public key to a remote server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host
# Correct permissions on key files (critical - SSH refuses to use wrong permissions)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys
# Audit authorized_keys on a server
sudo find /home -name authorized_keys -exec cat {} \; -printSSH checklist
-
PermitRootLogin no -
PasswordAuthentication no -
X11Forwarding no -
AllowGroupsset to restrict access to a specific group -
MaxAuthTries 4or lower -
LoginGraceTime 30or lower - Modern cipher suite configured (no arcfour, 3DES, MD5 MACs)
-
sshd -tvalidates config before restart - New session verified before closing old one after config change
-
AllowTcpForwarding nounless port forwarding is explicitly needed - SSH port-knock or non-standard port + firewall if high brute-force exposure
2. Firewall
ufw (Ubuntu/Debian - recommended for most servers)
# Initial lockdown
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routed
# SSH - always add BEFORE enabling
sudo ufw allow from 203.0.113.0/24 to any port 22 comment 'SSH from office'
# If SSH must be public, rate-limit it
sudo ufw limit 22/tcp comment 'SSH rate-limited'
# Web
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Internal services - restrict by source IP only
sudo ufw allow from 10.0.0.0/8 to any port 5432 comment 'PostgreSQL internal only'
sudo ufw allow from 10.0.0.0/8 to any port 6379 comment 'Redis internal only'
# Enable with verbose output for review
sudo ufw --force enable
sudo ufw status verbose
# Logging
sudo ufw logging mediumiptables (advanced - for complex rule sets)
#!/usr/bin/env bash
# /usr/local/sbin/firewall-setup.sh
set -euo pipefail
IPT="iptables"
# Flush
$IPT -F; $IPT -X; $IPT -Z
$IPT -t nat -F; $IPT -t nat -X
$IPT -t mangle -F; $IPT -t mangle -X
# Default policies
$IPT -P INPUT DROP
$IPT -P FORWARD DROP
$IPT -P OUTPUT ACCEPT
# Loopback
$IPT -A INPUT -i lo -j ACCEPT
# Established/related connections
$IPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Drop invalid packets
$IPT -A INPUT -m conntrack --ctstate INVALID -j DROP
# SSH with brute-force rate limiting
$IPT -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH_LIMIT --rsource
$IPT -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH_LIMIT --rsource -j LOG \
--log-prefix "SSH brute-force: " --log-level 4
$IPT -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH_LIMIT --rsource -j DROP
$IPT -A INPUT -p tcp --dport 22 -j ACCEPT
# Web
$IPT -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# ICMP (ping) - allow but rate-limit
$IPT -A INPUT -p icmp --icmp-type echo-request -m limit --limit 5/sec -j ACCEPT
$IPT -A INPUT -p icmp -j DROP
# Log and drop everything else
$IPT -A INPUT -j LOG --log-prefix "iptables-drop: " --log-level 4
$IPT -A INPUT -j DROP
# Persist
iptables-save > /etc/iptables/rules.v4Firewall checklist
- Default INPUT policy is DROP
- SSH is explicitly allowed before enabling firewall
- Internal services (databases, cache) are restricted to internal IPs
- ICMP rate-limited (not blocked - needed for MTU path discovery)
- Firewall rules persist across reboots (
iptables-persistentor ufw enabled) - Outbound traffic filtered if server has fixed external communication patterns
- Rules audited with
ufw status verboseoriptables -L -n -v --line-numbers
3. User Management
Principle of least privilege
# Create a dedicated service account with no shell and no home directory login
sudo useradd \
--system \
--no-create-home \
--shell /usr/sbin/nologin \
--comment "MyApp service account" \
myapp
# Lock the account password (key-only or service-only access)
sudo passwd -l myapp
# Create an app directory owned by the service user
sudo mkdir -p /opt/myapp
sudo chown myapp:myapp /opt/myapp
sudo chmod 750 /opt/myappsudo hardening
# Edit sudoers with visudo - never directly edit /etc/sudoers
sudo visudo
# Grant specific command access, not full sudo
# In /etc/sudoers or /etc/sudoers.d/admins:
# %admins ALL=(ALL) /usr/bin/systemctl restart myapp, /usr/bin/journalctl
# Require password for sudo (disable NOPASSWD in production)
# %admins ALL=(ALL) ALL
# Limit sudo session timeout
# Defaults timestamp_timeout=5
# Log all sudo usage (usually enabled by default)
# Defaults logfile=/var/log/sudo.logAccount auditing
# List all users with login shells (potential interactive login accounts)
grep -v '/nologin\|/false' /etc/passwd | awk -F: '{print $1, $7}'
# List users with UID 0 (should only be root)
awk -F: '$3 == 0 {print $1}' /etc/passwd
# Find accounts with empty passwords (security risk)
sudo awk -F: '($2 == "" || $2 == "!") {print $1}' /etc/shadow
# Check sudo group members
getent group sudo wheel admins 2>/dev/null
# Audit recent logins
last -n 20
lastb -n 20 # failed logins
# Check SSH authorized_keys across all home directories
sudo find /root /home -name authorized_keys -ls 2>/dev/nullUser management checklist
- Root account has no SSH access (
PermitRootLogin no) - Service accounts use
/usr/sbin/nologinshell and locked passwords - No user accounts with UID 0 except root
-
sudois required for privileged operations (no shared root password) - Sudoers entries are command-specific, not blanket
ALL - Inactive user accounts are disabled (
usermod -L) - Default password policy enforced (
pam_pwqualityorpwquality.conf) -
/etc/shadowhas mode 640 or 000 -
/etc/passwdhas mode 644
4. Kernel Hardening (sysctl)
# /etc/sysctl.d/99-hardening.conf
# Network: prevent common attacks
# Disable IP forwarding (unless this is a router)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 3
net.ipv4.tcp_synack_retries = 2
# Ignore ICMP redirects (prevent MITM route injection)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Log martian packets (invalid source/destination IPs)
net.ipv4.conf.all.log_martians = 1
# Prevent IP spoofing (reverse path filtering)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Disable ICMP broadcast responses
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Ignore bogus ICMP errors
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Filesystem: prevent some privilege escalation attacks
# Restrict /proc/pid visibility to own processes
kernel.hidepid = 2
# Restrict ptrace to parent process only (limits debugger-based exploits)
kernel.yama.ptrace_scope = 1
# Disable core dumps from setuid programs
fs.suid_dumpable = 0
# Restrict dmesg to root (prevents info leaks)
kernel.dmesg_restrict = 1
# Randomize memory layout (ASLR)
kernel.randomize_va_space = 2
# Restrict /proc/kallsyms and kernel pointers (prevents info leaks)
kernel.kptr_restrict = 2
# Disable magic SysRq key in production
kernel.sysrq = 0# Apply sysctl settings immediately without reboot
sudo sysctl --system
# Verify a specific setting
sysctl net.ipv4.tcp_syncookiesKernel hardening checklist
- SYN cookies enabled (
net.ipv4.tcp_syncookies = 1) - IP forwarding disabled (unless server is a router)
- ICMP redirects disabled
- Reverse path filtering enabled (
rp_filter = 1) - ASLR enabled (
randomize_va_space = 2) -
ptrace_scope = 1(Yama LSM) -
dmesg_restrict = 1 -
kptr_restrict = 2 - Settings persisted in
/etc/sysctl.d/
5. Audit Logging
auditd setup
# Install
sudo apt-get install auditd audispd-plugins # Debian/Ubuntu
sudo yum install audit # RHEL/CentOS
# Start and enable
sudo systemctl enable --now auditd# /etc/audit/rules.d/99-hardening.rules
# Delete all existing rules and set default policy
-D
-b 8192
-f 1
# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitor login/logout events
-w /var/log/wtmp -p wa -k logins
-w /var/log/btmp -p wa -k logins
-w /var/log/lastlog -p wa -k logins
# Monitor privilege escalation (sudo, su)
-w /usr/bin/sudo -p x -k privilege_escalation
-w /bin/su -p x -k privilege_escalation
# Monitor cron
-w /etc/cron.d/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Monitor network configuration changes
-w /sbin/iptables -p x -k firewall
-w /etc/hosts -p wa -k network_config
# Monitor kernel module loading (potential rootkit indicator)
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-a always,exit -F arch=b64 -S init_module -S delete_module -k modules
# Audit all commands run by root
-a always,exit -F arch=b64 -F uid=0 -S execve -k root_commands
# Make audit rules immutable (requires reboot to change - use on locked-down servers)
# -e 2# Reload audit rules
sudo augenrules --load
# Search audit log
sudo ausearch -k identity -ts today
sudo ausearch -k privilege_escalation -ts recent
sudo ausearch -k sshd_config
# Generate a human-readable report
sudo aureport --auth --summary
sudo aureport --login --summary
sudo aureport --failed --summarySystem log monitoring
# Key log files to monitor
/var/log/auth.log # (Debian/Ubuntu) authentication events
/var/log/secure # (RHEL/CentOS) authentication events
/var/log/audit/audit.log # auditd events
/var/log/syslog # general system messages
/var/log/kern.log # kernel messages (including iptables drops if LOG target set)
# Watch auth log live for suspicious activity
sudo tail -f /var/log/auth.log | grep -E 'Failed|Invalid|Accepted|sudo'
# Summarize failed SSH login attempts
sudo grep "Failed password" /var/log/auth.log | \
awk '{print $11}' | sort | uniq -c | sort -rn | head -20fail2ban (automated brute-force blocking)
# Install
sudo apt-get install fail2ban
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 4
backend = systemd
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 7200sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd # check bans
sudo fail2ban-client set sshd unbanip 1.2.3.4 # unban an IPAudit logging checklist
-
auditdinstalled and running - Audit rules cover: passwd/shadow/sudoers changes, login events, privilege escalation
- SSH authentication events logged at VERBOSE level
- Log files are protected from modification by non-root users
- Logs are shipped to a remote/central log server (so local tampering doesn't erase evidence)
-
fail2banor equivalent configured for SSH brute-force blocking - Log rotation configured to prevent disk exhaustion
- Alerts defined for: spike in auth failures, sudo usage, new user creation
6. System Update and Patch Management
# Debian/Ubuntu: enable automatic security updates
sudo apt-get install unattended-upgrades apt-listchanges
# /etc/apt/apt.conf.d/50unattended-upgrades
# Unattended-Upgrade::Allowed-Origins {
# "${distro_id}:${distro_codename}-security";
# };
# Unattended-Upgrade::Automatic-Reboot "true";
# Unattended-Upgrade::Automatic-Reboot-Time "03:00";
sudo dpkg-reconfigure --priority=low unattended-upgrades
# RHEL/CentOS: dnf-automatic for security updates
sudo dnf install dnf-automatic
sudo sed -i 's/upgrade_type = default/upgrade_type = security/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timerPackage management checklist
- OS and packages are on a supported version with active security updates
- Automatic security updates enabled (or a manual patching cadence enforced)
- Kernel and libc updates applied regularly (require reboot to take effect)
- Unnecessary packages are removed (
apt autoremove,dnf autoremove) - No development tools (compilers, build-essential) installed on production servers
- Container base images rebuilt when base OS packages are patched
Quick Reference: What to do first on a new server
# 1. Update all packages
sudo apt-get update && sudo apt-get upgrade -y
# 2. Create admin user and add to sudo group
sudo useradd -m -s /bin/bash -G sudo adminuser
sudo passwd adminuser
# 3. Deploy SSH public key for admin user
sudo mkdir -p /home/adminuser/.ssh
sudo cp ~/.ssh/authorized_keys /home/adminuser/.ssh/
sudo chown -R adminuser:adminuser /home/adminuser/.ssh
sudo chmod 700 /home/adminuser/.ssh
sudo chmod 600 /home/adminuser/.ssh/authorized_keys
# 4. Test login as new admin user in a NEW session before next step
# ssh adminuser@server
# 5. Harden sshd_config (PermitRootLogin no, PasswordAuthentication no)
# 6. Validate: sudo sshd -t
# 7. Restart: sudo systemctl restart sshd
# 8. Configure firewall (ufw or iptables)
# 9. Apply sysctl hardening
# 10. Install and configure auditd + fail2ban
# 11. Set up automatic security updates
# 12. Run a security scanner (Lynis, OpenSCAP) for a baseline audit# Lynis security audit (quick baseline)
sudo apt-get install lynis
sudo lynis audit system
# Review output: hardening index, warnings, and suggestions Frequently Asked Questions
What is linux-admin?
Use this skill when managing Linux servers, writing shell scripts, configuring systemd services, debugging networking, or hardening security. Triggers on bash scripting, systemd units, iptables, firewall, SSH configuration, file permissions, process management, cron jobs, disk management, and any task requiring Linux system administration.
How do I install linux-admin?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill linux-admin in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support linux-admin?
linux-admin works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.