WordPress on the Server Terminal – Automation, Auditing, and Administration

Published: March 30, 2026 · Author: Marcin Szewczyk-Wilgan

The WordPress admin panel and WP-CLI are the tools you reach for first. But there are situations where neither is enough – bulk domain replacement across dozens of theme files after a migration, searching for a backdoor in thousands of PHP files after a breach, analyzing server logs for brute force attacks, or auditing theme code before converting to a block theme. In such cases, four terminal commands – grep, find, sed, and awk – let you do in seconds what would take hours manually. In this article, you will find 85 ready-to-use commands grouped by topic – from security and migration, through optimization and log analysis, to theme work and bulk file operations.

1. Security and post-breach audit

A WordPress breach is a scenario where speed and precision matter. You need to quickly determine which files were modified, where malicious code is hidden, and what vector the attacker used. Manually reviewing thousands of files in wp-content/ is out of the question. The commands below let you scan the entire installation for typical signs of compromise in seconds. More on attack vectors and multi-layered protection in the article WordPress Security in 2025/2026.

PHP files modified in the last 24 hours – the first thing you check after suspecting a breach:

find wp-content/ -name "*.php" -mtime -1

Search for eval() – a typical malware marker – almost every WordPress backdoor uses eval() to execute dynamic code:

grep -rn "eval(" wp-content/ --include="*.php"

Base64-encoded payloads – the second most common vector: attackers encode payloads in base64 to avoid detection:

grep -rl "base64_decode" wp-content/ --include="*.php"

Files with 777 permissions – wide-open doors. No file in a WordPress installation should have full permissions:

find . -type f -perm 0777

Bulk-set correct permissions – 644 for files, 755 for directories:

find . -type f -exec chmod 644 {} + find . -type d -exec chmod 755 {} +

Files with suspiciously long names – backdoors often have random, multi-character names:

find wp-content/ -name "*.php" | awk '{ if (length($0) > 100) print }'

PHP files in the uploads directory – they should not be there. If they are – it is almost certainly malware:

find wp-content/uploads/ -name "*.php"

Hidden files (starting with a dot) – attackers hide scripts in files like .backdoor.php or .htaccess with redirects:

find wp-content/ -name ".*" -type f

Shell backdoor functionsshell_exec, passthru, system() allow executing system commands from PHP:

grep -rln "shell_exec\|passthru\|system(" wp-content/ --include="*.php"

Iframe injections in themes – a popular method of redirecting users to malicious sites:

grep -rn "<iframe" wp-content/themes/ --include="*.php"

Include/require from external URLs – legitimate WordPress code does not fetch files from the internet via include:

grep -rn "include\|require" wp-content/ --include="*.php" | grep "http"

PHP files larger than 1 MB – an unusual size for a WordPress file; worth checking what is inside:

find wp-content/ -name "*.php" -size +1M

Search for $_GET, $_POST, $_REQUEST directly in themes – potential entry points for injection attacks:

grep -rn "\$_GET\|\$_POST\|\$_REQUEST" wp-content/themes/ --include="*.php"

Search for file_put_contents and file_get_contents – file read/write functions often abused by malware:

grep -rn "file_put_contents\|file_get_contents" wp-content/ --include="*.php" | grep -v "vendor/"

2. Migration and domain change

Migrating WordPress to a new server or changing the domain is a moment where oversights happen easily. Hardcoded URLs of the old domain can lurk in theme PHP files, CSS stylesheets, SQL dumps, and even .htaccess files. WP-CLI and migration plugins handle the database (including serialized data), but theme, plugin, and configuration files need to be searched and fixed manually. The entire process is described step by step in the guide WordPress Server Migration.

Find all occurrences of the old domain in files – the starting point of every migration:

grep -rl "old-domain\.com" wp-content/

Count occurrences per file – so you know where most of the work is:

grep -rc "old-domain\.com" wp-content/ | awk -F: '$2 > 0' | sort -t: -k2 -rn

Replace domain in all theme PHP files:

find wp-content/themes/ -name "*.php" -exec sed -i 's/old-domain\.com/new-domain\.com/g' {} +

Replace domain in an SQL dump (plain text – does not handle serialization; use WP-CLI search-replace for that):

sed -i 's|https://old-domain.com|https://new-domain.com|g' dump.sql

Replace table prefix in an SQL dump:

sed -i 's/wp_/wp_client_/g' dump.sql

Replace HTTP with HTTPS in all CSS files:

find wp-content/themes/ -name "*.css" -exec sed -i 's|http://|https://|g' {} +

Find hardcoded HTTP (no S) URLs in themes – mixed content is a common problem after switching to SSL:

