Skip to content

About

Useful scripts for server administration

Resources

Stars

2 stars

Watchers

1 watching

Forks

Latest commit

 

History

200 Commits

Folders and files

Repository files navigation

Useful Server Scripts

License: MIT Bash Platform Security CSF Compatible Maintenance PRs Welcome

A collection of battle-tested bash scripts for Linux server administration, with a focus on security hardening, WordPress management, and cPanel/WHM server maintenance.

Author: Gabriel Serafini (gserafini@gmail.com)

Table of Contents

Overview

This repository provides production-ready scripts for system administrators managing:

  • WordPress Servers: Automated security monitoring and IP blocking
  • cPanel/WHM Environments: Domain management and account maintenance
  • SVN-Managed Sites: Version control workflow automation
  • General Server Security: Rootkit scanning, permission fixes, process management

These scripts are designed to be installed system-wide and run with root privileges, typically via cron jobs or manual administrative tasks.

Security Warning

⚠️ CRITICAL: These scripts modify production firewall rules and system configurations.

  • All scripts require root/sudo privileges
  • Scripts modify live firewall rules and can affect server connectivity
  • Always test in a VM or staging environment first
  • Review all scripts before running them on production systems
  • Keep backups of firewall configurations before making changes
  • The --init and --clear operations for csf_ban_wp_login_attackers are particularly sensitive

Use at your own risk. Always understand what a script does before executing it.

Requirements

Core Dependencies

  • Linux server (tested on RHEL/CentOS and Debian/Ubuntu)
  • Bash 4.0 or higher
  • Root access

Script-Specific Dependencies

Script Dependencies
csf_ban_wp_login_attackers ipset, iptables, csf, whois, conntrack-tools (optional), Apache logs
get_nameservers.sh dig, whois, /etc/userdomains (cPanel)
ban_ips.sh csf, geoiplookup
svn_add_remove svn (Subversion)
commit_htaccess_files.sh svn, /etc/trueuserowners (cPanel)
chkrootkit_output_clean chkrootkit

Installing conntrack-tools (recommended for csf_ban_wp_login_attackers):

# RHEL/CentOS
sudo yum install -y conntrack-tools ipset

# Debian/Ubuntu
sudo apt-get install -y conntrack ipset

Installation

Automated Installation (Recommended)

Clone the repository and run the install script:

git clone https://github.com/gserafini/useful-server-scripts.git
cd useful-server-scripts/
sudo ./install.sh

The install script will:

  • Create symlinks for all scripts in /usr/local/bin/
  • Detect and update existing symlinks
  • Provide a summary of what was installed
  • Show next steps for setup

Manual Installation

Alternatively, you can manually symlink scripts to /usr/local/bin/:

cd useful-server-scripts/
for f in `ls scripts/` ; do sudo ln -s $PWD/scripts/$f /usr/local/bin/$f ; done

Both methods make all scripts available system-wide as commands (e.g., sudo csf_ban_wp_login_attackers --init).

Quick Start

