Linux Server Essentials: What Every Developer Should Know (2026)
You deployed your app — now what? Here's everything you need to know to keep a Linux server running smoothly.
First Things to Do on a New Server
# 1. Update everything
sudo apt update && sudo apt upgrade -y
# 2. Create a non-root user (never run as root!)
sudo adduser deploy
sudo usermod -aG sudo deploy
# 3. SSH hardening (CRITICAL!)
sudo nano /etc/ssh/sshd_config
# Change these:
PermitRootLogin no
PasswordAuthentication no # Key-only auth!
PubkeyAuthentication yes
Port 2222 # Non-standard port (optional security)
MaxAuthTries 3
sudo systemctl restart sshd
# Before closing this session: test SSH from another terminal!
# 4. Firewall setup
sudo ufw allow 2222/tcp # Your SSH port
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable # Enable firewall
# 5. Automatic security updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades # Select "Yes"
# 6. Time synchronization (important for logs/certificates!)
sudo timedatectl set-timezone UTC
sudo apt install chrony && sudo systemctl enable chrony
# 7. Fail2Ban (auto-block brute force attackers)
sudo apt install fail2ban
sudo systemctl enable fail2ban
Monitoring: What's Happening on Your Server?
# Quick system overview:
htop # Interactive process viewer (q to quit)
free -h # Memory usage
df -h # Disk space
uptime # Load average + uptime
nethogs # Network bandwidth by process
iotop # Disk I/O by process
# Real-time log monitoring:
tail -f /var/log/syslog # System logs
tail -f /var/log/nginx/access.log # Nginx access logs
tail -f /var/log/nginx/error.log # Nginx error logs
journalctl -u nginx -f # Systemd service logs (follow mode)
# Resource usage deep dive:
vmstat 1 # CPU, memory, IO every second
iostat -xz 1 # Disk I/O stats per device
sar -u 1 10 # CPU history (10 samples, 1s apart)
mpstat -P ALL 1 # Per-CPU core usage
# Network diagnostics:
ss -tlnp # Listening ports + PIDs
netstat -tulpn # Alternative (older)
ip addr show # Network interfaces
curl -I https://localhost # Test local HTTP response
dig example.com # DNS lookup
traceroute google.com # Route tracing
tcpdump -i eth0 port 80 # Capture HTTP traffic (debug only!)
# Find what's using resources:
ps aux --sort=-%mem | head -10 # Top 10 memory consumers
ps aux --sort=-%cpu | head -10 # Top 10 CPU consumers
du -sh /* | sort -rh | head -10 # Largest directories
lsof +L1 # Find deleted files still holding disk space
User & Permission Management
# User management
sudo useradd -m -s /bin/bash newuser # Create with home directory
sudo passwd newuser # Set password
sudo userdel -r olduser # Delete with home directory
sudo usermod -aG docker newuser # Add to group
# File permissions (the most important concept!)
ls -la file.txt
# Output: -rw-r--r-- 1 user group 4096 Jun 10 date file.txt
# │││───┘ └─┬┘ └┬┘ └────┬────
# │││ │ │ filename
# │││ │ └─ group owner
# │││ └─ user owner
# ││└──────── others permissions
# │└───────── group permissions
# └────────── user/owner permissions
#
# r = read (4), w = write (2), x = execute (1)
# 7 = rwx, 6 = rw-, 5 = r-x, 4 = r--, etc.
chmod 755 script.sh # Owner=rwx, Group+Others=rx
chmod 600 secret.key # Only owner can read/write
chmod 644 public.html # Owner=rw, Others=ro
chown www-data:www-data /var/www/html # Change owner+group
chgrp deployers /opt/app # Change group only
# Special permissions:
chmod +s /usr/bin/sudo # Setuid bit (run as owner)
chmod +t /tmp # Sticky bit (only owner can delete own files)
umask 022 # Default permission mask for new files
# Sudo management
sudo visudo # Edit sudoers safely
# Add specific rules:
# deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
# deploy ALL=(ALL) NOPASSWD: /usr/bin/docker *
Process Management
# Finding processes
pgrep -fa "node" # Find node processes with command line
ps aux | grep node # Filter ps output
pidof nginx # Get PID of named process
# Controlling processes
kill PID # Graceful stop (SIGTERM)
kill -9 PID # Force kill (SIGKILL) — last resort
kill -HUP PID # Reload config (SIGHUP)
pkill -f "node server" # Kill by pattern
# Background jobs
command & # Run in background
nohup long-running-job & # Survives terminal close
disown # Detach from shell entirely
Ctrl+Z # Suspend current job
bg # Resume suspended job in background
fg # Bring to foreground
jobs # List background jobs
# Systemd services (modern way to manage daemons)
systemctl status nginx # Service status
systemctl start nginx # Start
systemctl stop nginx # Stop
systemctl restart nginx # Restart
systemctl reload nginx # Reload config (no downtime!)
systemctl enable nginx # Start on boot
systemctl disable nginx # Don't start on boot
journalctl -u nginx -n 50 # Last 50 log lines
journalctl -u nginx --since "2026-06-10" # Logs since date
# Creating your own systemd service:
# /etc/systemd/system/myapp.service
#[Unit]
#Description=My Node.js App
#After=network.target
#
#[Service]
#Type=simple
#User=deploy
#WorkingDirectory=/home/deploy/app
#ExecStart=/usr/bin/node server.js
#Restart=always
#RestartSec=10
#Environment=NODE_ENV=production
#Environment=PORT=3000
#
#[Install]
#WantedBy=multi-user.target
#
# Then: sudo systemctl daemon-reload && sudo systemctl enable myapp
Disk Management
# Usage overview
df -h # Human-readable disk usage
du -sh * # Directory sizes in current location
du -sh /home/deploy/* | sort -rh # Sorted by size
# Find large files
find /var/log -type f -size +100M # Files > 100MB
find /tmp -type f -mtime +7 -delete # Delete files older than 7 days
find . -type f -name "*.log" -exec ls -lh {} \; # List all log files with sizes
# Clean up space
sudo journalctl --vacuum-size=500M # Limit journal logs
sudo apt autoremove # Remove unused packages
docker system prune -a # Docker cleanup
npm cache clean --force # npm cache
# Monitor disk I/O
iostat -xz 1 # Continuous I/O stats
iotop # Per-process I/O
# LVM (if using logical volumes):
lvdisplay # Show logical volumes
lvextend -l +100%FREE /dev/mapper/vg-root # Extend volume
resize2fs /dev/mapper/vg-root # Resize filesystem (no data loss!)
Networking Essentials
# Interface info
ip addr show # All interfaces and IPs
ip link show # Link status (up/down)
ip route show # Routing table
# DNS resolution
/etc/resolv.conf # DNS servers
dig @8.8.8.8 example.com # Query specific DNS server
nslookup example.com # Simple lookup
# Port checking
ss -tlnp | grep :3000 # Is port 3000 listening?
nc -zv localhost 3000 # Test if port responds
curl -v http://localhost:3000 # Full HTTP request/response
# Firewall (ufw)
sudo ufw status verbose # Current rules
sudo ufw allow from 192.168.1.0/24 # Allow IP range
sudo ufw deny in 22 # Block port
sudo ufw logging on # Enable logging
# SSH tunneling (secure remote access)
ssh -L 8080:localhost:3000 user@server # Forward local 8080 → remote 3000
ssh -R 3000:localhost:8080 user@server # Forward remote 3000 → local 8080
ssh -D 1080 user@server # SOCKS proxy through SSH
# SSL/TLS certificates (Let's Encrypt)
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run # Test renewal
sudo certbot certificates # List installed certs
Automation & Backups
# Cron jobs (scheduled tasks)
crontab -e # Edit cron table
# Format: minute hour day-of-month month day-of-week command
# Examples:
# 0 2 * * * /usr/local/bin/backup.sh # Daily at 2 AM
# */15 * * * * /usr/local/bin/healthcheck.sh # Every 15 minutes
# 0 0 * * 0 find /tmp -type f -mtime +7 -delete # Weekly Sunday midnight cleanup
# Backup strategy:
#!/bin/bash
# backup.sh — Simple incremental backup
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
SOURCE="/home/deploy/app"
# Database backup
pg_dump $DATABASE_URL > "$BACKUP_DIR/db_$DATE.sql"
# Application files (tar.gz with compression)
tar -czf "$BACKUP_DIR/app_$DATE.tar.gz" $SOURCE
# Keep only last 7 days of backups
find "$BACKUP_DIR" -mtime +7 -delete
echo "Backup completed: $DATE"
# Make it executable: chmod +x backup.sh
# Schedule: crontab -e → 0 3 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1
What's the first thing you do when you get a new server? What did I miss?
Follow @armorbreak for more practical developer guides.
Top comments (0)