grep -rn "http://" wp-content/themes/ --include="*.php" --include="*.css"

Find hardcoded absolute paths to the old server:

grep -rn "/home/olduser/public_html" wp-content/ --include="*.php"

Replace absolute paths after relocation:

find wp-content/ -name "*.php" -exec sed -i 's|/home/olduser/public_html|/var/www/html|g' {} +

Replace upload paths after restructuring:

find . -name "*.php" -exec sed -i 's|/uploads/2023/|/uploads/2025/|g' {} +

Find leftover references to the old domain in JS files:

grep -rn "old-domain\.com" wp-content/ --include="*.js"

3. Optimization and cleanup

Over time, a WordPress installation accumulates unnecessary files – unused themes, orphaned translation files, duplicate images, dead CSS and JS files. The uploads/ directory grows month after month, and nobody checks how much space is taken up by files from years ago. The commands below will help you clean things up. For a full WordPress performance strategy, see our optimization service and the article Core Web Vitals Optimization.

20 largest files in uploads – you know what to optimize first:

find wp-content/uploads/ -type f -exec du -h {} + | sort -rh | head -20

Upload directory size per year:

du -sh wp-content/uploads/20*/ | sort -rh

Count PNG/JPG images awaiting WebP conversion:

find wp-content/uploads/ \( -name "*.png" -o -name "*.jpg" \) | wc -l

Find JPG images without a WebP counterpart:

find wp-content/uploads/ -name "*.jpg" | while read f; do [ ! -f "${f%.jpg}.webp" ] && echo "$f" done

Remove empty directories after theme cleanup:

find wp-content/themes/old-theme/ -type d -empty -delete

Count PHP lines of code in a theme (excluding blank lines and comments):

find wp-content/themes/theme/ -name "*.php" -exec cat {} + \ | awk 'NF && !/^[[:space:]]*(\/\/|\/\*|\*)/' | wc -l

Find CSS/JS files not modified for a year – dead code candidates:

find wp-content/themes/ \( -name "*.css" -o -name "*.js" \) -mtime +365

Find duplicate files by MD5:

find wp-content/uploads/ -type f -exec md5sum {} + | sort | awk 'a[$1]++{print}'

CSS files larger than 100 KB – candidates for splitting or minification:

find wp-content/themes/ -name "*.css" -size +100k

List all loaded fonts in theme CSS:

grep -rh "@font-face\|woff2\|woff" wp-content/themes/ --include="*.css" | sort -u

Find orphaned translation files (.mo) – plugin removed, but its language files remain:

find wp-content/languages/plugins/ -name "*.mo" | while read f; do plugin=$(basename "$f" | sed 's/-[a-z]\{2\}_[A-Z]\{2\}.mo//') [ ! -d "wp-content/plugins/$plugin" ] && echo "Orphan: $f" done

Count files per plugin – bloated plugins are a potential performance problem:

find wp-content/plugins/ -maxdepth 1 -type d | while read d; do echo "$(find "$d" -type f | wc -l) $d" done | sort -rn | head -15

4. Server log analysis

Server logs are a goldmine of information – if you know how to read them. The Nginx or Apache access.log file contains a record of every HTTP request: IP address, URL, response code, user agent. Meanwhile, the MySQL slow-query.log reveals queries that take too long. Awk and grep let you extract specific data from these files in seconds. If you need help with logging configuration and monitoring – we offer server administration on Linux and FreeBSD.

TOP 20 most requested URLs:

awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20

IP addresses generating the most 404 errors:

awk '$9 == 404 {print $1}' access.log | sort | uniq -c | sort -rn | head -10

Who is attacking wp-login.php (brute force attempts):

grep "POST /wp-login.php" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

Attacks on xmlrpc.php – the second most targeted file after wp-login.php for brute force:

grep "xmlrpc.php" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

HTTP status code distribution – 200/301/404/500:

awk '{print $9}' access.log | sort | uniq -c | sort -rn

Traffic per hour – detect peaks and anomalies:

awk '{print $4}' access.log | cut -d: -f2 | sort | uniq -c

Requests to wp-cron.php per day – too frequent calls can overload the server:

grep "wp-cron.php" access.log | awk '{print $4}' | cut -d: -f1 | sort | uniq -c

User agents scanning for vulnerabilities:

grep -i "sqlmap\|nikto\|nmap\|wpscan\|dirbuster" access.log | awk '{print $1}' | sort -u

Slowest MySQL queries from slow query log:

awk '/^# Query_time/{time=$3} /^SELECT|^UPDATE/{print time, $0}' slow-query.log \ | sort -rn | head -10

Attempts to access wp-config.php (configuration data leak attempt):