For WordPress Security (csf_ban_wp_login_attackers)

  1. Install dependencies:

    sudo yum install -y ipset conntrack-tools  # or apt-get on Debian/Ubuntu
  2. Initialize the system (one-time setup):

    sudo csf_ban_wp_login_attackers --init
  3. Test a manual ban:

    sudo csf_ban_wp_login_attackers --blacklist 1.2.3.4 "Test ban"
  4. Set up automated scanning (add to root's crontab):

    # Run every 5 minutes
    */5 * * * * /usr/local/bin/csf_ban_wp_login_attackers
  5. Generate abuse reports:

    sudo csf_ban_wp_login_attackers --abuse 1.2.3.4

For SVN WordPress Management

In a WordPress directory managed by SVN:

svn_add_remove

Follow the interactive prompts to schedule additions/deletions.

For cPanel Domain Information

# Human-readable output
get_nameservers.sh

# TSV format for spreadsheets
get_nameservers.sh --csv

Scripts

csf_ban_wp_login_attackers

Primary script: High-performance IP banning system using IPSET and CSF firewall integration.

Purpose: Automatically scan Apache logs for malicious activity (wp-login brute force, shell probes, ModSecurity violations) and ban offending IPs at the kernel level using IPSET.

Key Features:

  • Handles 50,000+ bans efficiently using IPSET
  • Integrates with CSF firewall via /etc/csf/csfpost.sh
  • Replays the persistent tracking file automatically after every CSF rebuild
  • Excludes assigned local, csf.allow, and csf.ignore IPv4 addresses from replay
  • Refuses manual, batch, delegated, and LF_DISTATTACK migration bans for exact csf.allow or csf.ignore IPv4 entries
  • Preserves complete multiword signatures, scans each hard-linked cPanel log only once, and counts one validated source IP per matched event
  • Treats only precise missing-script messages and high-confidence blocking signatures as ban triggers, not generic PHP errors or non-blocking OWASP warnings
  • Terminates existing connections when banning (requires conntrack-tools)
  • Supports safe ModSecurity exec bans through a no-argument CGI adapter
  • Generates abuse evidence reports with WHOIS integration
  • Supports manual whitelist/blacklist management
  • Rotatable ban list with permanent ban threshold

Setup & Configuration

One-time initialization (creates IPSET table and firewall rules):

sudo csf_ban_wp_login_attackers --init

This creates:

  • IPSET table named high_volume_bans
  • iptables DROP rule in /etc/csf/csfpost.sh
  • post-rebuild replay command in /etc/csf/csfpost.sh
  • Tracking file at /etc/csf/ipset_tracking_high_volume_bans.log

Batch persistence and concurrent writers

--blacklist-file validates all nonempty inputs before setup or firewall work, canonicalizes IPv4 addresses, and reports each original input line, including duplicates, protected addresses and errors. It loads protected policy once and normalizes one private tracker snapshot once. Duplicate tracker rows keep the first permanent (DO NOT DELETE) reason when present; unrelated comments and unique reasons remain. Unsupported tracker rows stop the batch before its native add.

There are two complete reads of the live tracker, independent of batch size: the snapshot copy and a final SHA-256 beforeimage guard. Parsing/normalization happens once on the private snapshot. One ipset restore is attempted, followed by individual live membership checks. A native restore error can leave later commands unattempted; it is never replayed automatically. Only verified members receive missing persistent entries. The result for every input separates live/persistence qualification from session-cleanup status. Existing single-IP syntax and existing reasons remain supported.

All eight tracker writer families use a stable private sidecar lock for snapshot/publication: blacklist, distributed-LFD migration, unblacklist, protected reconciliation, local whitelist, initialization, clear, and automatic scan. Publication checks canonical path, device/inode, owner/group, link count, mode, size, timestamps and SHA-256; it atomically renames a same-filesystem candidate while preserving owner/group/mode. Symlinks, multiple links and foreign ownership are refused. Setup, kernel actions and recursive CSF reload hooks run outside this lock. A stale writer stops rather than overwriting another writer. This is file-publication protection, not an atomic firewall transaction. External editors must honor the sidecar lock; an uncoordinated write after the final guard is outside this protocol.

Kernel success followed by persistence failure returns nonzero and retains a root-private transaction directory with source/planned afterimage, hashes, metadata, native results and per-input failure receipt. Do not roll back or retry automatically: concurrent changes and live coverage must be reviewed first. Successful transactions discard their own known temporary files. The automatic scanner retains its existing native kernel planner; this change guards its tracker publication and reports unqualified kernel-plan outcomes rather than asserting every kernel operation succeeded.

Batch session cleanup has a ten-second aggregate scheduling budget and a two-second native KILL timeout per attempted IP. After the budget, remaining cleanup is explicitly deferred_budget, with no background leftovers. Exit 124/137 means bounded timeout; other unexpected exits are errors. Invoking cleanup does not prove active connections closed. Coverage and persistence remain independently reported. The runtime uses installed Bash/coreutils/flock/gawk, including legacy Bash 4.1/coreutils 8.4; Python and strace are fixture-test dependencies only.

Run only the isolated fixtures in a new private directory, never a live ban to test this feature:

fixture_dir=$(mktemp -d "$PWD/.fixture-run.XXXXXX")
chmod 700 "$fixture_dir"
TMPDIR="$fixture_dir" python3.12 -B -m unittest discover -s tests -p 'test_tracking_*.py'
TMPDIR="$fixture_dir" python3.12 -B tests/test_csf_blacklist_batch.py
TMPDIR="$fixture_dir" python3.12 -B tests/test_batch_cleanup.py

Daily Operations

Manual IP management:

# Ban an IP
sudo csf_ban_wp_login_attackers --blacklist 1.2.3.4 "Brute force wp-login"
# Alias: --block

# Ban a reviewed list after validating the entire input before any mutation
sudo csf_ban_wp_login_attackers --blacklist-file campaign-ips.txt "Distributed crawler campaign"

# One private tracker normalization, one native ipset restore, one guarded publication.
# Inspect every BATCH_INPUT disposition; nonzero may leave verified live coverage.
# Never blindly retry a partial failure: inspect the retained private evidence first.

# Read the reviewed list from stdin
printf '%s\n' 1.2.3.4 5.6.7.8 | sudo csf_ban_wp_login_attackers --blacklist-file - "Distributed crawler campaign"

# Reverse this tool's false-positive deny without globally allowlisting the IP
sudo csf_ban_wp_login_attackers --unblacklist 1.2.3.4 "Reviewed false positive"

# Inspect exact live, tracked, CSF deny, and CSF allow coverage without mutation
sudo csf_ban_wp_login_attackers --check-ip 1.2.3.4

# Whitelist an IP (adds to /etc/csf/csf.allow)
sudo csf_ban_wp_login_attackers --whitelist 1.2.3.4 "Trusted admin"
# Alias: --unblock

# Remove sources protected by local or CSF numeric trust policy from live/tracked bans
# without restarting or reloading CSF
sudo csf_ban_wp_login_attackers --reconcile-allowlist

# Clear all bans (DANGEROUS - requires confirmation)
sudo csf_ban_wp_login_attackers --clear

Numeric trust policy covers exact IPv4 addresses and CIDRs in csf.allow and csf.ignore, recursive Include files, and numeric address operands in s= or d= advanced-filter fields. Ports, owner fields, comments, IPv6, and hostnames do not add IPv4 protection; this helper performs no DNS lookups. Address operands conservatively protect the whole address/range regardless of port or direction; standalone s=/d= address fields also receive this protection, without claiming to validate a complete CSF rule.

CSF documents absolute Include paths with no trailing comments. The protection helper also accepts relative paths resolved against the including file's directory as a conservative extension. An Include must name one literal, unquoted path; trailing comments are rejected. Missing/unreadable files (including either root file), include cycles, and malformed numeric IPv4/CIDR entries abort the operation before a partial policy can be used. The builder publishes only after validation; on failure its output stays unchanged, including an already empty output. Builder destinations must be regular private temporary files (or absent); symlinks and directories are rejected.

Automated scanning (default mode, no flags):

sudo csf_ban_wp_login_attackers

Scans default log paths and bans IPs matching keyword patterns. Add to cron:

*/5 * * * * /usr/local/bin/csf_ban_wp_login_attackers

ModSecurity persistent bans:

ModSecurity 2.x executes external programs without command-line arguments. Rules must call modsecurity_persistent_blacklist directly and rely on the CGI REMOTE_ADDR environment variable:

SecRule REQUEST_URI "@rx malicious-pattern" \
  "phase:1,id:999999,deny,status:403,exec:'/usr/local/useful-server-scripts/scripts/modsecurity_persistent_blacklist'"

The adapter validates the client IPv4 address, serializes concurrent burst matches, and invokes csf_ban_wp_login_attackers --blacklist through the existing sudo policy.

Custom log paths:

sudo csf_ban_wp_login_attackers --logs "/var/log/apache2/access.log /var/log/nginx/access.log"

Abuse Reporting (Read-Only)

These commands generate evidence for reporting abuse to ISPs. They don't modify firewall state.

Basic report (default threshold: 100 keyword matches):

sudo csf_ban_wp_login_attackers --abuse 1.2.3.4

Report with email draft (includes WHOIS abuse contact lookup):

sudo csf_ban_wp_login_attackers --abuse-email --abuse 1.2.3.4

Time-limited report (last 7 days):

sudo csf_ban_wp_login_attackers --abuse-days 7 --abuse 1.2.3.4

Custom threshold (50 hits instead of 100):

sudo csf_ban_wp_login_attackers --abuse-min 50 --abuse 1.2.3.4

Date range filtering:

sudo csf_ban_wp_login_attackers --abuse-since "2025-10-01" --abuse-until "2025-10-15" --abuse 1.2.3.4

Raw log search (grep wrapper):

sudo csf_ban_wp_login_attackers --raw-grep '1.2.3.4'
# Alias: --raw-search

All Command-Line Options

Flag Description
--init Initialize IPSET table and CSF integration (one-time setup)
--rebuild-live-set Bulk-replay tracked bans while excluding local and CSF-protected IPv4 addresses/CIDRs
--reconcile-allowlist Remove local and csf.allow/csf.ignore protected IPv4 addresses/CIDRs from live and tracked bans without a CSF reload
--resize-live-set MAX Atomically resize the live set and persist its capacity
--whitelist-local-addresses Remove assigned local addresses from bans and persist them in csf.allow
--blacklist IP [MSG] Manually ban an IP (alias: --block); refuses exact csf.allow and csf.ignore entries
`--blacklist-file FILE - [MSG]`
--unblacklist IP [MSG] Remove an exact IP from high_volume_bans and its tracking file without adding it to csf.allow or changing independent CSF deny policy
--check-ip IP Report exact live ipset, tracking, CSF deny, parent /24, and CSF allow coverage without mutation
--promote-cidr CIDR [MSG] Atomically replace covered CSF child bans with a canonical IPv4 /24; refuses unsafe capacity changes and rolls back on failure
--migrate-lfd-distributed Preserve recent LFD LF_DISTATTACK blocks in high_volume_bans, then atomically remove only their evictable exact-IP CSF entries
--whitelist IP [MSG] Remove IP from bans and add to CSF allow list (alias: --unblock)
--clear Flush all bans from IPSET and tracking file (DANGEROUS)
--logs "PATH1 PATH2" Override default log paths to scan
--abuse IP [MIN_HITS] Generate abuse report (alias: --abuse-report)
--abuse-min N Set default minimum hits for abuse reports
--abuse-days N Restrict search to last N days (best-effort)
--abuse-since STRING Filter logs by start date substring
--abuse-until STRING Filter logs by end date substring
--abuse-email Include WHOIS lookup and draft email in report
--raw-grep STRING Search all logs for string (alias: --raw-search)

Configuration Variables (in script)

Edit these at the top of the script if needed:

  • IP_SET_NAME: IPSET table name (default: high_volume_bans)
  • MAX_BANS: Maximum IPs in IPSET (default: 50,000)
  • MAX_PERMANENT_BANS: Max permanent bans (default: 5,000)
  • MAX_VIOLATION_COUNT: Threshold for permanent bans (default: 20 hits)
  • DEFAULT_LOG_PATHS: Apache log locations to scan

Implementation Notes

  • Reports are non-destructive - they read logs and write evidence to /tmp
  • Date-range handling is best-effort based on log timestamp formats
  • WHOIS abuse contact lookup is heuristic - always verify before emailing
  • Requires CSF installed and configured before --init
  • conntrack-tools optional but recommended for immediate session termination

svn_add_remove

Purpose: Interactive SVN workflow helper for WordPress sites.

Use Case: After WordPress auto-updates, quickly schedule new/modified/removed files for SVN commit.

What it does:

  1. Runs svn status to find unknown (?) and missing (!) files
  2. Excludes cache files and error logs
  3. Prompts to schedule additions and deletions
  4. Handles replaced resources (~)
  5. Fixes file ownership when run as root

Usage:

cd /path/to/wordpress
svn_add_remove

Follow interactive prompts to review and schedule changes.

Requirements: Subversion (svn)


get_nameservers.sh

Purpose: Extract domain information for all accounts on a cPanel/WHM server.

What it does:

  • Reads /etc/userdomains (cPanel's domain-to-user mapping)
  • Filters for top-level domains (excludes subdomains)
  • Queries DNS for A records using dig
  • Performs WHOIS lookups for registrar and nameservers
  • Groups output by cPanel user

Usage:

# Human-readable grouped output
get_nameservers.sh

# TSV format (tab-separated for spreadsheets)
get_nameservers.sh --csv

Output includes:

  • Domain name
  • IP address(es)
  • Registrar
  • Nameservers

Requirements: dig, whois, cPanel environment (/etc/userdomains)


ban_ips.sh

Purpose: Bulk ban IPs from copy-pasted lists using CSF.

What it does:

  1. Accepts multi-line input (paste IPs, press Enter, then Ctrl+D)
  2. Extracts IPs using regex (handles various formats)
  3. Performs GeoIP lookup for each IP
  4. Bans via CSF with geographic metadata

Usage:

ban_ips.sh
# Paste IPs (one per line or mixed text)
# Press Ctrl+D to process

Requirements: csf, geoiplookup


fix_permissions

Purpose: Recursively fix file and directory permissions for web files.

What it does:

  • Sets files (.php, .js, .png, .jpg, .jpeg, .txt, .css) to 644
  • Sets directories to 755
  • Defaults to current directory or accepts path argument

Usage:

# Fix current directory
fix_permissions

# Fix specific path
fix_permissions /home/user/public_html

Use Case: After manual file uploads or ownership issues.


Other Utility Scripts

commit_htaccess_files.sh

Auto-commits modified .htaccess files across all cPanel accounts to SVN, then fixes ownership.

Requirements: svn, cPanel environment (/etc/trueuserowners)

sudo commit_htaccess_files.sh

chkrootkit_output_clean

Runs chkrootkit -q and filters known false positives (e.g., cPanel's port 465).

Requirements: chkrootkit installed at /usr/local/src/chkrootkit

chkrootkit_output_clean

kill_long_php.sh

Kills all PHP processes running longer than 1 hour.

Usage:

sudo kill_long_php.sh

Use Case: Terminate runaway PHP scripts consuming resources.


inotify_watch_for_csf_whitelist_additions.sh

Monitors a file using inotify and adds IPs to CSF whitelist when detected.


inotify_watch_for_csf_whitelist_requests.sh

Monitors for whitelist requests and processes them.

whitelist_notify.sh

Sends the owner a whitelist audit message after a WhitelistMyIP request. In addition to CSF deny files and external blocklists, it captures live and tracked high_volume_bans evidence on both hosting servers. This preserves the original automated-ban reason before the next allowlist reconciliation removes the trusted address from the auxiliary deny set.

The notifier also queues a ClaudeGram wake in the server-operations chat with the client name, site, IP, and captured block evidence. The wake asks the ops agent to reconstruct the original block, classify a false positive versus real abuse, fix a faulty detector when appropriate, and verify the allowlist and site health. Client email addresses are intentionally excluded from the wake prompt. Repeated requests for the same IP are collapsed into one investigation during a 10-minute window, while the existing email remains as a delivery fallback.

sudo ./scripts/whitelist_notify.sh 203.0.113.10 "Client Name" \
  "client@example.com" "example.com"

restart_sshd.sh

Simple wrapper to restart SSH daemon.


rm_old_logs

Removes old log files (implementation TBD - check script for details).


ssh_count.sh

Counts active SSH connections.


License

This project is licensed under the MIT License - see the LICENSE file for details.

Copyright (c) 2014 Gabriel Serafini

Contributing

Contributions are welcome! Please:

  1. Test thoroughly in a VM/staging environment
  2. Document changes in commit messages
  3. Follow existing code style (bash best practices, shellcheck compliance)
  4. Add usage examples for new scripts or flags
  5. Update README.md if adding new scripts or features

Bug Reports: Open an issue with reproduction steps and environment details.

Security Issues: Email gserafini@gmail.com directly for security vulnerabilities.


Repository: https://github.com/gserafini/useful-server-scripts

Built with assistance from: Gemini Advanced (for csf_ban_wp_login_attackers and get_nameservers.sh)

cPanel WP-CLI launcher (dc3-1)

scripts/wp-cpanel is the dc3-1 /usr/local/bin/wp launcher. It uses cPanel's CLI PHP selector with --ea-reference-dir=. so maintenance commands use the PHP configured for the current WordPress directory, instead of the system default selected from the root-owned PHAR directory. Always change into the intended WordPress root before plain wp commands. For commands from elsewhere, use the verified explicit phpXX /usr/local/bin/wp --path=... form.

The unmodified vendor PHAR is /usr/local/lib/wp-cli/wp-cli.phar. Both argv arrays name that file so wp cli update updates the PHAR without replacing the launcher. Explicit PHP invocations continue to work. This does not change cPanel's system default or any site's web PHP configuration.

Before deployment, preserve/hash the installed PHAR, install an identical copy at that library path, and test the staged launcher as the account owner with --info and a read-only plugin-loading command. Only then atomically replace /usr/local/bin/wp with the reviewed launcher. Keep the original PHAR as a rollback receipt. Do not run the generic all-script symlink installer on this launcher; deployment is explicit and dc3-1-only.

Test: python3.12 tests/test_wp_cpanel.py (requires cPanel EA PHP 8.2/8.5 and the Share root). The fixture runs outside cPanel's noexec securetmp.

This repository uses master; publish the checked-out branch with git push origin HEAD rather than assuming a main branch.

About

Useful scripts for server administration

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages