🐧

Linux Related

48 notes  •  Linux & Server Admin

Zip a Directory in Linux

This guide covers how to zip a directory in Linux using the zip command, including options to exclude specific file types or subdirectories.

Prerequisites

  • zip installed (apt install zip or yum install zip)
  • Access to the directory you want to compress

Steps

  1. Zip a directory recursively:
    zip -r archive.zip my_directory/
  2. Zip current directory, excluding specific file types:
    zip -r zipfile.zip . -x "*.zip" -x "*.log" -x "*.gz" -x "*.tgz"
  3. Zip current directory, excluding a subdirectory:
    zip -r archive.zip . -x ./image/\*
  4. Zip excluding the .git directory:
    zip -r files.zip . -x '*.git*'
  5. Zip each subdirectory into its own zip file:
    find /path/to/directory -mindepth 1 -type d -exec sh -c 'cd "{}" && zip -r "../{}.zip" .' \;

Verify

unzip -l archive.zip

Lists the contents of the archive without extracting.

Notes

  • The -r flag makes zip recurse into subdirectories.
  • Multiple -x patterns can be chained to exclude several file types.
  • Use tar -czf archive.tar.gz directory/ as an alternative for gzip-compressed archives.

Add GNOME Desktop to CentOS Minimal Install

A CentOS minimal install ships without a graphical desktop. This guide shows how to add the GNOME desktop environment to CentOS 6 or CentOS 7 after a minimal installation.

Prerequisites

  • CentOS 6 or 7 minimal install with internet access
  • Root or sudo privileges

Steps — CentOS 6

  1. Install core desktop packages:
    yum -y groupinstall "Desktop" "Desktop Platform" "X Window System" "Fonts"
  2. (Optional) Install additional GUI tools:
    yum -y groupinstall "Graphical Administration Tools"
    yum -y groupinstall "Internet Browser"
    yum -y groupinstall "General Purpose Desktop"
    yum -y groupinstall "Office Suite and Productivity"
    yum -y groupinstall "Graphics Creation Tools"
  3. (Optional) Install KDE instead of or alongside GNOME:
    yum -y groupinstall "KDE Desktop"
  4. Set the default boot target to graphical:
    # CentOS 6
    echo "id:5:initdefault:" >> /etc/inittab
    
    # CentOS 7
    systemctl set-default graphical.target
  5. Start the graphical session immediately (without rebooting):
    startx

Steps — CentOS 7

  1. Install the GNOME desktop group:
    yum -y groupinstall "GNOME Desktop" "Graphical Administration Tools"
  2. Set graphical boot as default:
    systemctl set-default graphical.target
  3. Reboot:
    reboot

Verify

systemctl get-default

Should return graphical.target.

Troubleshooting

  • If startx fails, check that the X Window System group was installed correctly with yum grouplist installed.
  • Ensure the system has enough RAM (at least 512 MB recommended for GNOME).

Common Ubuntu Error Fixes

This guide covers fixes for frequently encountered Ubuntu package manager and system errors.

Error 1: Hash Sum Mismatch