grep "wp-config" access.log | awk '{print $1, $7, $9}'

Count unique IP addresses per day:

awk '{print $4, $1}' access.log | cut -d: -f1 | sort -u | cut -d' ' -f1 | uniq -c | sort -rn

Requests returning 500 errors (Internal Server Error):

awk '$9 == 500 {print $4, $7}' access.log | head -20

5. Themes and conversion

Auditing theme code before deployment, converting from a classic theme to a block theme (FSE), searching for deprecated functions, checking what the theme loads and which hooks it uses – these are tasks where grep is indispensable. Instead of clicking through dozens of PHP files, in a few seconds you can extract all add_action, add_filter, wp_enqueue_script, shortcodes, and hardcoded colors from a theme. We discuss the criteria for choosing a WordPress theme in a separate article.

Deprecated WordPress functions in a themequery_posts, create_function, mysql_query:

grep -rn "query_posts\|create_function\|mysql_query\|ereg(" wp-content/themes/ --include="*.php"

All add_action and add_filter hooks in a theme:

grep -rh "add_action\|add_filter" wp-content/themes/theme/ --include="*.php" | sort

Extract hook names only:

grep -rohP "(add_action|add_filter)\(\s*['\"](\K[^'\"]+)" wp-content/themes/theme/ | sort -u

Theme template tags – get_header, get_footer, get_template_part:

grep -rn "get_header\|get_footer\|get_sidebar\|get_template_part" wp-content/themes/theme/

Count how many times each template part is used:

grep -roh "get_template_part([^)]*)" wp-content/themes/theme/ | sort | uniq -c | sort -rn

What the theme loads – wp_enqueue_script and wp_enqueue_style:

grep -rn "wp_enqueue_script\|wp_enqueue_style" wp-content/themes/theme/ --include="*.php"

Shortcodes used in the XML database export:

grep -oP '\[\w+' export.xml | sort | uniq -c | sort -rn

Hardcoded colors in CSS – to be replaced with CSS variables:

grep -oP '#[0-9a-fA-F]{3,6}' wp-content/themes/theme/style.css | sort | uniq -c | sort -rn

Inline styles in PHP templates – to be moved to a stylesheet:

grep -rn 'style="' wp-content/themes/theme/ --include="*.php" | wc -l

Registered sidebars and widgets – important when converting to a block theme:

grep -rn "register_sidebar\|register_widget" wp-content/themes/theme/

Replace Google Analytics tag in all templates:

find wp-content/themes/ -name "*.php" -exec sed -i 's/UA-XXXXXXX-X/G-XXXXXXXXXX/g' {} +

Custom post types and taxonomies registered in a theme (should be in a plugin):

grep -rn "register_post_type\|register_taxonomy" wp-content/themes/ --include="*.php"

Direct SQL queries in a theme – should use $wpdb->prepare():

grep -rn "\$wpdb->query\|\$wpdb->get_" wp-content/themes/ --include="*.php" | grep -v "prepare"

6. Sitemap, SEO, and structure

The technical side of SEO requires regular audits – do all pages have a meta description, does the sitemap contain the right URLs, are .htaccess files not blocking indexing? The commands below let you quickly scan files for missing SEO elements. A full guide to URL structure, sitemaps, and structured data can be found in the article WordPress Technical SEO.

Extract all URLs from sitemap.xml:

awk -F'[<>]' '/<loc>/{print $3}' sitemap.xml

Count URLs in sitemap:

grep -c "<loc>" sitemap.xml

Find .htaccess files in all subdirectories:

find . -name ".htaccess" -type f

Remove BOM (Byte Order Mark) from PHP files – causes problems with HTTP headers:

find . -name "*.php" -exec sed -i '1s/^\xEF\xBB\xBF//' {} +

Check which HTML files are missing a meta description:

find . -name "*.html" | while read f; do grep -qL 'meta name="description"' "$f" && echo "Missing: $f" done

Extract title from all HTML files:

grep -ohP '<title>\K[^<]+' *.html

Compare sitemap URLs with actual files:

awk -F'[<>]' '/<loc>/{print $3}' sitemap.xml | while read url; do path=$(echo "$url" | sed 's|https://domain.com||') [ ! -f ".$path" ] && [ ! -f ".$path/index.html" ] && echo "404: $url" done

Find pages without a canonical tag:

find . -name "*.html" | while read f; do grep -qL 'rel="canonical"' "$f" && echo "Missing canonical: $f" done

7. Databases and WP-CLI

