Linux Server Essentials: What Every Developer Should Know (2026)
You deployed your app — now what? Here's how to keep it running, secure, and fast.
First Things After SSH In
# 1. Who am I and where am I?
whoami # Current user (should NOT be root for daily work)
hostname # Server name
uname -a # Kernel info
cat /etc/os-release # OS version
# 2. What's running on this machine?
free -h # Memory usage
df -h # Disk space
nproc # CPU cores
uptime # Uptime + load average
top # Resource-hungry processes (q to quit)
# 3. What's listening? (Security check!)
ss -tlnp # TCP ports + PIDs
ss -ulnp # UDP ports
# If you see unexpected ports → investigate immediately!
# 4. Who else is here?
w # Logged-in users + what they're doing
last # Recent login history
lastb # Failed login attempts (if enabled)
User & Security Setup
# NEVER use root for daily operations!
# Create a user with sudo access:
sudo adduser deploy # Create new user
sudo usermod -aG sudo deploy # Add to sudo group
# Set up SSH key auth (disable password login!):
# On YOUR local machine:
ssh-keygen -t ed25519 # Generate key pair
ssh-copy-id deploy@your-server-ip # Copy public key to server
# On server: disable password authentication:
sudo nano /etc/ssh/sshd_config
# Change: PasswordAuthentication no
# Change: PubkeyAuthentication yes
# Change: PermitRootLogin no
sudo systemctl restart sshd
# Firewall basics (ufw):
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh # Don't lock yourself out!
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 3000/tcp # Your app port (or use reverse proxy)
sudo ufw enable # Activate firewall
sudo ufw status verbose # Check rules
# Fail2Ban (auto-ban brute force attackers):
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
# [sshd]
# enabled = true
# maxretry = 3
# bantime = 3600
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Process Management: Keep Your App Running
# Option 1: systemd service (recommended for production)
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/app
ExecStart=/home/deploy/.nvm/v22/bin/node /home/deploy/app/server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
# Manage your service:
sudo systemctl daemon-reload # After editing .service file
sudo systemctl start myapp # Start
sudo systemctl stop myapp # Stop
sudo systemctl status myapp # Check status
sudo systemctl restart myapp # Restart
sudo journalctl -u myapp -f # Follow logs live!
# Enable auto-start on boot:
sudo systemctl enable myapp
# Option 2: PM2 (easier for Node.js, good for development)
npm install -g pm2
pm2 start server.js --name "myapp"
pm2 start server.js --name "myapp" -i max # Cluster mode (all CPUs)
pm2 list # List apps
pm2 logs myapp # View logs
pm2 monit # Interactive monitor
pm2 save # Save process list (survives reboot)
pm2 startup # Generate startup script (run output!)
# Option 3: screen/tmux (quick and dirty)
tmux new -s myapp # Create named session
node server.js # Run inside session
Ctrl+B D # Detach (keeps running!)
tmux attach -t myapp # Reattach later
Nginx Reverse Proxy
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP → HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
location / {
proxy_pass http://127.0.0.1:3000; # Your Node.js app
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Static files cache
location /static/ {
alias /home/deploy/app/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Rate limiting zone (defined in nginx.conf)
limit_req zone=api burst=20 nodelay;
}
# SSL certificate (Let's Encrypt — free!):
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renews! Check with: sudo certbot renew --dry-run
# Test and reload Nginx:
sudo nginx -t # Test config syntax
sudo systemctl reload nginx # Reload without dropping connections
sudo systemctl restart nginx # Full restart
Monitoring & Log Management
# System logs:
journalctl -f # All system logs (follow)
journalctl -u myapp -f # Your app's logs
journalctl -u myapp --since "1 hour ago"
# Application logs (if you write your own):
tail -f /var/log/myapp/access.log
tail -f /var/log/myapp/error.log
# Log rotation (prevent disk filling up!):
sudo nano /etc/logrotate.d/myapp
# /var/log/myapp/*.log {
# daily
# missingok
# rotate 14
# compress
# delaycompress
# notifempty
# copytruncate
# }
# Quick health check script:
#!/bin/bash
# health-check.sh — run via cron every 5 minutes
APP_URL="https://example.com/health"
RESPONSE=$(curl -sf -o /dev/null -w "%{http_code}" "$APP_URL")
if [ "$RESPONSE" != "200" ]; then
echo "$(date): DOWN (HTTP $RESPONSE)" >> /var/log/health-check.log
# Optional: send alert (email, webhook, etc.)
fi
Automated Backups
#!/bin/bash
# backup.sh — Daily database backup with retention
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
# PostgreSQL backup:
pg_dump -U app mydb | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"
# Or SQLite backup:
cp /path/to/data.db "$BACKUP_DIR/db_$DATE.db"
# Delete backups older than RETENTION_DAYS:
find "$BACKUP_DIR" -mtime +$RETENTION_DAYS -delete
echo "Backup complete: $BACKUP_DIR/db_$DATE.sql.gz"
# Add to crontab (runs daily at 2 AM):
# 0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1
What's your go-to server setup command? What did you wish you knew when you started?
Follow @armorbreak for more practical developer guides.
Top comments (0)