Symptom: E: Failed to fetch ... Hash Sum mismatch when running apt-get update.

  1. Remove stale package lists:
    sudo rm -rf /var/lib/apt/lists/*
  2. Re-run the update:
    sudo apt-get update

Error 2: Unmet Dependencies / Broken Packages

Symptom: apt-get install fails with unmet dependency errors.

  1. Fix broken dependencies:
    sudo apt-get install -f
  2. Reconfigure any partially installed packages:
    sudo dpkg --configure -a

Error 3: Could Not Get Lock

Symptom: E: Could not get lock /var/lib/dpkg/lock

  1. Kill any running apt/dpkg processes:
    sudo killall apt apt-get
  2. Remove the lock files:
    sudo rm /var/lib/apt/lists/lock
    sudo rm /var/cache/apt/archives/lock
    sudo rm /var/lib/dpkg/lock*
  3. Reconfigure dpkg and update:
    sudo dpkg --configure -a
    sudo apt-get update

Error 4: GPG Key Error (NO_PUBKEY)

Symptom: W: GPG error: ... NO_PUBKEY XXXXXXXXXXXXXXXX

  1. Import the missing key (replace the key ID):
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys XXXXXXXXXXXXXXXX
  2. Update again:
    sudo apt-get update

Error 5: add-apt-repository Command Not Found

sudo apt-get install software-properties-common

Verify

sudo apt-get update && sudo apt-get upgrade

A clean run with no errors confirms the system is healthy.

Install GitLab (MySQL) on Ubuntu 14.04

This guide walks through installing GitLab Community Edition using MySQL as the database backend on Ubuntu 14.04 LTS.

Prerequisites

  • Ubuntu 14.04 LTS server, 2 GB RAM minimum
  • Root or sudo access
  • MySQL 5.5+ installed and running
  • A domain name or IP address for GitLab

Steps

  1. Install dependencies:
    sudo apt-get update
    sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev   libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server   redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev   libicu-dev logrotate python-docutils pkg-config cmake
  2. Install Git 2.x:
    sudo apt-get install -y git

    Verify: git --version (must be 2.x or newer)

  3. Install Ruby 2.1+:
    curl -L https://get.rvm.io | bash -s stable --ruby
    source ~/.rvm/scripts/rvm
    gem install bundler --no-ri --no-rdoc
  4. Create a MySQL database and user for GitLab:
    mysql -u root -p
    CREATE USER 'git'@'localhost' IDENTIFIED BY 'your_password';
    CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
    GRANT ALL PRIVILEGES ON `gitlabhq_production`.* TO 'git'@'localhost';
    FLUSH PRIVILEGES;
    \q
  5. Create the git system user:
    sudo adduser --disabled-login --gecos 'GitLab' git
  6. Clone GitLab source:
    cd /home/git
    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-9-stable gitlab
  7. Configure GitLab:
    cd /home/git/gitlab
    sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
    sudo -u git -H cp config/database.yml.mysql config/database.yml
    # Edit config/gitlab.yml to set host, port, email settings
    # Edit config/database.yml to set MySQL password
  8. Install gems and initialize database:
    sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
    sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
  9. Install and start the init script:
    sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
    sudo update-rc.d gitlab defaults 21
    sudo service gitlab start
  10. Configure Nginx as a reverse proxy:
    sudo apt-get install -y nginx
    sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
    sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
    sudo service nginx restart

Verify

sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production

All checks should pass. Then visit http://your-server-ip in a browser. Default credentials: root / 5iveL!fe (change immediately).

Troubleshooting

  • Check GitLab logs: /home/git/gitlab/log/production.log
  • Check Nginx logs: /var/log/nginx/gitlab_error.log
  • Ensure Redis is running: service redis-server status

Find the User Apache Runs As

Apache runs worker processes under a specific system user (commonly www-data, apache, or nobody). This guide shows how to identify that user.

Prerequisites

  • Apache (apache2 or httpd) installed and running
  • Access to a terminal

Steps

  1. Check running Apache processes:
    ps aux | egrep '(apache|httpd)'

    The second column shows the username under which the worker processes run (the master process usually runs as root).

  2. Check the Apache configuration directly:
    # Debian/Ubuntu
    grep -i 'user\|group' /etc/apache2/envvars
    
    # RHEL/CentOS
    grep -i '^User\|^Group' /etc/httpd/conf/httpd.conf
  3. Alternatively, use apachectl:
    apachectl -S 2>&1 | head -20

Verify

ps aux | grep apache2 | grep -v root | head -1 | awk '{print $1}'

This prints only the username of the non-root Apache process.

Notes

  • On Debian/Ubuntu the default user is www-data.
  • On RHEL/CentOS the default user is apache.
  • File permissions for web content should match this user so Apache can read them.

Find Files Modified by Date with the find Command

The find command can search for files based on when they were last modified. This is useful for auditing changes, locating recently edited configs, or investigating security incidents.

Prerequisites

  • A Linux/Unix system with the find utility (standard on all distributions)

Key Concepts

  • -mtime N — files modified exactly N days ago
  • -mtime -N — files modified less than N days ago (within the last N days)
  • -mtime +N — files modified more than N days ago
  • -mmin N — same as mtime but in minutes
  • -newer FILE — files modified more recently than a reference file

Steps

  1. Find files modified in the last 2 days in the current directory:
    find . -mtime -2 -ls
  2. Find files modified in the last 60 minutes:
    find /var/www -mmin -60 -type f
  3. Find files modified more than 30 days ago:
    find /home -mtime +30 -type f
  4. Find files modified between 7 and 14 days ago:
    find /etc -mtime +7 -mtime -14 -type f
  5. Find files newer than a reference file:
    find /var/log -newer /tmp/reference.txt -type f
  6. List results with timestamps:
    find . -mtime -2 -type f -exec ls -la {} \;

Verify

find . -mtime -1 -type f | wc -l

Counts the number of files modified in the last 24 hours.

Notes

  • The dot (.) means start from the current directory; replace with an absolute path to search elsewhere.
  • Add -type f to limit results to files only (exclude directories).
  • Combine with -name "*.php" to narrow results by file type, useful for finding recently changed PHP files on a web server.

Clean a Hacked Site with Sucuri

When a website is compromised, Sucuri's free tools can help you inspect and clean malicious content. This guide covers using command-line tools alongside Sucuri's approach to identify and remove malware from a hacked site.

Prerequisites

  • SSH or shell access to the web server
  • lynx installed (apt install lynx or yum install lynx)
  • Access to the Sucuri SiteCheck tool: sitecheck.sucuri.net

Steps

  1. Dump the site's rendered source to inspect for injected content:
    lynx --source --dump https://example.com

    Review the output for obfuscated JavaScript, hidden iframes, or unfamiliar base64 blobs.

  2. Search the filesystem for recently modified files:
    find /var/www/html -mtime -7 -type f -name "*.php" | sort
  3. Search for common malware signatures:
    grep -rl "eval(base64_decode" /var/www/html/
    grep -rl "base64_decode(gzinflate" /var/www/html/
  4. Check for hidden files or files with unusual permissions:
    find /var/www/html -name ".*" -type f
    find /var/www/html -perm 777 -type f
  5. Remove the malicious files:
    rm /var/www/html/path/to/malicious_file.php
  6. Reset file permissions:
    find /var/www/html -type d -exec chmod 755 {} \;
    find /var/www/html -type f -exec chmod 644 {} \;
  7. Update all CMS plugins, themes, and core software, then change all passwords (FTP, SSH, database, admin).

Verify

Re-scan with the Sucuri SiteCheck tool at https://sitecheck.sucuri.net and re-run the source dump to confirm injected content is gone.

Troubleshooting

  • If malware keeps reappearing, check cron jobs: crontab -l and cat /etc/cron*.
  • Review web server access logs for the initial attack vector: /var/log/apache2/access.log or /var/log/nginx/access.log.
  • Consider setting up a Web Application Firewall (WAF) such as Sucuri's or ModSecurity to prevent reinfection.

Essential Linux Commands Reference

A quick reference of frequently used Linux commands for disk management, file operations, networking, and system inspection.

Disk and Volume Commands

# List block devices with sizes and mount points
lsblk

# Show filesystem type of a raw device
file -s /dev/xvdf

# Display disk usage by directory
df -h
du -sh /var/log/*

File Operations

# Overwrite files without confirmation prompt
yes | cp -a new/files/* destination/

# Find the location of a program or file
find / -name jre 2>/dev/null

# List directory contents with details
ls -lah

# View the last N lines of a file (follow live)
tail -n 100 -f /var/log/syslog

Text Processing

# Search for a string in files recursively
grep -rnw /path/to/dir -e "search_string"

# Count lines, words, characters in a file
wc -l filename.txt

# Display specific columns from a file
awk '{print $1, $3}' filename.txt

# Stream editor: replace text in file
sed -i 's/old_text/new_text/g' filename.txt

Process Management

# List all running processes
ps aux

# Kill a process by name
pkill process_name

# Kill a process by PID
kill -9 PID

# Show real-time process usage
top
htop

Networking

# Show network interfaces and IPs
ip addr show
ifconfig

# Check open ports and listening services
ss -tlnp
netstat -tlnp

# Test connectivity
ping -c 4 google.com
traceroute google.com

# DNS lookup
dig example.com
nslookup example.com

User and Permissions

# Show current user
whoami

# Switch to root
sudo -i

# Change file ownership
chown -R www-data:www-data /var/www/html

# Change file permissions
chmod 644 file.txt
chmod 755 directory/

System Information

# Show OS version
cat /etc/os-release
uname -a

# Show memory usage
free -h

# Show uptime and load
uptime

Debug Postfix: Config, Logs, and Mail Queues

This guide walks through diagnosing Postfix mail server issues including email delivery failures, delays, bounces, and queue backlogs.

Prerequisites

  • Postfix installed and configured
  • Root or sudo access

Check Postfix Configuration

# View effective Postfix configuration
postconf -n

# Check syntax of the main config file
postfix check

View Mail Logs

# Debian/Ubuntu
tail -f /var/log/mail.log

# RHEL/CentOS
tail -f /var/log/maillog

# Filter by recipient or sender
grep "user@example.com" /var/log/mail.log

Inspect the Mail Queue

# List all messages in the queue
mailq
# or
postqueue -p

# Show queue statistics
qshape deferred

Manage the Mail Queue

# Attempt to deliver all deferred messages immediately
postqueue -f

# Delete all messages in the queue (use with caution)
postsuper -d ALL

# Delete only deferred messages
postsuper -d ALL deferred

# Delete a specific message by queue ID
postsuper -d QUEUEID

# Put all messages on hold
postsuper -h ALL

# Release all held messages
postsuper -H ALL

Inspect a Specific Queued Message

# Show message content and headers by queue ID
postcat -q QUEUEID

Test Mail Delivery

# Send a test email
echo "Test body" | mail -s "Test subject" user@example.com

# Test SMTP connection directly
telnet localhost 25
EHLO localhost
MAIL FROM:
RCPT TO:
DATA
Subject: Test
.
QUIT

Verify

postqueue -p | tail -5

An empty queue (Mail queue is empty) confirms messages are being delivered.

Troubleshooting

  • Relay access denied: Check mynetworks and relay_domains in /etc/postfix/main.cf.
  • Connection refused on port 25: Verify Postfix is running — systemctl status postfix.
  • DNS lookup failures: Check that /etc/resolv.conf has valid nameservers.
  • TLS errors: Verify certificate paths in main.cf (smtpd_tls_cert_file, smtpd_tls_key_file).

Move /home Folder to Another Partition in Ubuntu

Moving /home to a dedicated partition allows you to reinstall the OS without losing user data. This guide covers doing it live on a running Ubuntu system.

Prerequisites

  • An additional disk or partition available (use lsblk to identify it)
  • Root privileges
  • Enough free space on the new partition to hold all data in /home

Steps

  1. Identify the new partition:
    lsblk
    sudo fdisk -l

    Note the device path, e.g., /dev/sdb1.

  2. Format the new partition (if needed):
    sudo mkfs.ext4 /dev/sdb1
  3. Mount the new partition temporarily:
    sudo mkdir /mnt/newhome
    sudo mount /dev/sdb1 /mnt/newhome
  4. Copy all data from /home to the new partition (preserving permissions):
    sudo rsync -aXS /home/. /mnt/newhome/.
  5. Verify the copy:
    diff -rq /home /mnt/newhome
  6. Rename the old /home as a backup:
    sudo mv /home /home.old
  7. Create a new empty /home mount point:
    sudo mkdir /home
  8. Unmount the temporary mount and remount on /home:
    sudo umount /mnt/newhome
    sudo mount /dev/sdb1 /home
  9. Add the new partition to /etc/fstab for persistent mounting:
    # Get the UUID
    sudo blkid /dev/sdb1
    
    # Add to /etc/fstab (replace UUID with actual value)
    UUID=your-uuid-here  /home  ext4  defaults  0  2
  10. Verify fstab and reboot:
    sudo mount -a
    sudo reboot

Verify

df -h /home

Should show the new partition mounted on /home. Confirm user files are accessible.

Cleanup

sudo rm -rf /home.old

Only remove the backup after confirming everything works correctly.

Troubleshooting

  • If login fails after the move, boot into recovery mode and check that /etc/fstab has the correct UUID.
  • Run sudo mount -a before rebooting to catch any fstab errors.

Remove Partitions from a USB Drive

USB drives used as bootable Linux installers often end up with multiple partitions that prevent normal use. This guide shows how to remove those partitions and restore the drive to a single usable partition.

Prerequisites

  • Root or sudo access
  • The USB drive unmounted
  • The device name of the USB drive (e.g., /dev/sdb) — verify carefully with lsblk

Warning: Deleting the wrong partition will cause data loss. Double-check the device name before proceeding.

Steps — Using fdisk

  1. Identify the USB drive:
    lsblk
    sudo fdisk -l
  2. Unmount all partitions on the drive:
    sudo umount /dev/sdb1
    sudo umount /dev/sdb2
  3. Open fdisk:
    sudo fdisk /dev/sdb
  4. Inside fdisk, delete all existing partitions:
    p   # print partition table
    d   # delete partition (repeat for each partition)
    p   # confirm partitions are gone
    n   # create a new primary partition (accept defaults for full drive)
    t   # set partition type (b = FAT32, 83 = Linux)
    w   # write changes and exit
  5. Format the new partition:
    # For FAT32 (compatible with most devices)
    sudo mkfs.vfat /dev/sdb1
    
    # For ext4 (Linux only)
    sudo mkfs.ext4 /dev/sdb1

Alternative: Using wipefs

# Wipe all filesystem signatures from the drive
sudo wipefs -a /dev/sdb

# Then create a new partition table
sudo fdisk /dev/sdb

Verify

lsblk /dev/sdb

Should show a single partition. Mount the drive and confirm it is writable.

Troubleshooting

  • If the drive shows as read-only, check whether it has a physical write-protect switch.
  • If fdisk reports the device is busy, make sure all partitions are unmounted.

8 Dangerous Linux Commands to Avoid

Linux executes destructive commands without warning. Understanding which commands are dangerous — and why — helps prevent accidental data loss or system damage.

1. rm -rf /

rm -rf /

Recursively deletes every file on the system starting from root. Modern Linux distributions block this with a --no-preserve-root requirement, but it remains an irreversible catastrophe if run.

2. Fork Bomb

:(){ :|:& };:

A shell function that calls itself recursively in the background. Rapidly exhausts process table and memory, crashing the system. Mitigate by setting limits in /etc/security/limits.conf.

3. Overwrite Disk with /dev/zero or /dev/random

dd if=/dev/zero of=/dev/sda

Overwrites the entire disk with zeros, destroying all data and the partition table. Used intentionally for secure erasure, but devastating when run on the wrong device.

4. Piping wget/curl Output to Shell

wget -O- https://example.com/script.sh | sh

Downloads and executes a remote script with no review. If the remote host is compromised or the URL changes, this can install malware. Always download first, inspect, then execute.

5. mv Directory to /dev/null

mv /home/user /dev/null

Moves files into a black hole — they are unrecoverable. Always use rm intentionally rather than redirecting to /dev/null.

6. chmod -R 777 /

chmod -R 777 /

Makes every file on the system world-writable. Completely destroys security model. Use targeted permission changes only.

7. Redirect Output to an Existing File Without Backup

command > /etc/important-config.conf

The > redirect truncates the file before writing. If the command fails or produces no output, the original file is lost. Use >> to append, or back up first.

8. Downloading and Running Unknown Scripts as Root

sudo bash /tmp/unknown_script.sh

Running scripts obtained from untrusted sources as root grants them full system access. Always read and understand a script before executing it with elevated privileges.

Safe Practices

  • Test destructive commands on non-production systems first.
  • Use echo before destructive commands to preview what they will do.
  • Back up critical data before any system operation.
  • Avoid running as root unless absolutely necessary — use sudo for specific commands.
  • Set ulimit -u 100 to limit fork bombs for normal users.

Add Colors to Shell Scripts

Shell scripts can use ANSI escape codes to display colored text in the terminal. This makes output easier to read and helps highlight warnings, errors, and status messages.

ANSI Color Code Reference

ColorForegroundBackground
Black3040
Red3141
Green3242
Yellow3343
Blue3444
Magenta3545
Cyan3646
White3747

Escape Sequence Format

\e[STYLE;COLOR_CODEm text here \e[0m
  • \e[0m — Reset to default (always terminate colored output with this)
  • \e[1m — Bold
  • \e[2m — Dim
  • \e[4m — Underline

Steps: Define Color Variables in a Script

  1. Define reusable color variables:
    #!/bin/bash
    RED='\e[31m'
    GREEN='\e[32m'
    YELLOW='\e[33m'
    BLUE='\e[34m'
    BOLD='\e[1m'
    RESET='\e[0m'
  2. Use echo -e to print colored text:
    echo -e "${GREEN}Success:${RESET} Operation completed."
    echo -e "${RED}Error:${RESET} Something went wrong."
    echo -e "${YELLOW}Warning:${RESET} Disk space is low."
    echo -e "${BOLD}${BLUE}Info:${RESET} Starting process..."
  3. Create helper functions for consistent messaging:
    info()    { echo -e "\e[34m[INFO]\e[0m $*"; }
    success() { echo -e "\e[32m[OK]\e[0m $*"; }
    warn()    { echo -e "\e[33m[WARN]\e[0m $*"; }
    error()   { echo -e "\e[31m[ERROR]\e[0m $*" >&2; }
    
    info "Starting backup..."
    success "Backup completed."
    warn "Low disk space."
    error "Backup failed!"

Verify

bash your_script.sh

Colored output should appear in any terminal that supports ANSI codes (virtually all modern terminals do).

Notes

  • Use tput for a more portable approach: tput setaf 1 (red), tput sgr0 (reset).
  • Check if the terminal supports color before using it: [ -t 1 ] && COLORS=true.
  • When redirecting output to a file, color codes appear as raw escape sequences. Guard with a terminal check or use --color=never flags where available.

Add a User to Multiple Groups in Linux

This guide shows how to add an existing Linux user to one or more supplementary groups using the usermod command.

Prerequisites

  • Root or sudo privileges
  • The user and the target groups must already exist

Steps

  1. Add a user to multiple groups at once:
    sudo usermod -a -G group1,group2,group3 username

    The -a flag appends the user to the listed groups without removing them from existing ones. Omitting -a will replace all supplementary group memberships.

  2. Add a user to a single group:
    sudo usermod -a -G docker username
  3. Alternative: use gpasswd to add to one group:
    sudo gpasswd -a username groupname

Verify

groups username
id username

The output lists all groups the user belongs to. The new groups should appear.

Notes

  • Group changes take effect at the next login. The current session retains the old group memberships unless the user runs newgrp groupname or logs out and back in.
  • To list all groups on the system: cat /etc/group.
  • To create a new group: sudo groupadd newgroupname.
  • To view a user's current groups: id username.

10 Useful Shell Script Snippets

A collection of practical shell script snippets covering timing, date handling, user input, loops, and common automation patterns.

1. Measure Script Execution Time

#!/bin/bash
START=$(date +%s)

# ... your script logic here ...

END=$(date +%s)
echo "Elapsed time: $((END - START)) seconds"

2. Check If Running as Root

if [ "$(id -u)" -ne 0 ]; then
  echo "This script must be run as root." >&2
  exit 1
fi

3. Prompt User for Confirmation

read -p "Are you sure? (y/n): " confirm
if [ "$confirm" != "y" ]; then
  echo "Aborted."
  exit 0
fi

4. Check If a File Exists

FILE="/etc/myconfig.conf"
if [ -f "$FILE" ]; then
  echo "File exists."
else
  echo "File not found."
fi

5. Loop Over Files in a Directory

for file in /var/log/*.log; do
  echo "Processing: $file"
  # do something with $file
done

6. Read a File Line by Line

while IFS= read -r line; do
  echo "Line: $line"
done < /path/to/file.txt

7. Get Current Date and Time

DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M:%S")
DATETIME=$(date +"%Y-%m-%d_%H-%M-%S")
echo "Date: $DATE, Time: $TIME"

8. Check If a Command Exists

if ! command -v curl &>/dev/null; then
  echo "curl is not installed. Installing..."
  apt-get install -y curl
fi

9. Redirect stdout and stderr to a Log File

exec > /var/log/myscript.log 2>&1
echo "This goes to the log file"

10. Retry a Command on Failure

MAX_RETRIES=5
COUNT=0
until some_command; do
  COUNT=$((COUNT + 1))
  if [ $COUNT -ge $MAX_RETRIES ]; then
    echo "Command failed after $MAX_RETRIES attempts."
    exit 1
  fi
  echo "Retrying ($COUNT/$MAX_RETRIES)..."
  sleep 5
done

Notes

  • Always use double quotes around variables to prevent word-splitting: "$variable".
  • Use set -e at the top of scripts to exit on any error.
  • Use set -u to exit on undefined variables.
  • Use shellcheck to lint your scripts for common mistakes.

Finding Files, Directories, and Text in Linux

This guide covers the primary tools for locating files, directories, and text patterns on a Linux system: find, locate, and grep.

Finding Text Inside Files (grep)

# Recursively search for a pattern, showing line numbers and matching whole words
grep -rnw '/path/to/search/' -e "pattern"

# Options:
#   -r / -R  recursive
#   -n       show line numbers
#   -w       match whole words only
#   -l       show only file names (not matching lines)
#   -i       case-insensitive

# Include only specific file types
grep -rnw /var/www/ -e "eval(base64" --include="*.php"

# Exclude certain directories
grep -rnw /var/www/ -e "pattern" --exclude-dir=".git"

Finding Files by Name (find)

# Find files by name
find /path -name "filename.txt"

# Case-insensitive name search
find /path -iname "*.PHP"

# Find files modified in the last 24 hours
find /var/www -mtime -1 -type f

# Find files larger than 100MB
find / -size +100M -type f 2>/dev/null

# Find and delete files matching a pattern
find /tmp -name "*.tmp" -type f -delete

Finding Files by Content Type (find + file)

# Find all PHP files and check their type
find /var/www -name "*.php" -exec file {} \;

Fast File Lookup (locate)

# Update the database
sudo updatedb

# Search for a file
locate filename.conf

# Case-insensitive search
locate -i readme

Finding Executables (which / whereis)

# Find the path of a command
which python3
whereis nginx

Verify

find /etc -name "*.conf" -type f | wc -l

Should return a count of config files found.

Notes

  • find searches in real time; locate uses a pre-built index (faster, but may be stale).
  • Append 2>/dev/null to find commands to suppress permission-denied errors.

Redis Security Best Practices

Redis is designed for trusted environments and is not secure by default. This guide covers essential steps to harden a Redis installation against unauthorized access.

Prerequisites

  • Redis installed and running
  • Root or sudo access
  • Redis config file at /etc/redis/redis.conf (or /etc/redis.conf)

Steps

  1. Bind Redis to localhost only:
    # In /etc/redis/redis.conf
    bind 127.0.0.1

    Never expose Redis directly on a public IP unless behind a firewall.

  2. Set a strong authentication password:
    # In redis.conf
    requirepass YourStrongPasswordHere

    Then authenticate in the CLI: AUTH YourStrongPasswordHere

  3. Rename or disable dangerous commands:
    # In redis.conf — rename FLUSHALL and CONFIG to random strings
    rename-command FLUSHALL ""
    rename-command CONFIG "CONFIG_SECRET_STRING"
    rename-command DEBUG ""
  4. Disable protected mode only if binding to a specific interface with a password:
    # Only disable if you have a password AND bind to a specific interface
    protected-mode yes

    Keep protected-mode yes (the default) unless you have a specific reason to disable it.

  5. Run Redis as a non-root user:
    sudo adduser --system --no-create-home redis
    # Set in the init script or systemd unit:
    User=redis
  6. Use a firewall to block external access to port 6379:
    sudo iptables -A INPUT -p tcp --dport 6379 -s 127.0.0.1 -j ACCEPT
    sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
  7. Restart Redis after config changes:
    sudo systemctl restart redis

Verify

# Test that unauthenticated access is blocked
redis-cli ping

# Test with the password
redis-cli -a YourStrongPasswordHere ping

Without the password, Redis should return NOAUTH Authentication required.

Troubleshooting

  • If Redis won't start after adding requirepass, check the config file for syntax errors: redis-server /etc/redis/redis.conf --test-memory 0.
  • Confirm Redis is only listening on localhost: ss -tlnp | grep 6379.

Redis Quick Start Guide

This guide covers installing Redis from source, starting the server, and basic data operations using the Redis CLI.

Prerequisites

  • A Linux server with GCC and make installed
  • Root or sudo access

Install Redis from Source

  1. Download and compile Redis:
    wget https://download.redis.io/redis-stable.tar.gz
    tar xzf redis-stable.tar.gz
    cd redis-stable
    make
  2. Run the test suite (optional but recommended):
    make test
  3. Install to system paths:
    sudo make install

Install Redis from Package Manager

# Debian/Ubuntu
sudo apt-get install redis-server

# RHEL/CentOS
sudo yum install redis

Start and Enable Redis

sudo systemctl start redis
sudo systemctl enable redis
sudo systemctl status redis

Basic Redis CLI Usage

# Connect to local Redis
redis-cli

# Ping the server
127.0.0.1:6379> PING
PONG

# Set and get a key
127.0.0.1:6379> SET mykey "hello"
OK
127.0.0.1:6379> GET mykey
"hello"

# Set a key with expiry (in seconds)
127.0.0.1:6379> SET session:user1 "data" EX 3600

# Check remaining TTL
127.0.0.1:6379> TTL session:user1

# Delete a key
127.0.0.1:6379> DEL mykey

# List all keys (avoid on production with large datasets)
127.0.0.1:6379> KEYS *

# Get server info
127.0.0.1:6379> INFO server

Working with Data Structures

# Lists
RPUSH mylist "a" "b" "c"
LRANGE mylist 0 -1

# Hashes
HSET user:1 name "Alice" email "alice@example.com"
HGETALL user:1

# Sets
SADD myset "member1" "member2"
SMEMBERS myset

# Sorted Sets
ZADD leaderboard 100 "player1" 200 "player2"
ZRANGE leaderboard 0 -1 WITHSCORES

Verify

redis-cli ping

Should return PONG.

Notes

  • Default Redis port is 6379.
  • Config file is at /etc/redis/redis.conf.
  • Data is persisted via RDB snapshots (dump.rdb) or AOF logging — configure in redis.conf.

View and Troubleshoot Cron Logs

By default, Ubuntu does not log cron job output to a dedicated file. This guide shows how to enable cron logging and interpret cron log entries.

Prerequisites

  • Root or sudo access
  • rsyslog installed (standard on Ubuntu/Debian)

Steps: Enable Cron Logging

  1. Edit the rsyslog configuration:
    sudo nano /etc/rsyslog.d/50-default.conf

    Find the line #cron.* and remove the leading #:

    cron.*                          /var/log/cron.log
  2. Restart rsyslog:
    sudo service rsyslog restart
  3. Restart cron:
    sudo service cron restart

View Cron Logs

# View the cron log
cat /var/log/cron.log
tail -f /var/log/cron.log

# On RHEL/CentOS (cron logs to syslog by default)
grep cron /var/log/syslog
grep cron /var/log/messages

Capture Cron Job Output Directly

Redirect individual cron job output to a log file within the crontab:

# Edit crontab
crontab -e

# Example: run every hour, log stdout and stderr
0 * * * * /usr/local/bin/myscript.sh >> /var/log/myscript.log 2>&1

Verify

ls -la /var/log/cron.log
tail -20 /var/log/cron.log

Cron job execution events should appear after the next scheduled run.

Troubleshooting

  • If a cron job runs but produces no output in the log, confirm output is being redirected with >> /path/to/log 2>&1.
  • Cron uses a minimal environment — scripts that work in your shell may fail in cron if they rely on $PATH. Use absolute paths in cron commands.
  • Test the cron job command manually in a shell first to confirm it works.
  • Verify the cron daemon is running: systemctl status cron.

AWK Print Statement Examples

AWK is a powerful text-processing tool. This guide covers 14 practical examples of the AWK print statement for extracting and formatting data from files and command output.

Prerequisites

  • awk installed (standard on all Linux distributions)

Key Concepts

  • $0 — entire line
  • $1, $2, ... — individual fields (columns)
  • NR — current line number
  • NF — number of fields in the current line
  • FS — field separator (default: whitespace)

Examples

  1. Print the entire file:
    awk '{print}' file.txt
  2. Print a specific column:
    awk '{print $2}' file.txt
  3. Print multiple columns:
    awk '{print $1, $3}' file.txt
  4. Print with a custom separator:
    awk '{print $1 ":" $2}' file.txt
  5. Print line numbers:
    awk '{print NR, $0}' file.txt
  6. Print the last field on each line:
    awk '{print $NF}' file.txt
  7. Print lines matching a pattern:
    awk '/error/{print}' /var/log/syslog
  8. Print lines where a field matches a value:
    awk '$3 == "active" {print $1, $2}' status.txt
  9. Use a custom field separator (CSV):
    awk -F',' '{print $1, $2}' file.csv
  10. Sum values in a column:
    awk '{sum += $2} END {print "Total:", sum}' file.txt
  11. Print lines between two patterns:
    awk '/START/,/END/{print}' file.txt
  12. Print number of fields per line:
    awk '{print NF, $0}' file.txt
  13. Print first 5 lines:
    awk 'NR<=5{print}' file.txt
  14. Print unique lines (deduplication):
    awk '!seen[$0]++' file.txt

Notes

  • Combine awk with pipes: ps aux | awk '{print $1, $11}'
  • Use BEGIN and END blocks for setup and teardown logic.
  • For complex processing, write awk programs to a file and run with awk -f script.awk.

Postfix Queue Management Commands

When emails are delayed or stuck, Postfix queue management commands help you inspect, retry, and clean the mail queue.

Prerequisites

  • Postfix installed and running
  • Root or sudo access

View Queue Status

# List all messages in the queue (all queues)
mailq
# or
postqueue -p

# Show queue shape by age
qshape deferred
qshape incoming

Postfix maintains several queues:

  • incoming — newly arrived messages being processed
  • active — messages being delivered right now
  • deferred — messages that failed delivery and are waiting for retry
  • hold — messages manually placed on hold
  • corrupt — unreadable messages

Inspect a Specific Message

# View message content by queue ID
postcat -q QUEUEID

# View just the headers
postcat -qh QUEUEID

Flush and Retry Delivery

# Attempt immediate delivery of all deferred messages
postqueue -f
# or
postfix flush

Delete Messages from the Queue

# Delete a specific message by queue ID
postsuper -d QUEUEID

# Delete ALL messages in all queues (use with caution)
postsuper -d ALL

# Delete only deferred messages
postsuper -d ALL deferred

Hold and Release Messages

# Put all active messages on hold
postsuper -h ALL

# Release all held messages
postsuper -H ALL

# Release a specific held message
postsuper -H QUEUEID

Filter Queue by Sender or Recipient

# Delete all messages from a specific sender
mailq | grep "^[A-F0-9]" | awk '{print $1}' |   while read ID; do
    if postcat -qh $ID | grep -q "sender@example.com"; then
      postsuper -d $ID
    fi
  done

Verify

mailq | tail -3

Mail queue is empty confirms all messages have been delivered or removed.

Troubleshooting

  • Check the reason messages are deferred: postcat -q QUEUEID | grep "deferred"
  • Review the mail log for delivery errors: tail -100 /var/log/mail.log

Optimize and Compress Images from the Linux CLI

This guide covers using command-line tools to optimize and compress JPEG and PNG images without significant quality loss, ideal for preparing images before web deployment.

Prerequisites

  • Install the required tools:
    # Debian/Ubuntu
    sudo apt-get install jpegoptim optipng pngquant imagemagick
    
    # RHEL/CentOS
    sudo yum install epel-release
    sudo yum install jpegoptim optipng pngquant ImageMagick

Compress JPEG Images (jpegoptim)

# Compress a single JPEG to 80% quality
jpegoptim --max=80 image.jpg

# Compress all JPEGs in a directory (in place)
jpegoptim --max=80 /var/www/html/images/*.jpg

# Strip all metadata (EXIF) to reduce file size further
jpegoptim --strip-all image.jpg

# Process recursively
find /var/www/html -name "*.jpg" -exec jpegoptim --max=80 {} \;

Compress PNG Images (optipng)

# Optimize a single PNG (lossless)
optipng image.png

# Optimize with higher compression level (0-7, default 2)
optipng -o7 image.png

# Optimize all PNGs in a directory
optipng /var/www/html/images/*.png

Lossy PNG Compression (pngquant)

# Compress PNG with lossy reduction (results in significant size savings)
pngquant --quality=65-80 image.png

# Output to a specific file
pngquant --quality=65-80 --output compressed.png image.png

# Overwrite the original
pngquant --quality=65-80 --force --ext .png image.png

Resize Images (ImageMagick)

# Resize to a specific width, maintaining aspect ratio
convert image.jpg -resize 800x output.jpg

# Resize to a specific width and height (may distort)
convert image.jpg -resize 800x600! output.jpg

# Batch resize all JPEGs
for f in *.jpg; do convert "$f" -resize 1200x "resized_$f"; done

Verify

# Compare file sizes before and after
ls -lh image.jpg compressed.jpg

Notes

  • Always work on copies of your original images until you are satisfied with the results.
  • JPEG compression is lossy — lowering quality below 70 typically produces visible artifacts.
  • PNG optimization with optipng is lossless; pngquant is lossy but produces much smaller files.

Linux cut Command Tutorial with Examples

The cut command extracts sections from each line of a file or input stream, useful for parsing delimited text, log files, and CSV data.

Prerequisites

  • cut is part of GNU coreutils — available on all Linux distributions by default.

Key Options

  • -f — select fields (columns), used with a delimiter
  • -d — specify field delimiter (default: tab)
  • -c — select by character position
  • -b — select by byte position
  • --complement — invert the selection (print everything except the selected range)

Examples

  1. Cut by field using a delimiter (colon-separated):
    cut -d: -f1 /etc/passwd

    Prints just the usernames from /etc/passwd.

  2. Extract multiple fields:
    cut -d: -f1,3 /etc/passwd
  3. Extract a range of fields:
    cut -d: -f1-4 /etc/passwd
  4. Cut by character position:
    echo "Hello World" | cut -c1-5
    # Output: Hello
  5. Cut by character position from a file:
    cut -c1-10 /var/log/syslog
  6. Use a comma as delimiter (CSV):
    cut -d',' -f2 data.csv
  7. Cut all fields except the first (complement):
    cut -d: -f1 --complement /etc/passwd
  8. Combine with other commands:
    # Get the IP address from ifconfig output
    ifconfig eth0 | grep "inet " | cut -d' ' -f10
    
    # Extract domain names from a log
    awk '{print $7}' access.log | cut -d'/' -f3

Verify

echo "one:two:three:four" | cut -d: -f2
# Expected output: two

Notes

  • cut does not handle multiple consecutive delimiters as one — use awk for that case.
  • For more complex field extraction, awk -F':' '{print $1}' is more flexible.

Run scp Inside a screen Session

Long scp transfers can be interrupted if your SSH session disconnects. Running scp inside a screen session lets the transfer continue even if you lose the connection.

Prerequisites

  • screen installed on the remote server (apt install screen or yum install screen)
  • SSH access to the remote server

Method 1: Start scp Inside a screen Session

  1. SSH to the remote server:
    ssh user@remote-server
  2. Start a named screen session:
    screen -S transfer
  3. Run the scp command inside the screen session:
    scp -r /source/path user@destination:/dest/path
  4. Detach from screen (transfer continues in background):
    Ctrl+A, then D
  5. Reattach later to check progress:
    screen -r transfer

Method 2: Background and Disown the scp Process

  1. Start the scp transfer as usual.
  2. Suspend the process: Ctrl+Z
  3. Resume in the background: bg
  4. Disown the process so it survives session close: disown
  5. Close the SSH session — the transfer continues.

Method 3: Use rsync Instead

rsync is resumable by design and is preferred for large transfers:

rsync -avz --progress /source/path user@remote:/dest/path

If interrupted, re-run the same command and it resumes where it left off.

Verify

screen -list

Shows all active screen sessions. Reattach with screen -r session_name to confirm the transfer is running.

Notes

  • nohup scp ... & also works for running scp in the background.
  • For very large transfers, prefer rsync with --partial flag to enable resumable transfers.

Run Commands in screen After Creation

This guide shows how to create a detached screen session and send commands to it from outside — useful for automation scripts that need to start processes in a named screen.

Prerequisites

  • screen installed

Steps

  1. Create a named detached screen session:
    screen -d -m -S mysession
    • -d -m — create and immediately detach
    • -S mysession — name the session
  2. Send a command to the detached session:
    screen -S mysession -X -p 0 stuff $'cd /home/myapp
    '
    • -X stuff — sends keystrokes to the session
    • $'... ' — the trailing is the Enter key
    • -p 0 — targets window 0 in the session
  3. Run a second command in the same session:
    screen -S mysession -X -p 0 stuff $'node app.js
    '
  4. Full example — start a Node.js app in a screen:
    screen -d -m -S nodeapp
    screen -S nodeapp -X -p 0 stuff $'cd /home/nodejsapp
    '
    screen -S nodeapp -X -p 0 stuff $'node start.js app.js
    '

Reattach to Verify

screen -r mysession

Shows the session's output. Detach again with Ctrl+A, D.

List All Sessions

screen -list

Notes

  • Each stuff call sends characters immediately — add sleep 1 between commands if the shell needs time to process the previous command.
  • An alternative is to use tmux with tmux send-keys, which has a cleaner API for scripting.

Common iptables Commands Reference

A quick reference for common iptables firewall commands on Linux. iptables controls packet filtering, NAT, and network traffic rules at the kernel level.

Prerequisites

  • Root or sudo access
  • iptables installed (standard on most Linux distributions)

View Current Rules

# List all rules with line numbers
sudo iptables -L -n -v --line-numbers

# List rules for a specific chain
sudo iptables -L INPUT -n -v

Block and Allow Traffic

# Drop all connections from a specific IP
sudo iptables -A INPUT -s 192.168.1.100 -j DROP

# Allow a specific IP
sudo iptables -A INPUT -s 192.168.1.100 -j ACCEPT

# Block a specific port
sudo iptables -A INPUT -p tcp --dport 8080 -j DROP

# Allow a specific port
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# Allow a port from a specific IP only
sudo iptables -A INPUT -p tcp -s 10.0.0.1 --dport 3306 -j ACCEPT

Allow Established Connections

sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

Delete Rules

# Delete a specific rule by line number (get numbers from -L --line-numbers)
sudo iptables -D INPUT 3

# Flush (delete) all rules in a chain
sudo iptables -F INPUT

# Flush all chains
sudo iptables -F

Set Default Policies

# Drop all incoming traffic by default (whitelist approach)
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

Save and Restore Rules

# Debian/Ubuntu — save rules
sudo iptables-save > /etc/iptables/rules.v4

# RHEL/CentOS — save rules
sudo service iptables save

# Restore rules
sudo iptables-restore < /etc/iptables/rules.v4

NAT / Port Forwarding

# Forward port 80 on eth0 to an internal host
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.10:80

Verify

sudo iptables -L -n --line-numbers

Notes

  • Rules are applied in order — the first match wins.
  • On modern systems, consider using nftables (via nft) or ufw as higher-level wrappers.
  • Always allow SSH before setting a default DROP policy to avoid locking yourself out:
    sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

Linux screen Command Reference

GNU screen is a terminal multiplexer that lets you run persistent shell sessions that survive SSH disconnections. This guide covers common screen commands.

Prerequisites

  • screen installed: apt install screen or yum install screen

Starting Sessions

# Start a new screen session
screen

# Start a named session
screen -S sessionname

# Start a detached session (runs in background immediately)
screen -d -m -S sessionname

# Run a command in a new detached session
screen -d -m -S sessionname bash -c 'command'

Attaching and Detaching

# List all screen sessions
screen -list
screen -ls

# Attach to a session by name
screen -r sessionname

# Attach to the only available session
screen -r

# Force attach (detach from other terminal first)
screen -D -r sessionname

# Detach from inside a session
# Press: Ctrl+A, then D

Managing Windows Inside a Session

# Create a new window:        Ctrl+A, C
# Switch to next window:      Ctrl+A, N
# Switch to previous window:  Ctrl+A, P
# Switch to window by number: Ctrl+A, 0-9
# List all windows:           Ctrl+A, "
# Rename current window:      Ctrl+A, A

Kill Sessions

# Kill a specific session by name
screen -S sessionname -X quit

# Kill the current session from inside
# Press: Ctrl+A, then K

# Kill all screens (use with caution)
screen -ls | grep -oP '\d+\.\w+' | xargs -I{} screen -S {} -X quit

Send Commands to a Detached Session

screen -S sessionname -X -p 0 stuff $'your_command
'

Verify

screen -ls

Lists all sessions with their status (Attached/Detached).

Notes

  • All screen keyboard shortcuts begin with Ctrl+A (the escape key).
  • For a more modern alternative with better defaults, consider tmux.
  • Use screen -L to enable logging of session output to a file.

Change the Linux I/O Scheduler

The Linux I/O scheduler controls how disk read/write requests are ordered. Choosing the right scheduler for your workload can significantly improve performance. This guide covers checking the current scheduler and changing it temporarily or permanently.

Available Schedulers

  • cfq (Completely Fair Queuing) — balanced I/O for desktops and general use (default on older kernels)
  • deadline — low latency, good for databases and random I/O workloads
  • noop — minimal scheduling, best for SSDs and virtual machines (no disk seeks to optimize)
  • mq-deadline / bfq / kyber — multi-queue schedulers on kernel 4.x+ for NVMe and SSDs

Check the Current Scheduler

# Replace sda with your disk device
cat /sys/block/sda/queue/scheduler

The active scheduler appears in brackets, e.g., noop [deadline] cfq.

Change Scheduler Temporarily (until next reboot)

# Set deadline scheduler on sda
echo deadline | sudo tee /sys/block/sda/queue/scheduler

# Set noop scheduler (good for SSDs/VMs)
echo noop | sudo tee /sys/block/sda/queue/scheduler

Change Scheduler Permanently

  1. Edit the GRUB configuration:
    sudo nano /etc/default/grub

    Add the scheduler to the kernel parameter line:

    GRUB_CMDLINE_LINUX="elevator=deadline"
  2. Update GRUB:
    # Debian/Ubuntu
    sudo update-grub
    
    # RHEL/CentOS
    sudo grub2-mkconfig -o /boot/grub2/grub.cfg
  3. Reboot and verify:
    sudo reboot
    cat /sys/block/sda/queue/scheduler

Recommendations by Use Case

  • SSD / NVMe drives: Use noop or none (kernel 4.x+)
  • Virtual machines (guest OS): Use noop — the hypervisor handles scheduling
  • Database servers (spinning disks): Use deadline
  • General-purpose desktop: Use cfq or bfq

Verify

cat /sys/block/sda/queue/scheduler

The selected scheduler appears in brackets.

Check Apache Concurrent Connections with netstat

Monitoring the number of concurrent connections to Apache helps identify traffic spikes, connection leaks, and potential DDoS activity. This guide uses netstat and ss to inspect connections.

Prerequisites

  • net-tools installed for netstat (apt install net-tools), or use the built-in ss command
  • Root or sudo access for full output

Count Total Connections to Apache

# Using netstat (ports 80 and 443)
netstat -an | grep ':80' | wc -l
netstat -an | grep ':443' | wc -l

# Using ss (modern replacement for netstat)
ss -s

View Connection States

# Count connections by state
netstat -an | grep ':80' | awk '{print $6}' | sort | uniq -c | sort -rn

# Using ss
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

Common states:

  • ESTABLISHED — active connections
  • TIME_WAIT — connections recently closed, waiting for cleanup
  • CLOSE_WAIT — server is waiting for the application to close the connection
  • SYN_RECV — half-open connections (possible SYN flood)

View Connections Per IP Address

netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

Shows the top 20 IP addresses by connection count. A single IP with hundreds of connections may indicate abuse.

Real-Time Connection Monitoring

# Repeat every 2 seconds
watch -n 2 'netstat -an | grep ":80" | wc -l'

Check via Apache mod_status

# Enable mod_status in Apache config (if not already enabled)
sudo a2enmod status

# Then access (from localhost)
curl http://localhost/server-status?auto | grep "BusyWorkers\|IdleWorkers"

Verify

ss -tan state established '( dport = :80 or dport = :443 )' | wc -l

Troubleshooting

  • High TIME_WAIT counts can be reduced by enabling TCP keep-alive or adjusting net.ipv4.tcp_fin_timeout in /etc/sysctl.conf.
  • High CLOSE_WAIT counts often indicate a bug in application code that is not properly closing connections.

Remove VNC Server from Startup with update-rc.d

This guide covers how to remove a VNC server from the system startup sequence and re-add it with a corrected priority using update-rc.d on Debian/Ubuntu systems.

Prerequisites

  • A VNC server init script at /etc/init.d/vncserver
  • Root or sudo access

Steps

  1. Remove the VNC server from all runlevels:
    sudo update-rc.d -f vncserver remove

    The -f flag forces removal even if the init script still exists.

  2. Re-add the VNC server with a startup priority of 99 (starts late in the boot process):
    sudo update-rc.d vncserver defaults 99

    A higher number means the service starts later. The default priority of 20 can cause issues if VNC starts before the display manager is ready.

  3. Verify the symlinks were created:
    ls -la /etc/rc*.d/ | grep vncserver
  4. Start the VNC server:
    sudo service vncserver start

On systemd-based Systems (Ubuntu 16.04+)

Modern Ubuntu systems use systemd instead of init scripts. Use these commands instead:

# Disable VNC from starting at boot
sudo systemctl disable vncserver@1.service

# Re-enable it
sudo systemctl enable vncserver@1.service

# Start now
sudo systemctl start vncserver@1.service

Verify

sudo service vncserver status

Notes

  • update-rc.d manages SysV init script symlinks in /etc/rc*.d/.
  • If using systemd, avoid mixing update-rc.d and systemctl — use systemctl exclusively.

Fix Broken dpkg Package Installations

When a dpkg package is only partially installed or in a broken state, standard apt-get commands may refuse to run. This guide shows how to manually clean up and fix such packages.

Symptoms

  • dpkg: error processing package
  • Package is in a very bad inconsistent state
  • E: Sub-process /usr/bin/dpkg returned an error code

Steps

  1. Identify the broken package name from the error output.
  2. Remove the package's dpkg info files:
    sudo rm /var/lib/dpkg/info/package_name.*

    Replace package_name with the actual broken package name (e.g., mysql-server-5.7).

  3. Force dpkg to reconfigure all pending packages:
    sudo dpkg --configure -a
  4. Update the package list:
    sudo apt-get update
  5. Reinstall the broken package:
    sudo apt-get install package_name

Alternative: Force-Remove the Package

sudo dpkg --remove --force-remove-reinstreq package_name
sudo apt-get install package_name

Fix All Broken Dependencies

sudo apt-get install -f

The -f (fix-broken) flag attempts to correct any broken dependency chain.

Verify

dpkg -l | grep '^.H\|^.i'

Packages marked with H (half-installed) or problematic flags should no longer appear after the fix.

Troubleshooting

  • If errors persist, try: sudo apt-get clean && sudo apt-get update
  • Check for held packages: dpkg --get-selections | grep hold
  • Review the full dpkg log: cat /var/log/dpkg.log | grep package_name

Debug Programs with strace on Linux

strace traces system calls and signals made by a process. It is invaluable for debugging programs that fail without clear error messages, especially when you lack access to the source code.

Prerequisites

  • strace installed: apt install strace or yum install strace

Basic Usage

# Trace all system calls of a command
strace ls -l

# Attach to a running process by PID
strace -p 1234

# Trace a process and all its child processes
strace -f command

Useful Options

# Show timestamps on each line
strace -t command

# Show time spent in each syscall
strace -T command

# Filter to only specific syscalls (e.g., file-related)
strace -e trace=open,read,write,close command

# Save output to a file
strace -o /tmp/strace.log command

# Combine: trace children, timestamps, save to file
strace -f -t -o /tmp/strace.log ./myapp

Common Debugging Scenarios

Find which files a program opens

strace -e trace=open,openat,stat command 2>&1 | grep -i "\.conf\|\.log\|No such"

Debug a failing web server startup

strace -f -e trace=network,file nginx -t 2>&1 | head -50

Find why a command says "Permission denied"

strace command 2>&1 | grep "EACCES\|EPERM"

Trace network connections

strace -e trace=connect,socket,bind command

Count syscall frequency

strace -c command

Prints a summary table of syscall counts and time spent after the command exits.

Attach to a Running Process

# Find PID of a process
pgrep nginx

# Attach strace to it
strace -p $(pgrep -n nginx) -e trace=network

Verify

strace -c ls /tmp

Should produce a syscall summary table — confirms strace is working.

Notes

  • strace output goes to stderr by default — redirect with 2>&1 or use -o file.
  • Tracing adds overhead — do not leave strace attached to production processes for extended periods.
  • Use ltrace to trace library calls instead of kernel system calls.

Test HTTP Response Codes and Timing with curl

The curl command can measure HTTP response codes, DNS lookup time, connection time, and total transfer time — useful for diagnosing slow or broken web endpoints.

Prerequisites

  • curl installed (standard on most Linux distributions)

Check HTTP Response Code Only

curl -s -o /dev/null -w "%{http_code}" https://example.com
  • -s — silent mode (suppress progress bar)
  • -o /dev/null — discard the response body
  • -w — write formatted output after completion

Full Timing Breakdown

curl "https://example.com" -s -o /dev/null -w   " response_code:    %{http_code}
 dns_time:         %{time_namelookup}
 connect_time:     %{time_connect}
 pretransfer_time: %{time_pretransfer}
 starttransfer:    %{time_starttransfer}
 total_time:       %{time_total}
"

Available curl Timing Variables

  • %{http_code} — HTTP status code (e.g., 200, 301, 404, 500)
  • %{time_namelookup} — time for DNS resolution
  • %{time_connect} — time to establish TCP connection
  • %{time_appconnect} — time to complete SSL/TLS handshake
  • %{time_pretransfer} — time from start to just before data transfer begins
  • %{time_starttransfer} — time to first byte (TTFB)
  • %{time_total} — total elapsed time
  • %{size_download} — total bytes downloaded
  • %{speed_download} — download speed in bytes/second

Save Format to a File for Reuse

cat > /tmp/curl-format.txt << 'EOF'
  dns_time:         %{time_namelookup}s
  connect_time:     %{time_connect}s
  tls_time:         %{time_appconnect}s
  ttfb:             %{time_starttransfer}s
  total_time:       %{time_total}s
  http_code:        %{http_code}
EOF

curl -s -o /dev/null -w "@/tmp/curl-format.txt" https://example.com

Follow Redirects

curl -L -s -o /dev/null -w "%{http_code} %{url_effective}
" https://example.com

Verify

curl -s -o /dev/null -w "%{http_code}" https://example.com

Should return 200 for a healthy endpoint.

Notes

  • Use -k to skip SSL certificate verification (for testing self-signed certs).
  • Add -v for verbose output including request and response headers.
  • Use --max-time 10 to set a timeout and avoid hanging on unresponsive servers.

Fix Compilation Errors for Missing ncurses Libraries

When compiling software that depends on the ncurses library, the linker may report errors like cannot find -lcurses or cannot find -lncurses. This guide shows how to resolve these errors.

Symptom

/usr/bin/ld: cannot find -lcurses
/usr/bin/ld: cannot find -lncurses
collect2: error: ld returned 1 exit status

Cause

The ncurses development headers and libraries are not installed. The runtime library (libncurses5) may be present, but the development package (libncurses5-dev) — which includes .h headers and .so linker stubs — is needed for compilation.

Steps

  1. Install the ncurses development package:
    # Debian/Ubuntu
    sudo apt-get install libncurses5-dev libncursesw5-dev
    
    # RHEL/CentOS
    sudo yum install ncurses-devel
  2. Re-run the compilation:
    make clean
    make

If the Error Persists: Check Library Paths

# Confirm the library is installed
ldconfig -p | grep ncurses

# Verify the .so file exists
ls /usr/lib/x86_64-linux-gnu/libncurses*

For -lcurses Specifically

Some software links against -lcurses rather than -lncurses. Create a symlink if needed:

sudo ln -s /usr/lib/x86_64-linux-gnu/libncurses.so /usr/lib/x86_64-linux-gnu/libcurses.so

Verify

make && echo "Compilation successful"

Notes

  • The same pattern applies to other missing libraries: install the -dev package for the library in question.
  • Use apt-file search libcurses.so to find which package provides a specific library file.
  • On 64-bit systems compiling 32-bit software, install lib32ncurses5-dev as well.

Resize an AWS EBS NVMe Volume

After increasing the size of an AWS EBS volume in the console, the OS must be informed of the new size. This guide covers growing the partition and filesystem on an NVMe-attached EBS volume.

Prerequisites

  • The EBS volume has already been resized in the AWS Console (EC2 → Volumes → Modify Volume)
  • SSH access to the EC2 instance
  • Root or sudo access

Steps

  1. Verify the new size is visible to the OS:
    lsblk

    The disk (e.g., nvme0n1) should show the new size. If the partition (nvme0n1p1) still shows the old size, you need to grow the partition.

  2. Grow the partition to use the new disk space:
    sudo growpart /dev/nvme0n1 1

    Replace 1 with the partition number shown in lsblk.

  3. Verify the partition is now the full size:
    lsblk
  4. Extend the filesystem to fill the partition:
    # For ext4 filesystems
    sudo resize2fs /dev/nvme0n1p1
    
    # For XFS filesystems (common on Amazon Linux 2)
    sudo xfs_growfs /
  5. Verify the filesystem now shows the new size:
    df -h

Check Filesystem Type

lsblk -f

The FSTYPE column shows whether the filesystem is ext4, xfs, or another type.

Verify

df -h /

The root partition should now reflect the expanded size.

Troubleshooting

  • growpart: NOCHANGE — the partition already spans the full disk. Skip to the filesystem resize step.
  • "No space left on device" during growpart — see the separate guide on fixing growpart space errors (often caused by a stale GPT backup header).
  • AWS documentation: Recognize an expanded volume

Install Linux Across Multiple Drives with Software RAID

When provisioning a server with two or more drives, you can configure software RAID during installation to span the OS across multiple disks for redundancy or performance. This guide covers using the Hetzner installimage script as an example, with general RAID configuration concepts.

Prerequisites

  • Two or more physical drives installed
  • Root access to the installation environment

Software RAID Levels Available

  • RAID 0 — striping (performance, no redundancy)
  • RAID 1 — mirroring (redundancy, requires 2+ drives)
  • RAID 5 — striping with parity (requires 3+ drives)
  • RAID 6 — striping with double parity (requires 4+ drives)
  • RAID 10 — mirrored stripes (requires 4+ drives)

Configure Using installimage (Hetzner)

In the installimage configuration file, set the following parameters:

# Enable software RAID
SWRAID 1

# Set RAID level (0, 1, 5, 6, or 10)
SWRAIDLEVEL 1

With RAID 1 enabled, the install script automatically mirrors the OS across all available drives.

Manual Software RAID Setup with mdadm

  1. Install mdadm:
    sudo apt-get install mdadm
  2. Create a RAID 1 array from two drives:
    sudo mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sda /dev/sdb
  3. Create a filesystem on the array:
    sudo mkfs.ext4 /dev/md0
  4. Save the RAID configuration:
    sudo mdadm --detail --scan >> /etc/mdadm/mdadm.conf
    sudo update-initramfs -u

Monitor RAID Health

cat /proc/mdstat
sudo mdadm --detail /dev/md0

Verify

cat /proc/mdstat

Should show the array as [UU] (both drives up and in sync).

Notes

  • RAID is not a substitute for backups — it protects against drive failure, not accidental deletion or corruption.
  • RAID 1 is recommended for OS drives on bare-metal servers to ensure continuity if one drive fails.

Introduction to RAID Concepts and Levels

RAID (Redundant Array of Independent Disks) combines multiple physical drives into a single logical unit to improve performance, reliability, or both. This guide explains the core RAID concepts and the most common RAID levels.

Key Concepts

  • Striping — data is split across multiple drives, improving read/write performance by parallelizing I/O
  • Mirroring — data is written identically to two or more drives, providing redundancy
  • Parity — error-correction data is calculated and distributed across drives, allowing reconstruction of lost data

RAID Levels

RAID 0 — Striping

  • Drives required: 2+
  • Data split across all drives for maximum performance
  • No redundancy — if one drive fails, all data is lost
  • Use case: temporary scratch storage, video editing, where speed matters and data loss is acceptable

RAID 1 — Mirroring

  • Drives required: 2
  • Identical data written to both drives
  • Can survive failure of one drive; read performance improved
  • Usable capacity: 50% of total
  • Use case: OS drives, small databases where reliability is critical

RAID 5 — Striping with Distributed Parity

  • Drives required: 3+
  • Data and parity striped across all drives
  • Can survive failure of one drive
  • Usable capacity: (N-1) drives
  • Use case: general-purpose file servers, NAS

RAID 6 — Striping with Double Parity

  • Drives required: 4+
  • Can survive failure of two drives simultaneously
  • Usable capacity: (N-2) drives
  • Use case: large disk arrays where rebuild time is long and two-drive failure is a real risk

RAID 10 — Mirrored Stripes (RAID 1+0)

  • Drives required: 4+ (even number)
  • Mirrors pairs of drives and stripes across them
  • High performance and high redundancy; can survive multiple drive failures (as long as not both drives in the same mirror pair fail)
  • Usable capacity: 50% of total
  • Use case: high-traffic databases, virtualization hosts

Hardware RAID vs. Software RAID

  • Hardware RAID — a dedicated RAID controller handles all I/O. Faster, independent of OS, but expensive and requires matching spare controllers.
  • Software RAID — managed by the OS kernel (e.g., Linux mdadm). Free, flexible, portable, but uses some CPU.

Check RAID Status (mdadm)

cat /proc/mdstat
sudo mdadm --detail /dev/md0

Notes

  • RAID does not replace backups. RAID protects against drive hardware failure only — not against file corruption, accidental deletion, or ransomware.
  • Always monitor RAID arrays with alerting — a degraded array without monitoring is nearly as risky as no RAID at all.

Find Largest Directories and Files in Linux

Disk space can fill up unexpectedly. This guide covers the commands to identify the largest files and directories consuming the most space on a Linux system.

Prerequisites

  • Standard Linux utilities: df, du, find — available on all distributions

Check Overall Disk Usage

# Show disk usage for all mounted filesystems
df -h

# Show disk usage for a specific path
df -h /var

Find the Largest Directories

# Show sizes of all top-level directories, sorted
du -sh /* 2>/dev/null | sort -rh | head -20

# Drill into a specific directory
du -sh /var/* 2>/dev/null | sort -rh | head -20

# One-liner to find the top 10 largest directories
du -ah /var 2>/dev/null | sort -rh | head -10

Find the Largest Files

# Find the 20 largest files on the entire system
find / -type f -printf '%s %p
' 2>/dev/null | sort -rn | head -20

# Find large files in a specific directory
find /var -type f -size +100M 2>/dev/null | xargs ls -lh

# Find files larger than 500MB anywhere
find / -type f -size +500M -exec ls -lh {} \; 2>/dev/null

Common Space Hogs to Check

# Log files
du -sh /var/log/* | sort -rh | head -10

# Docker images and containers
docker system df

# Old kernel packages (Debian/Ubuntu)
dpkg --list | grep linux-image

# Package manager cache
du -sh /var/cache/apt/
du -sh /var/cache/yum/

Interactive Disk Usage Browser (ncdu)

# Install ncdu
sudo apt-get install ncdu   # Debian/Ubuntu
sudo yum install ncdu       # RHEL/CentOS

# Run it
ncdu /

ncdu provides an interactive tree view — navigate with arrow keys, press d to delete.

Verify

df -h /

After cleanup, the available space shown should have increased.

Notes

  • Always confirm what a file or directory contains before deleting it.
  • Log files in /var/log can be safely truncated with truncate -s 0 /var/log/bigfile.log if the process still has the file open.
  • Use lsof | grep deleted to find large deleted files that are still held open by processes (these don't free space until the process closes them or is restarted).

Create and Manage LVM Volumes in Linux

LVM (Logical Volume Manager) provides flexible disk management — volumes can be resized, snapshots taken, and storage pooled across multiple physical disks. This guide covers creating an LVM setup from scratch.

Prerequisites

  • One or more unpartitioned or newly partitioned disks
  • lvm2 installed: apt install lvm2 or yum install lvm2
  • Root access

LVM Architecture

  • Physical Volume (PV) — a raw disk or partition initialized for LVM use
  • Volume Group (VG) — a pool of storage formed from one or more PVs
  • Logical Volume (LV) — a virtual partition carved from a VG, used like a regular partition

Steps

  1. Partition the disk and set type to Linux LVM:
    sudo fdisk /dev/xvdb

    Inside fdisk: press n (new), p (primary), accept defaults, then t to set type to 8e (Linux LVM), then w to write.

  2. Initialize the partition as a Physical Volume:
    sudo pvcreate /dev/xvdb1
  3. Create a Volume Group:
    sudo vgcreate myvg /dev/xvdb1
  4. Create a Logical Volume (e.g., 10GB):
    sudo lvcreate -L 10G -n mylv myvg
    
    # Or use all available space
    sudo lvcreate -l 100%FREE -n mylv myvg
  5. Format the Logical Volume:
    sudo mkfs.ext4 /dev/myvg/mylv
  6. Mount it:
    sudo mkdir /mnt/data
    sudo mount /dev/myvg/mylv /mnt/data
  7. Add to /etc/fstab for persistent mounting:
    /dev/myvg/mylv  /mnt/data  ext4  defaults  0  2

Extend a Logical Volume

# Extend LV by 5GB
sudo lvextend -L +5G /dev/myvg/mylv

# Extend the filesystem to fill the new LV size
sudo resize2fs /dev/myvg/mylv

Inspect LVM

sudo pvs    # Physical volumes
sudo vgs    # Volume groups
sudo lvs    # Logical volumes
sudo lsblk  # Block device tree

Verify

df -h /mnt/data

Should show the logical volume mounted with the correct size.

Notes

  • LVM snapshots are useful for consistent backups: lvcreate -L 2G -s -n mysnap /dev/myvg/mylv
  • Multiple disks can be added to a VG to expand capacity: vgextend myvg /dev/xvdc1

Fix growpart "No Space Left on Device" Error

When running growpart to expand an EBS partition, you may encounter a "No space left on device" error even when there is clearly free space. This is typically caused by a stale GPT backup partition table header at the end of the disk.

Symptom

$ sudo growpart /dev/nvme0n1 1
FAILED: failed to growpart

Or the filesystem still shows as 100% full after the EBS volume was resized in AWS.

Diagnosis

  1. Check partition and disk sizes:
    lsblk
    lsblk -f
  2. Check if the root filesystem is actually full:
    df -h
  3. Check for GPT errors with sgdisk:
    sudo sgdisk -v /dev/nvme0n1

    A message like "Caution: invalid backup GPT header" confirms the stale header problem.

Fix: Move the GPT Backup Header

# Repair the GPT backup header to the end of the disk
sudo sgdisk -e /dev/nvme0n1

Install sgdisk if not present: apt install gdisk or yum install gdisk

Then Grow the Partition and Filesystem

# Grow the partition
sudo growpart /dev/nvme0n1 1

# Verify
lsblk

# Extend the ext4 filesystem
sudo resize2fs /dev/nvme0n1p1

# For XFS filesystem
sudo xfs_growfs /

Verify

df -h /

The root filesystem should now reflect the full expanded size.

Troubleshooting

  • If resize2fs reports "Nothing to do", the filesystem already matches the partition size — the partition grow step may not have taken effect. Reboot and try again.
  • Confirm you are growing the correct partition number — use lsblk to verify.
  • AWS documentation: Extend a Linux file system after resizing an EBS volume

Detect Linux System Architecture (32/64-bit)

Shell scripts that download or install binaries need to detect the system architecture to select the correct build. This guide covers methods for detecting CPU architecture in shell scripts.

Basic Architecture Detection

# Simple check
uname -m

Common output values:

  • x86_64 — 64-bit Intel/AMD
  • i686 / i386 — 32-bit Intel/AMD
  • aarch64 — 64-bit ARM (e.g., AWS Graviton, Raspberry Pi 4)
  • armv7l — 32-bit ARM

Shell Script: Map to Standard Architecture Names

#!/bin/bash
arch="$(uname -m)"

case "$arch" in
  x86_64)  arch="amd64" ;;
  i686|i386) arch="386" ;;
  aarch64) arch="arm64" ;;
  armv7l)  arch="arm" ;;
  *)
    echo "Unsupported architecture: $arch" >&2
    exit 1
    ;;
esac

echo "Detected architecture: $arch"

Detect glibc vs. musl (for Alpine Linux / static builds)

if getconf GNU_LIBC_VERSION > /dev/null 2>&1; then
  libc="glibc"
else
  libc="musl"
fi
echo "libc: $libc"

Detect OS Type

OS="$(uname -s)"
case "$OS" in
  Linux)   os="linux" ;;
  Darwin)  os="darwin" ;;
  *)
    echo "Unsupported OS: $OS" >&2
    exit 1
    ;;
esac

Practical Example: Download the Right Binary

#!/bin/bash
ARCH=$(uname -m)
[ "$ARCH" = "x86_64" ] && ARCH="amd64"
[ "$ARCH" = "aarch64" ] && ARCH="arm64"

VERSION="1.2.3"
URL="https://example.com/releases/mytool_${VERSION}_linux_${ARCH}.tar.gz"

curl -L "$URL" -o mytool.tar.gz
tar xzf mytool.tar.gz

Verify

uname -m
arch
file /bin/bash   # Shows ELF 32-bit or 64-bit

Notes

  • dpkg --print-architecture gives the Debian package architecture (e.g., amd64, arm64).
  • rpm --eval '%{_arch}' gives the RPM architecture on RHEL/CentOS.

Set or Change the Timezone in Linux

The system timezone affects timestamps in logs, cron job execution times, and application behavior. This guide covers checking and changing the timezone on modern Linux systems.

Prerequisites

  • Root or sudo access
  • timedatectl (systemd-based systems: Ubuntu 16.04+, CentOS 7+) or manual /etc/localtime symlink method

Check Current Timezone

timedatectl

List Available Timezones

timedatectl list-timezones
timedatectl list-timezones | grep America
timedatectl list-timezones | grep Asia

Set the Timezone (systemd)

sudo timedatectl set-timezone America/New_York
sudo timedatectl set-timezone UTC
sudo timedatectl set-timezone Asia/Kolkata

Set the Timezone Manually (all distributions)

# Create a symlink from the timezone file to /etc/localtime
sudo ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime

# Update /etc/timezone (Debian/Ubuntu)
echo "America/New_York" | sudo tee /etc/timezone
sudo dpkg-reconfigure -f noninteractive tzdata

Enable NTP Time Synchronization

# Check NTP status
timedatectl

# Enable NTP sync
sudo timedatectl set-ntp true

# Or use chrony
sudo apt-get install chrony
sudo systemctl enable chrony
sudo systemctl start chrony

Verify

timedatectl
date

The output should show the new timezone and the correct local time.

Notes

  • Always use UTC for servers when possible — it avoids daylight saving time complications in logs.
  • Application-level timezones (e.g., in PHP, MySQL, or Python) may need to be set separately even after changing the OS timezone.
  • After changing the timezone, restart any services that cache time (e.g., cron, PHP-FPM) to pick up the change.

Mount a Disk in Linux

This guide covers how to identify, format, and mount a new or additional disk on a Linux server — including making the mount persistent across reboots.

Prerequisites

  • Root or sudo access
  • A disk or partition to mount (e.g., a secondary EBS volume, a new data drive)

Steps

  1. Identify the disk device:
    lsblk

    Look for the new disk, commonly /dev/sdb, /dev/nvme1n1, etc. It appears without a mount point.

  2. Verify if the disk has an existing filesystem:
    sudo file -s /dev/nvme1n1

    Output of data means the disk is empty (no filesystem). Output of a filesystem type means it already has one.

  3. Format the disk (skip if it already has a filesystem):
    sudo mkfs.ext4 /dev/nvme1n1
  4. Create a mount point:
    sudo mkdir /mnt/data
  5. Mount the disk:
    sudo mount /dev/nvme1n1 /mnt/data
  6. Verify the mount:
    df -h /mnt/data
    lsblk
  7. Make the mount persistent across reboots:
    # Get the UUID of the disk
    sudo blkid /dev/nvme1n1
    
    # Add to /etc/fstab (replace UUID with your value)
    echo "UUID=your-uuid-here  /mnt/data  ext4  defaults,nofail  0  2" | sudo tee -a /etc/fstab

    The nofail option prevents the system from hanging on boot if the disk is not present.

  8. Test fstab before rebooting:
    sudo umount /mnt/data
    sudo mount -a
    df -h /mnt/data

Verify

df -h
lsblk

The disk should appear mounted at /mnt/data with the correct size.

Troubleshooting

  • If mount fails with "wrong fs type", the disk may not be formatted — run mkfs.ext4 first.
  • If the disk doesn't appear in lsblk after attaching (e.g., in AWS), run sudo partprobe or restart the instance.

Fix "Can't Open Display" X11 Forwarding Error

The "Can't open display" error occurs when attempting to run a graphical application over SSH with X11 forwarding. This guide explains the causes and how to fix them.

Symptom

xterm: Xt error: Can't open display:
xterm: DISPLAY is not set
Error: Can't open display: localhost:10.0

Prerequisites

  • An X server running on your local machine (XQuartz on macOS, Xming/VcXsrv on Windows, or the native X server on Linux)
  • SSH access to the remote server

Step 1: Enable X11 Forwarding in SSH

Connect with X11 forwarding enabled:

ssh -X user@remote-server
# or (trusted, allows more features)
ssh -Y user@remote-server

Step 2: Enable X11 Forwarding on the Server

sudo nano /etc/ssh/sshd_config

Ensure these lines are present and uncommented:

X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes

Restart SSH:

sudo systemctl restart sshd

Step 3: Install xauth on the Server

# Debian/Ubuntu
sudo apt-get install xauth

# RHEL/CentOS
sudo yum install xorg-x11-xauth

Step 4: Verify the DISPLAY Variable

echo $DISPLAY

Should output something like localhost:10.0. If empty, X11 forwarding is not working.

Step 5: Test X11 Forwarding

xterm &
# or
xclock &

A graphical window should appear on your local machine.

Common Fixes

  • macOS users: Install and launch XQuartz before connecting via SSH.
  • Windows users: Install and start Xming or VcXsrv before the SSH session.
  • Permission error: Run xauth list on the server to confirm a cookie is present. If not, reconnect with -X.
  • sudo and X11: When switching to another user with sudo, copy the xauth cookie:
    sudo XAUTHORITY=$HOME/.Xauthority xterm

Verify

echo $DISPLAY
xdpyinfo | head -5

A successful output confirms X11 forwarding is working.

AWK Commands Reference

A quick reference for common awk commands used for text processing, column extraction, and data manipulation in shell scripts and one-liners.

Basic Syntax

awk 'pattern { action }' file
  • If no pattern is given, the action runs on every line.
  • If no action is given with a pattern, matching lines are printed.
  • $0 = entire line, $1 = first field, $2 = second field, etc.

Working with CSV and Delimited Files

# Print first column from a CSV
awk -F ',' '{print $1}' input.csv

# Print first and second columns
awk -F ',' '{print $1, $2}' input.csv

# Print with a custom output separator
awk -F ',' 'BEGIN{OFS="	"} {print $1, $2}' input.csv

Field and Line Operations

# Print lines where field 3 equals "active"
awk '$3 == "active" {print}' file.txt

# Print lines matching a regex pattern
awk '/error/' /var/log/syslog

# Print line numbers with output
awk '{print NR, $0}' file.txt

# Print total number of lines
awk 'END {print NR}' file.txt

# Print last field of each line
awk '{print $NF}' file.txt

# Skip header line and process remaining
awk 'NR>1 {print $1}' file.csv

Math and Aggregation

# Sum a column
awk '{sum += $2} END {print "Total:", sum}' file.txt

# Calculate average
awk '{sum += $1; count++} END {print "Average:", sum/count}' file.txt

# Find max value in column
awk 'BEGIN{max=0} {if($1>max) max=$1} END{print max}' file.txt

Useful One-Liners

# Remove duplicate lines (preserve order)
awk '!seen[$0]++' file.txt

# Print lines between two patterns
awk '/START/,/END/' file.txt

# Print every other line
awk 'NR%2==0' file.txt

# Count occurrences of a pattern
awk '/error/{count++} END{print count}' /var/log/syslog

# Extract IP addresses from Apache log
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -10

BEGIN and END Blocks

awk 'BEGIN {print "Starting..."} {print $1} END {print "Done."}' file.txt

Notes

  • Use -F to set the field separator (e.g., -F ':', -F ' ', -F ',').
  • For multi-file programs, use awk -f script.awk.
  • gawk (GNU awk) offers extended features including OFMT, network access, and more.

strace Cheat Sheet

strace intercepts and records system calls made by a process. This cheat sheet covers the most useful options and common debugging patterns.

Basic Usage

# Trace a command from start
strace command

# Attach to a running process by PID
strace -p PID

# Trace and all child processes (follow forks)
strace -f command

Output Control

# Save output to a file
strace -o /tmp/trace.log command

# Show timestamps (wall clock time)
strace -t command

# Show relative timestamps between syscalls
strace -r command

# Show time spent in each syscall
strace -T command

# Increase string length in output (default 32)
strace -s 1024 command

Filter by Syscall Type

# Only trace file-related syscalls
strace -e trace=file command

# Only trace network-related syscalls
strace -e trace=network command

# Only trace specific syscalls
strace -e trace=open,read,write,close command

# Exclude a syscall
strace -e trace=\!futex command

Statistics Mode

# Print syscall count and time summary
strace -c command

Produces a table with columns: % time, seconds, usecs/call, calls, errors, syscall name.

Common Debugging Patterns

# Find which config files a program reads
strace -e trace=openat command 2>&1 | grep -v ENOENT

# Find why a process fails (look for EACCES, ENOENT)
strace command 2>&1 | grep -E "EACCES|ENOENT|EPERM"

# Trace network connections
strace -e trace=connect command 2>&1 | grep -A2 "connect("

# Debug a slow website (find time spent per syscall)
strace -T -e trace=read,write,recv,send -p $(pgrep nginx | head -1)

# Trace an already-running process and save to file
strace -f -T -o /tmp/app_trace.log -p PID

Troubleshooting with strace

# Check why a service fails to start
strace -f systemctl start myservice 2>&1 | tail -30

# Find missing libraries
strace -e trace=open ./myapp 2>&1 | grep "No such file"

# Debug permission issues
strace -e trace=file myapp 2>&1 | grep "EACCES"

Notes

  • strace output goes to stderr by default. Use 2>&1 to pipe it, or -o file to save it.
  • Attaching strace to production processes adds overhead — use briefly and detach.
  • Use ltrace to trace library function calls (a higher abstraction than syscalls).
  • On systems with restricted ptrace (kernel.yama.ptrace_scope = 1), you may need to run strace as root.

SSH Commands Reference

A practical reference for SSH commands covering single-line execution, tunneling, key management, and common configuration options.

Basic Connection

# Connect to a remote host
ssh user@hostname

# Connect on a non-standard port
ssh -p 2222 user@hostname

# Connect with a specific private key
ssh -i ~/.ssh/my_key.pem user@hostname

Run Commands Remotely

# Run a single command
ssh user@host command
ssh admin@webserver uptime

# Run multiple commands
ssh user@host "command1; command2; command3"
ssh user@host "ls /etc/nginx; nginx -t"

# Run a command with sudo (allocate a TTY)
ssh -t user@host sudo systemctl restart nginx

SSH Tunneling and Port Forwarding

# Local port forwarding (access remote service locally)
# Forwards localhost:3306 -> remote_host:3306
ssh -L 3306:localhost:3306 user@remote_host

# Remote port forwarding (expose local service remotely)
ssh -R 8080:localhost:80 user@remote_host

# Dynamic SOCKS proxy (use as a proxy)
ssh -D 1080 user@remote_host

File Transfer

# Copy file to remote host
scp localfile.txt user@host:/remote/path/

# Copy file from remote host
scp user@host:/remote/file.txt ./local/

# Copy entire directory recursively
scp -r ./localdir user@host:/remote/path/

# Transfer with rsync (preferred for large/resumable transfers)
rsync -avz ./localdir/ user@host:/remote/path/

SSH Key Management

# Generate a new SSH key pair
ssh-keygen -t ed25519 -C "your_email@example.com"

# Copy public key to remote server
ssh-copy-id user@host

# Manually add public key
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

# Test key authentication
ssh -i ~/.ssh/id_ed25519 user@host echo "key works"

SSH Config File (~/.ssh/config)

Host webserver
    HostName 192.168.1.10
    User admin
    IdentityFile ~/.ssh/webserver_key.pem
    Port 22

Host jump
    HostName jump.example.com
    User ec2-user
    ProxyJump bastion

With this config, connect simply with: ssh webserver

Useful SSH Options

# Keep connection alive (prevent timeout)
ssh -o ServerAliveInterval=60 user@host

# Disable strict host key checking (for scripts/automation)
ssh -o StrictHostKeyChecking=no user@host

# Enable X11 forwarding
ssh -X user@host

# Verbose mode (debug connection issues)
ssh -v user@host
ssh -vvv user@host   # maximum verbosity

Notes

  • Use ed25519 keys for new setups — they are more secure and faster than RSA.
  • Keep ~/.ssh permissions at 700 and private key files at 600.
  • Use ssh-agent and ssh-add to avoid entering passphrases repeatedly.

Fix SSH Timeout When IPMI Access Works

If SSH connections to a server time out but you can still access the server via IPMI (out-of-band management), the issue is typically with the network interface configuration on the OS itself. A common cause is the network interface losing its IPv4 configuration.

Diagnosis

  1. Log in via IPMI console.
  2. Check the network interface:
    ifconfig
    ip addr show

    If the interface (e.g., eno1) has no IPv4 address assigned, that is the problem.

  3. Check if IPv6 is still working while IPv4 is not:
    ping6 -c 3 google.com
    ping -c 3 google.com

Fix: Release and Renew the DHCP Lease

# Get the interface name (look for eth0, eno1, ens3, etc.)
ifconfig

# Release the current DHCP lease
sudo dhclient -r eno1

# Request a new DHCP lease
sudo dhclient eno1

Replace eno1 with the actual interface name from ifconfig.

Alternative Fix: Restart the Network Service

# Systemd-based systems
sudo systemctl restart networking       # Debian/Ubuntu
sudo systemctl restart NetworkManager   # CentOS/RHEL with NetworkManager

# Bring the interface down and back up
sudo ip link set eno1 down
sudo ip link set eno1 up
sudo dhclient eno1

Make the Fix Persistent

If this happens repeatedly, check the network configuration:

# Debian/Ubuntu — /etc/network/interfaces
auto eno1
iface eno1 inet dhcp

# Or for a static IP
auto eno1
iface eno1 inet static
  address 192.168.1.100
  netmask 255.255.255.0
  gateway 192.168.1.1
  dns-nameservers 8.8.8.8

Verify

ip addr show eno1

The interface should have an IPv4 address assigned. Then test SSH from an external machine.

Troubleshooting

  • Check the SSH daemon is running: systemctl status sshd
  • Check firewall rules: iptables -L -n | grep 22
  • Check SSH logs for clues: journalctl -u sshd -n 50
  • If the interface repeatedly loses its IP, investigate the DHCP server or consider setting a static IP.