A WordPress database SQL dump can be hundreds of megabytes. Awk and grep let you quickly extract information without importing into MySQL – table sizes, user lists, post counts by type, or wp_options table load. More on database cleaning and tuning in the article WordPress Database Optimization. Need help with MySQL administration or PostgreSQL? We handle that daily.

Table sizes in an SQL dump (approximate, based on INSERT sizes):

awk '/^INSERT INTO/{gsub(/`/,"",$3); size[$3]+=length($0)} END{for(t in size) print int(size[t]/1024)"KB", t}' dump.sql \ | sort -rn | head -20

Count posts per post type in XML export:

grep -oP '<wp:post_type>\K[^<]+' export.xml | sort | uniq -c | sort -rn

Extract user email addresses from a dump:

grep "INSERT INTO.*wp_users" dump.sql | grep -oP "'[^']*@[^']*'" | sort -u

Count autoload records in wp_options – excessive autoload is a common cause of slow TTFB:

grep -c "'yes'" dump.sql

Compare plugin lists of two installations (e.g., staging vs production):

diff <(wp plugin list --format=csv --path=/site1 | awk -F',' '{print $1}' | sort) \ <(wp plugin list --format=csv --path=/site2 | awk -F',' '{print $1}' | sort)

8. Bulk file operations

Changing contact hours across 40 HTML pages, replacing tabs with spaces throughout a theme, converting line endings from Windows to Unix after editing in Notepad, adding a license header to all PHP files – these operations take hours manually but seconds with find + sed. If you prefer to delegate such tasks – we perform them regularly as part of our WordPress care plans.

Bulk text replacement in all HTML files:

find . -name "*.html" -exec sed -i 's/8:00–21:00/8:00–16:00/g' {} +

Replacement with HTML entity handling (e.g., &ndash; alongside UTF-8 ):

find . -name "*.html" -exec sed -i \ -e 's/8:00–21:00/8:00–16:00/g' \ -e 's/8:00\&ndash;21:00/8:00\&ndash;16:00/g' {} +

Rename .jpeg extensions to .jpg:

find wp-content/uploads/ -name "*.jpeg" -exec sh -c 'mv "$1" "${1%.jpeg}.jpg"' _ {} \;

Remove trailing whitespace from PHP files:

find wp-content/themes/ -name "*.php" -exec sed -i 's/[[:space:]]*$//' {} +

Replace tabs with 4 spaces:

find wp-content/themes/theme/ -name "*.php" -exec sed -i 's/\t/ /g' {} +

Find files with CRLF line endings (Windows):

find wp-content/themes/ -name "*.php" -exec file {} + | grep CRLF

Convert CRLF to LF:

find wp-content/themes/ -name "*.php" -exec sed -i 's/\r$//' {} +

Add a comment with date and author at the beginning of PHP files:

find wp-content/themes/theme/ -name "*.php" \ -exec sed -i '1i\<?php /* Modified: 2026-03-30 by WebOptimo */' {} +

Replace email address in all HTML files:

find . -name "*.html" -exec sed -i 's/kontakt@weboptimo\.pl/contact@weboptimo\.pl/g' {} +

Recursive phone number replacement:

find . -name "*.html" -exec sed -i 's/+48 600 000 000/+48 608 271 665/g' {} +

Frequently Asked Questions

The -i flag modifies files directly (in-place) – without creating a backup. Before making bulk changes in production, always create a backup or use sed -i.bak, which saves the original with a .bak extension. On test and staging servers, sed -i is perfectly safe.

The -r flag searches files recursively and displays matching lines with filenames. The -l (list) flag makes grep output only the names of files containing a match – without the actual lines. Use -rl when you need a list of files, and -rn when you need line numbers.

No. The WordPress database stores many values in PHP serialize format, which includes string length information. Replacing text with sed will change the content but won't update the length counter – which will corrupt the data. For search-and-replace in the WordPress database, use wp search-replace (details in the WP-CLI article), which correctly handles serialization.

Add --include="*.php" or --include="*.css" to grep, and -name "*.php" to find. This limits the operation to files with a specific extension. You can also use grep -I (uppercase i), which automatically skips binary files.

Most of them do, but macOS uses BSD sed, which requires sed -i '' (with an empty argument) instead of sed -i. Grep on macOS does not support the -P flag (Perl regex) – use grep -E instead or install GNU grep via Homebrew (brew install grep).

Need help with server administration or WordPress?

If you prefer to delegate a security audit, migration, or optimization instead of doing it yourself – let's talk. No commitments, no marketing jargon – a concrete proposal after a short conversation.

Phone

+48 608 271 665

Mon–Fri, 8:00–16:00 CET

E-mail

kontakt@weboptimo.pl

We respond within 24h

Company

WebOptimo

VAT ID: PL6391758393